How the SuperSplat UI System Integrates with the PlayCanvas Engine
The SuperSplat UI system integrates with the PlayCanvas engine through a custom PCApp class that extends AppBase, a shared Events bus for bidirectional communication, and a canvas element that bridges PCUI components with the WebGL renderer.
SuperSplat is an open-source Gaussian Splat editor built on top of the PlayCanvas engine. Understanding how its UI layer communicates with the underlying 3D runtime reveals a clean architecture that separates presentation from rendering while maintaining tight synchronization. This article examines the specific integration patterns found in the playcanvas/supersplat repository.
SuperSplat PlayCanvas Integration Architecture
The integration relies on three architectural pillars: a customized PlayCanvas application class, an event-driven UI layer built with PCUI, and shared state management through a central event bus.
Extending AppBase with PCApp
The PCApp class in src/pc-app.ts extends PlayCanvas’ AppBase to bootstrap the engine runtime. It registers the component systems required for rendering and simulation, including the standard render, camera, light, and animation systems, plus the custom GSplat component system specific to Gaussian Splatting.
// Simplified structure from src/pc-app.ts
class PCApp extends AppBase {
constructor(canvas: HTMLCanvasElement, options: any) {
super(canvas, options);
this.addComponentSystems();
this.addResourceHandlers();
}
addComponentSystems() {
// Registers GSplatComponentSystem alongside standard systems
}
}
PCApp also initializes resource handlers for loading textures, cubemaps, containers, and splat data via addResourceHandlers(). This eliminates boilerplate and provides the UI with a fully configured PlayCanvas instance ready for entity manipulation.
Event-Driven UI Layer
The UI is constructed in src/ui/editor.ts within the EditorUI class. It uses PCUI components such as Container and Label to compose panels, toolbars, and dialogs. Rather than directly accessing PlayCanvas internals, UI widgets communicate through a shared Events object instantiated in src/events.ts.
UI actions fire events like 'camera.focalPointPicked' or 'prerender', while the engine listens to these events to update the scene, camera position, or splat data. This pub/sub pattern ensures the UI remains framework-agnostic while driving engine state.
Key Integration Points Between UI and Engine
-
Canvas Element Bridging: The
EditorUIcreates an HTMLcanvaselement that is passed toPCAppvia thegraphicsDeviceoption. This provides the WebGL rendering surface that PlayCanvas targets each frame. -
Component System Registration: Through
PCApp.addComponentSystems(), the customGSplatComponentSystemis registered alongside standard PlayCanvas systems. UI tools manipulate entities containing these components, and PlayCanvas updates them during the simulation step. -
Resource Handler Pipeline:
PCApp.addResourceHandlers()adds loaders likeGSplatHandlerandTextureHandler. When UI dialogs request assets (e.g., image or video settings), these handlers load them into PlayCanvas’s asset registry. -
Shared Event Bus: The
Eventsobject enables loose coupling. UI dispatches events ('prerender','show.shortcuts','render.image') that the engine consumes, while the engine fires events ('camera.focalPointPicked') that UI widgets handle (seeEditorUIlines 89‑92). -
Gizmo Synchronization: UI overlays like the
ViewCubeinsrc/ui/view-cube.tsupdate on the'prerender'event using the current camera matrix, keeping interactive controls synchronized with the PlayCanvas camera.
Code Examples of UI-Engine Communication
The following patterns demonstrate the typical flow from UI interaction to engine execution and back.
UI triggering engine action:
// UI creates a button that requests camera focal point capture
const captureBtn = new Button({ text: 'Capture' });
captureBtn.on('click', () => events.fire('camera.captureFocalPoint'));
// Engine side listens and responds
events.on('camera.captureFocalPoint', () => {
const pos = camera.getPosition(); // PlayCanvas camera API
events.fire('camera.focalPointPicked', { position: pos });
});
// UI updates with returned position
events.on('camera.focalPointPicked', (details) => {
cursorLabel.text = `${details.position.x.toFixed(2)}, …`;
});
Canvas initialization:
// EditorUI creates and injects the canvas
const canvas = document.createElement('canvas');
canvas.id = 'canvas';
canvasContainer.append(canvas);
// PCApp receives the canvas and starts the render loop
const app = new PCApp(canvas, { graphicsDevice: gfxDevice });
app.start();
Dialog-driven rendering:
// ImageSettingsDialog invokes engine render operations
events.function('show.imageSettingsDialog', async () => {
const imgSettings = await imageSettingsDialog.show();
if (imgSettings) {
await events.invoke('render.image', imgSettings);
}
});
Summary
- PCApp in
src/pc-app.tsextends PlayCanvasAppBaseto register theGSplatComponentSystemand resource handlers, providing a configured engine instance. - EditorUI in
src/ui/editor.tsconstructs the interface using PCUI components and wires them to the engine exclusively through the sharedEventsbus. - The
canvaselement created by the UI is passed toPCAppas the rendering surface, bridging the DOM with WebGL. - Bidirectional communication occurs via event names like
'camera.focalPointPicked'and'prerender', keeping UI overlays synchronized with the PlayCanvas camera and scene state.
Frequently Asked Questions
What is PCApp in SuperSplat?
PCApp is a custom class in src/pc-app.ts that extends PlayCanvas’ AppBase. It bootstraps the engine by registering component systems (including the custom GSplatComponentSystem) and resource handlers, then manages the GraphicsDevice and render loop.
How does the UI communicate with the PlayCanvas engine without direct coupling?
The UI uses a shared Events object created in src/events.ts as a pub/sub bus. UI components fire events (e.g., 'camera.captureFocalPoint') that the engine listens to, and the engine fires events (e.g., 'camera.focalPointPicked') that UI widgets consume. This prevents direct imports between the UI layer and engine internals.
What role does the Events bus play in SuperSplat?
The Events bus acts as the central nervous system of the application. It allows the PCUI-based interface in src/ui/editor.ts to trigger engine operations like rendering or camera updates, and allows the engine to notify the UI of state changes such as focal point selection or prerender synchronization for gizmos.
Where is the canvas element created and how is it connected to PlayCanvas?
The canvas element is created in src/ui/editor.ts by the EditorUI class and appended to a container in the DOM. It is passed to the PCApp constructor via the graphicsDevice option, which attaches it to the PlayCanvas AppBase instance as the target for all WebGL rendering operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →