Create a new scene manager instance.
Sets up the EventDispatcher base class. Does not initialize Three.js components - call init() to set up renderer, scene, camera, and controls.
The graphics-API renderer. Holds the Renderer union honestly:
a THREE.WebGLRenderer on the default path, or a WebGPURenderer
when opted in via ?renderer=webgpu / VITE_LUXAR_USE_WEBGPU=1
(which itself may dispatch to a real WebGPU adapter or transparently
fall back to its internal WebGL2 backend depending on browser support).
Every method called on this field across the codebase
(PostProcessingManager, picking-system, BloomChain,
FxaaPass, UI panels) is part of the common Renderer surface
in Three r185 — no WebGLRenderer-only API is used
unconditionally. The discriminator for callers that genuinely
must branch is this.capabilities.apiSurface (see
RendererCapabilities).
The lazily built scene environment that lights material="physical" meshes;
null until init. Public so a debug surface or test can ask
isReady(); nothing else should need to touch it.
PrivateunsubscribeCapabilities snapshot for the active renderer. Hides raw-GL queries behind a typed interface so the eventual WebGPU port has a single implementation seam.
Three.js scene graph - container for all 3D objects and lights
Camera for 3D viewing (perspective or orthographic)
ControlsManager - manages different camera control types (orbit, fly, ortho)
HDR post-processing manager for bloom and tone mapping effects
PrivatecanvasThe HTML canvas element where 3D rendering occurs
PrivateisTrack whether we're centered on bounding box or origin
PrivatelastStore the last calculated bounding box center
PrivatesceneThe scene's up vector — world +Y unless the zarr viewer_config
authored one (set in loadSceneData). Every camera fit/reset
(Home/F, center-on-origin, load-time auto-frame) squares to this,
so author-oriented scenes reset to THEIR horizon, not world Y.
PrivateresizerResize debouncing with requestAnimationFrame for smooth resizing
PrivatecontextWebGL context-loss / restoration concern. Constructed lazily in setupContextLossHandling() once the canvas + renderer are wired up. The class owns the canvas listeners and the isContextLost flag — SceneManager just forwards events through it.
PrivatelastPerspective FOV in effect at the last perspective→ortho swap, restored on the inverse ortho→perspective swap so a control-mode round trip preserves the user's FOV instead of resetting it to the config default.
PrivatedynamicDynamic clipping planes state
Private ReadonlyboundsCached scene bounds + bounding sphere + near-cull margin.
Invalidated on scene load/clear; lazily recomputed by
boundsCache.ensure(scene).
Private Readonly_Reusable Vector2 for getDrawingBufferSize (avoids per-call allocation).
Ownership contract: This Vector2 instance is owned by SceneManager
and is reused across every camera-materials update — every resize
mutates it in place. Consumers reached via makeCameraMaterialsCtx
MUST treat the supplied bufferSize as read-only borrow scoped to
the current call. If a consumer needs to hold the value across
frames, it must .copy() the Vector2 into its own storage, not
retain the shared reference — otherwise the next resize will silently
corrupt the held value.
PrivatepixelExplicit DPR selected by adaptive/manual resolution control.
null means "track the live pixel-ratio ceiling".
Non-null values must survive ordinary window resizes; otherwise a
resize event immediately after a manual DPR change silently restores
ceiling resolution while the AdaptiveDPRManager/UI still reports the
reduced DPR.
PrivatedebugWhen true, additional hardware/runtime info is logged at startup.
Set via init's debug flag (forwarded from ?debug URL parameter).
PrivaterendererOptional per-instance backend override. When set, setupRenderer
uses it instead of consulting the env var. Threaded from the
?renderer=webgl|webgpu URL parameter through LuxarAppOptions.
PrivatewebgpuDiagnostic WebGPURenderer mode. When true and the selected renderer is
WebGPURenderer, pass { forceWebGL: true } so Three.js uses its
internal WebGL2 backend while Luxar still dispatches TSL materials.
PrivateperfOpt-in to WebGPURenderer({ trackTimestamp: true }) for the perf
bench. Off by default; flipped via ?perfTimestamp URL param.
PrivateblendWebGL-only blend-variant warm-up (?noBlendWarmup disables).
When true, resize events are suppressed (used during recording to prevent resolution changes). Delegated to ResizeOrchestrator — exposed as a getter/setter so recording-panel.ts continues to read
sceneManager.resizeLocked directly.Current FOV in degrees. Perspective: the live camera FOV. Orthographic:
the stashed perspective FOV (lastPerspectiveFov) — the single source of
truth for the FOV that a future ortho→perspective swap will restore, so a
FOV set while in ortho (Reset-to-Defaults / zarr-authored) is reported and
honored rather than masked by the config default.
The pixel ratio the renderer is currently sized for: the explicit override if one is engaged, otherwise the live ceiling — and clamped to that ceiling either way.
Exposed so callers that need to re-apply the pixel ratio by hand
(the recording session, after a scaled capture) can ask for the
value the resize path would use instead of reaching for
window.devicePixelRatio, which respects neither the override nor
the pixel-ratio cap.
Set an absolute perspective FOV using rendering-setting validation semantics. Invalid values fall back to the configured default rather than clamping.
Apply an authored zoom after the camera has switched to ortho projection.
Initialize the renderer pipeline.
The HTMLCanvasElement to render into. Callers
resolve this themselves (document.getElementById(...) in the
standalone app's main.ts; arbitrary container child for embedders).
SceneManager performs no DOM lookups of its own.
Optionaldebug?: booleanVerbose hardware/runtime logging.
Optionalrenderer?: "webgpu" | "webgl"Optional backend override. When provided, wins over the
VITE_LUXAR_USE_WEBGPU / VITE_LUXAR_USE_LEGACY_WEBGL env vars
and the WebGL default. Threaded from LuxarAppOptions.renderer,
ultimately from the ?renderer=webgl|webgpu URL parameter.
OptionalwebgpuForceWebGL?: booleanDiagnostic flag for WebGPURenderer({ forceWebGL: true }).
Threaded from LuxarAppOptions.webgpuForceWebGL, ultimately from
the ?webgpuForceWebgl URL parameter.
OptionalperfTimestamp?: booleanOpt-in to GPU timestamp queries. Threaded from
LuxarAppOptions.perfTimestamp, ultimately from the
?perfTimestamp URL flag set by the perf bench.
OptionalblendWarmup?: booleanWebGL-only blend warm-up. When true, classic THREE.WebGLRenderer
sessions pre-compile each DISTINCT blend-mode program variant a
material can reach, one compile per post-frame idle opportunity, so the first
Layers-panel blend switch does not pay SwiftShader's synchronous
link cost on the click path.
PrivatesetupInitialize Three.js WebGL renderer with optimal settings
This creates the WebGL rendering context that will handle all GPU operations. Key configurations:
PrivatesetupPrivatesetupOpt-in renderer setup — selected via ?renderer=webgpu URL flag
or VITE_LUXAR_USE_WEBGPU=1. Constructs a WebGPURenderer and
runs its async init(). On browsers with WebGPU support, the
renderer acquires a WebGPU adapter and dispatches TSL graphs
to WGSL. On browsers without WebGPU (Firefox today, older
Safari), Three's WebGPURenderer transparently falls back to a
WebGL2 backend — the TSL graphs target both from one source.
NOTE: WebGL is the production default; this method is
reached only when the user explicitly opts into WebGPU.
See BROWSER_SUPPORT_POLICY.md for the policy.
PrivatesetupConstruct the WebGLContextRecovery concern and attach its
canvas listeners (WebGL2 path), OR attach a device.lost
observer that surfaces the failure as an event (WebGPU path).
WebGL2 path. The recovery instance owns the loss/restored
handlers, the isContextLost flag, and the deterministic
rebuild order. Bound here because webglcontextlost /
webglcontextrestored canvas events fire only on WebGL
contexts.
WebGPU path. Three's WebGPURenderer recreates its own
GPU device internally when device.lost resolves, but it does
not rebuild Luxar-owned resources (post-processing render
targets, picking buffers, material caches, interleaved geometry
buffers). A full rebuild path mirroring WebGL2 is non-trivial
and untested in CI today, so for now we treat WebGPU device
loss as unrecoverable: log it loudly and dispatch a
webgpu-device-lost event so the host application can prompt
for a reload. The event payload carries the device-loss reason
for diagnostics.
Check if WebGL context is currently lost. Forwards to the
recovery instance; returns false when recovery hasn't been
wired up yet (pre-init / post-dispose).
PrivateconfigurePrivatesetupInitialize the Three.js scene
PrivatesetupWire the LAZY scene environment (rendering/environment/scene-environment.ts).
Nothing is built here. The environment — a prefiltered RoomEnvironment on
scene.environment, the one lighting input a physically based mesh material
needs — is constructed the first time the material manager creates a physical
mesh material and never otherwise, so a scene without one keeps
scene.environment === null and renders exactly as it did before the
environment existed. House materials never read it either way (spec
MESH_PHYSICAL_MATERIALS_SPEC.md §3.3).
PrivatesetupInitialize the perspective camera. Thin delegate over
createDefaultPerspectiveCamera in scene-manager/camera/camera-setup
so the FOV/clip/initial-position pose is unit-testable in isolation.
PrivatesetupInitialize ControlsManager for flexible camera control
ControlsManager supports multiple control types:
Features:
PrivateresetReset camera + controls to default state when loading a new
dataset. Camera-side reset is delegated to
resetCameraToInitialPosition; the controls-side state machine
(reset → update → saveState) stays here because it's the
controls manager's contract.
PrivatesetupInitialize the HDR post-processing pipeline. Wires the manager's resize callback to refresh material uniforms that cache the drawing-buffer size.
Load scene data from Zarr source.
Cache and prefetch flags propagate through loaderConfig from
LuxarApp (originally derived from ?noCache/?cacheDebug/etc URL
parameters in main.ts).
options.applyViewerConfigFov is the caller's localStorage-precedence
decision, not a feature switch. Returning visitors keep their stored FOV
for auto-framed scenes; an authored position instead carries the resolved
scene FOV with it as one framing contract. Under an orthographic camera the
FOV only stashes the next perspective value; framing uses camera zoom, and
the later projection swap preserves that frustum.
OptionalloaderConfig: LoaderConfigGet the viewer config from the loaded scene's root group userData.
The baked environment map the loader found in the store, if any (see loadBakedEnvironment).
Give the scene environment what a live scene capture needs (the init pipeline
calls this once the load-activity predicate exists). The capture pushes the cube
camera's params (a square drawing buffer, perspective, pixel ratio 1) to the
material manager so point and line footprints render at the right size in the six
faces — the 90° projection itself is read in shader from the cube camera's matrix
— and restores the main camera's push afterwards through the ordinary path.
Arm WebGL blend warm-up after all scene-dependent dataset setup completes.
PrivateapplyApply viewer config from zarr (camera position/target/up, background
color). Thin delegate over applyZarrViewerConfig in
scene-manager/camera/camera-setup; forwards the helper's
positionApplied flag so loadSceneData can suppress auto-framing.
(Author target alone does NOT suppress auto-framing.)
PrivateclearClear all loaded content from the scene, keeping lights and background.
Thin delegate over clearLoadedSceneContent in
scene-manager/render-pipeline/scene-disposal.
Center camera on the bounding box of all visible objects in scene.
Computes the bounding box of all Points and InstancedMesh objects, positions camera to view entire scene, and updates controls target. Skips centering if no geometry is found in the scene.
Called automatically after scene loading if dataset has reasonable size. Can be called manually via F key to recenter after navigation.
Frame the camera on one object subtree (the per-layer sibling of centerCameraOnScene). Returns false when the subtree holds no framable geometry (e.g. a partition whose parts haven't streamed yet).
Get current camera target center point.
Returns either the bounding box center (if auto-centered) or origin (0,0,0) if using default positioning. The returned vector is a clone, safe to modify.
Current center as Vector3 (bounding box center or origin)
Get controls manager for camera interaction.
Provides access to orbit, fly, and ortho controls for advanced camera manipulation.
ControlsManager instance managing camera controls
Toggle camera centering between origin and bounding box center.
Switches between two centering modes:
Useful when dataset is not centered at origin or when you want to return to default camera position.
Center camera and controls on the origin (0,0,0) at the current distance. Also used by the rail Home popover's "Center on origin" action. Clears the bounding-box-centered flag so a subsequent toggleCentering switches back to the bounding box.
Update canvas size and camera aspect ratio for window resize.
Handles window resize events with debouncing via requestAnimationFrame. Updates:
Called automatically on window resize. Debouncing ensures smooth resize without excessive recomputations.
PrivatemeasureMeasure the viewport the canvas should fill.
Fullscreen-first: while any fullscreen is active the canvas is styled
to fill the screen (100vw/100vh, see
window-event-handler.onFullscreenChange, which keys off the same
isDocumentFullscreen() check — standard + webkit), so its DOM parent — an embed
container — no longer reflects its displayed size. Measure the window.
Parent-first otherwise: Three.js stamps inline width/height px
styles onto the canvas on every setSize, so the canvas's own client box
reflects our last stamp, not the host's layout. The parent element (the
embedder's frame, or document.body in the standalone app — sized 100% by
base/layout.css) is the box that actually tracks layout changes. Falls
back to the canvas's own box, then the window, when the parent reports
zero (detached canvas, jsdom).
Resize to the canvas's container box rather than the window.
This is the embedding-safe resize path: an embedded canvas lives inside
a host container whose size can change without the window changing
(sidebars, splitters, flex/grid reflow), so a window.innerWidth-based
resize would stamp window-sized inline styles onto the canvas and
overflow the host frame. Sizing comes from measureViewport
(parent-first). Synchronous via resizeNow; callers that fire it from
a ResizeObserver already get browser-batched delivery (~once/frame).
PrivatemakeBuild the per-call ResizeCtx snapshot used by the resize orchestrator.
PrivatesetStore/clear the explicit DPR override and return the effective DPR. Delegates the math to dpr-policy.computePixelRatioOverride.
Update pixel ratio for adaptive performance optimization.
Uses setSize with updateStyle=false to keep canvas CSS size constant while reducing the internal buffer resolution for better performance.
This method is called by the AdaptiveDPRManager when FPS drops below acceptable thresholds, and by the manual DPR control when adaptive mode is disabled.
The new device pixel ratio to use
Update perspective camera FOV with bounds checking. Returns true when applied. In orthographic mode there is no live FOV to change, but the request is applied to the stashed perspective FOV so a FOV set while in ortho is honored on the next ortho→perspective swap.
Update camera clipping planes with validation.
Auto-adjust clipping planes from scene bounds (metadata first, geometry fallback). Also feeds the bounding-box diagonal into the scale-aware controls.
PrivateautoAuto-frame the camera to fit the scene contents using metadata bounds. Delegates to the camera-framing helper. Updates the centering-state tracking fields when framing succeeds.
If true, keep the current controls target (set by zarr viewer_config) instead of overwriting it with the bounding box center.
PrivateinvalidateInvalidate cached scene bounds. Called on scene load / clear. Display dims are immutable per scene, so no invalidation is needed for dimension navigation.
Update dynamic clipping planes using cached bounding sphere projection.
Called each frame by AnimationController. Uses a cached bounding sphere
(invalidated on scene load/clear) for smooth near/far values with zero
object allocations and — once the metadata bounds are cached — zero
per-frame scene graph traversal. A metadata-less scene is the exception:
the cache has no negative caching, so the per-frame ensure() re-walks
the graph each frame (see clipping/scene-bounds-cache.ts).
PrivatemakeBuild the narrow ctx that the clipping-policy helpers consume. Created on demand to keep helper signatures stable as subsystem fields evolve.
Set dynamic clipping enabled/disabled.
Get current dynamic clipping state.
PrivategetGet 3D bounding box from scene metadata, projecting nD bounds to display dimensions. Delegates to the bounds-cache helper.
3D bounding box or null if metadata bounds not available
Update global exposure (log2 stops). Applied in the vendored tone mapping shader before tone mapping.
Update global offset (additive brightness shift). Applied in the vendored tone mapping shader before tone mapping.
Update global gamma correction. Applied in the vendored tone mapping shader before tone mapping.
Clean up all Three.js resources to prevent memory leaks.
WebGL resources (textures, buffers, shaders) are not automatically garbage collected and must be explicitly disposed. This method ensures proper cleanup of all GPU resources:
After calling dispose(), the scene manager cannot be reused.
Enable or disable automatic camera rotation
Whether to enable auto-rotation
Set the turntable speed, in REVOLUTIONS PER MINUTE.
A full turn takes 60 / speed seconds — 1.0 is one turn a minute, the
0.25 default is one turn every four minutes. The unit is inherited from
three.js OrbitControls and is what auto_rotate_speed means in every
authored scene, so it is the stored unit; the Navigation popover shows the
equivalent PERIOD in seconds (see secondsPerTurnFromRpm).
Frame-rate independent: the update step scales by deltaTime, so the
turn takes the same wall-clock time at 30 fps and at 144.
Revolutions per minute (> 0).
Set the camera-frame or fixed scene axis the turntable revolves around; see AutoRotateAxis.
Enable/disable the auto-dolly: a sinusoidal in-and-out motion along the
view direction, the turntable's radial sibling. Live in orbit AND ortho
(in 2D it breathes camera.zoom).
Dolly amplitude as a percent of the viewing distance (15 → ±15%).
Dolly period in seconds (one full in-and-out oscillation).
Orbit wheel-zoom speed (live; shared with ortho — same control class).
Orbit damping factor (live; shared with ortho — same control class).
Fly-mode mouse-look sensitivity (live on the current fly controls).
Toggle "natural drag" — swap LEFT ↔ RIGHT mouse buttons in orbit mode so a one-finger touchpad drag rotates and right-drag pans. Applies to orbit (3D) only; ortho and fly modes ignore.
Get current auto-rotation state
Current auto-dolly state (see setAutoDolly).
Current turntable axis (see setAutoRotateAxis).
Switch camera control type. Handles camera swap for ortho mode and
dispatches camera-changed at this public call site.
Control type ('orbit', 'fly', or 'ortho')
Get current control type.
PrivatemakeBuild the narrow ctx that the camera-mode helpers consume.
Update all materials with current camera projection parameters. Perspective: FOV-based. Orthographic: frustum-based.
Called internally on resize and camera changes. Also called by RecordingPanel when the renderer is resized for offline capture.
PrivatemakeBuild the narrow ctx that the camera-materials helpers consume.
Set fly controls movement speed
Set fly controls rotation speed
Set fly controls inertial mode
Set fly controls damping
Set fly controls rotation damping
Get current scene scale (bounding box diagonal). Returns 0 if not yet set.
SceneManager orchestrates all Three.js components for 3D rendering
Responsibilities:
Technical Details: