Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Class SceneManager

    SceneManager orchestrates all Three.js components for 3D rendering

    Responsibilities:

    • WebGL renderer setup with HDR capabilities
    • Camera configuration for optimal 3D viewing
    • Control system for user interaction (rotation, zoom, pan)
    • Scene graph management for 3D objects
    • HDR post-processing pipeline with bloom effects
    • Advanced shader-based points rendering
    • Dynamic loading of points data from Zarr sources
    • Resource cleanup to prevent memory leaks

    Technical Details:

    • Uses perspective camera for realistic 3D projection
    • LuxarOrbitControls provide quaternion-based camera movement (no gimbal lock)
    • HDR post-processing with ACES tone mapping and bloom
    • Custom Gaussian point shaders for enhanced visual quality
    • Automatic canvas resizing for responsive design

    Hierarchy

    • EventDispatcher<
          {
              change: {};
              "camera-changed": {};
              "webgl-context-restored": {};
              "webgpu-device-lost": { reason?: string; message?: string };
          },
      >
      • SceneManager
    Index
    • 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.

      Returns SceneManager

      const sceneManager = new SceneManager();
      await sceneManager.init(); // Initialize Three.js components
      await sceneManager.loadSceneData(url); // Load data
    renderer: Renderer

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

    environment: SceneEnvironment | null = null

    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.

    unsubscribeEnvironment: (() => void) | null = null
    capabilities: RendererCapabilities

    Capabilities snapshot for the active renderer. Hides raw-GL queries behind a typed interface so the eventual WebGPU port has a single implementation seam.

    scene: Scene

    Three.js scene graph - container for all 3D objects and lights

    camera: LuxarCamera

    Camera for 3D viewing (perspective or orthographic)

    controls: ControlsManager

    ControlsManager - manages different camera control types (orbit, fly, ortho)

    postProcessing: PostProcessingManager

    HDR post-processing manager for bloom and tone mapping effects

    canvasElement: HTMLCanvasElement

    The HTML canvas element where 3D rendering occurs

    isCenteredOnBoundingBox: boolean = false

    Track whether we're centered on bounding box or origin

    lastBoundingBoxCenter: Vector3 = ...

    Store the last calculated bounding box center

    sceneUp: Vector3 = ...

    The 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.

    resizer: ResizeOrchestrator = ...

    Resize debouncing with requestAnimationFrame for smooth resizing

    contextRecovery: WebGLContextRecovery | null = null

    WebGL 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.

    lastPerspectiveFov: number = config.renderingControls.defaults.fov

    Perspective 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.

    dynamicClippingEnabled: boolean = config.renderingControls.defaults.dynamicClippingEnabled

    Dynamic clipping planes state

    boundsCache: SceneBoundsCache = ...

    Cached scene bounds + bounding sphere + near-cull margin. Invalidated on scene load/clear; lazily recomputed by boundsCache.ensure(scene).

    _bufferSize: Vector2 = ...

    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.

    pixelRatioOverride: number | null = null

    Explicit 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.

    debug: boolean = false

    When true, additional hardware/runtime info is logged at startup. Set via init's debug flag (forwarded from ?debug URL parameter).

    rendererOverride: "webgpu" | "webgl" | undefined

    Optional 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.

    webgpuForceWebGL: boolean = false

    Diagnostic 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.

    perfTimestamp: boolean = false

    Opt-in to WebGPURenderer({ trackTimestamp: true }) for the perf bench. Off by default; flipped via ?perfTimestamp URL param.

    blendWarmup: boolean = true

    WebGL-only blend-variant warm-up (?noBlendWarmup disables).

    • get resizeLocked(): boolean

      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

      • write sceneManager.resizeLocked directly.

      Returns boolean

    • set resizeLocked(v: boolean): void

      Parameters

      • v: boolean

      Returns void

    • get currentFov(): number

      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.

      Returns number

    • get activePixelRatio(): number

      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.

      Returns number

    • Set an absolute perspective FOV using rendering-setting validation semantics. Invalid values fall back to the configured default rather than clamping.

      Parameters

      • degrees: number

      Returns boolean

    • Apply an authored zoom after the camera has switched to ortho projection.

      Parameters

      • zoom: number

      Returns void

    • Initialize the renderer pipeline.

      Parameters

      • options: {
            canvas: HTMLCanvasElement;
            debug?: boolean;
            renderer?: "webgpu" | "webgl";
            webgpuForceWebGL?: boolean;
            perfTimestamp?: boolean;
            blendWarmup?: boolean;
        }
        • canvas: HTMLCanvasElement

          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?: boolean

          Verbose 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?: boolean

          Diagnostic flag for WebGPURenderer({ forceWebGL: true }). Threaded from LuxarAppOptions.webgpuForceWebGL, ultimately from the ?webgpuForceWebgl URL parameter.

        • OptionalperfTimestamp?: boolean

          Opt-in to GPU timestamp queries. Threaded from LuxarAppOptions.perfTimestamp, ultimately from the ?perfTimestamp URL flag set by the perf bench.

        • OptionalblendWarmup?: boolean

          WebGL-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.

      Returns Promise<void>

    • Initialize Three.js WebGL renderer with optimal settings

      This creates the WebGL rendering context that will handle all GPU operations. Key configurations:

      • Antialiasing for smooth edges (MSAA)
      • Custom canvas element for precise DOM control
      • High DPI display support via pixel ratio
      • Accessibility attributes for screen readers
      • Fullscreen immersive experience

      Returns Promise<void>

    • Opt-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.

      Returns Promise<void>

    • Construct 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.

      Returns void

    • 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).

      Returns boolean

    • Wire 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).

      Returns void

    • Initialize 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.

      Returns void

    • Initialize ControlsManager for flexible camera control

      ControlsManager supports multiple control types:

      • Orbit: Traditional orbital camera with auto-rotation
      • Fly: First-person flying controls with inertia

      Features:

      • Hot-swapping between control types
      • Auto-rotation for presentations
      • Inertial and non-inertial movement modes
      • Smooth transitions and state preservation

      Returns void

    • Reset 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.

      Returns void

    • Initialize the HDR post-processing pipeline. Wires the manager's resize callback to refresh material uniforms that cache the drawing-buffer size.

      Returns void

    • 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.

      Parameters

      Returns Promise<void>

    • 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.

      Parameters

      • isSettled: () => boolean

      Returns void

    • Arm WebGL blend warm-up after all scene-dependent dataset setup completes.

      Returns Promise<void>

    • Apply 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.)

      Parameters

      • root: Group

      Returns { positionApplied: boolean; appliedUp: Vector3 | null }

    • Clear all loaded content from the scene, keeping lights and background. Thin delegate over clearLoadedSceneContent in scene-manager/render-pipeline/scene-disposal.

      Returns void

    • 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.

      Returns void

      // After loading scene
      await sceneManager.loadSceneData(url);
      sceneManager.centerCameraOnScene();
      // Camera now frames entire dataset
    • 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).

      Parameters

      • obj: Object3D

      Returns boolean

    • 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.

      Returns Vector3

      Current center as Vector3 (bounding box center or origin)

    • Toggle camera centering between origin and bounding box center.

      Switches between two centering modes:

      • Origin (0,0,0): Default Three.js behavior
      • Bounding box center: Computed from all visible objects

      Useful when dataset is not centered at origin or when you want to return to default camera position.

      Returns void

      // Switch to origin centering
      sceneManager.toggleCentering();
    • 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.

      Returns void

    • Update canvas size and camera aspect ratio for window resize.

      Handles window resize events with debouncing via requestAnimationFrame. Updates:

      • Canvas dimensions to match window size
      • Camera aspect ratio to prevent distortion
      • Renderer viewport
      • Post-processing effect sizes

      Called automatically on window resize. Debouncing ensures smooth resize without excessive recomputations.

      Returns void

      // Manual resize (usually not needed, window resize auto-triggers)
      sceneManager.updateSize();
    • Measure 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).

      Returns { width: number; height: number }

    • 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).

      Returns void

    • Store/clear the explicit DPR override and return the effective DPR. Delegates the math to dpr-policy.computePixelRatioOverride.

      Parameters

      • dpr: number

      Returns number

    • 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.

      Parameters

      • dpr: number

        The new device pixel ratio to use

      Returns void

    • 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.

      Parameters

      • deltaY: number

      Returns boolean

    • Update camera clipping planes with validation.

      Parameters

      • near: number
      • far: number

      Returns void

    • Auto-adjust clipping planes from scene bounds (metadata first, geometry fallback). Also feeds the bounding-box diagonal into the scale-aware controls.

      Returns { near: number; far: number }

    • Auto-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.

      Parameters

      • preserveTarget: boolean = false

        If true, keep the current controls target (set by zarr viewer_config) instead of overwriting it with the bounding box center.

      Returns void

    • Invalidate cached scene bounds. Called on scene load / clear. Display dims are immutable per scene, so no invalidation is needed for dimension navigation.

      Returns void

    • 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).

      Returns void

    • Set dynamic clipping enabled/disabled.

      Parameters

      • enabled: boolean

      Returns void

    • Get current dynamic clipping state.

      Returns { enabled: boolean; near: number; far: number }

    • Get 3D bounding box from scene metadata, projecting nD bounds to display dimensions. Delegates to the bounds-cache helper.

      Returns BoundingBox | null

      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.

      Parameters

      • value: number

      Returns void

    • Update global offset (additive brightness shift). Applied in the vendored tone mapping shader before tone mapping.

      Parameters

      • value: number

      Returns void

    • Update global gamma correction. Applied in the vendored tone mapping shader before tone mapping.

      Parameters

      • value: number

      Returns void

    • 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:

      1. Post-processing: Dispose HDR render targets and effect composer
      2. Controls: Remove event listeners and internal references
      3. Renderer: Clean up WebGL context and associated resources
      4. Scene objects: Dispose geometry buffers and material shaders
      5. Materials: Free texture memory and shader programs

      After calling dispose(), the scene manager cannot be reused.

      Returns void

    • Enable or disable automatic camera rotation

      Parameters

      • enabled: boolean

        Whether to enable auto-rotation

      Returns void

    • 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.

      Parameters

      • speed: number

        Revolutions per minute (> 0).

      Returns void

    • 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).

      Parameters

      • enabled: boolean

      Returns void

    • Dolly amplitude as a percent of the viewing distance (15 → ±15%).

      Parameters

      • percent: number

      Returns void

    • Dolly period in seconds (one full in-and-out oscillation).

      Parameters

      • seconds: number

      Returns void

    • Orbit wheel-zoom speed (live; shared with ortho — same control class).

      Parameters

      • speed: number

      Returns void

    • Orbit damping factor (live; shared with ortho — same control class).

      Parameters

      • factor: number

      Returns void

    • Fly-mode mouse-look sensitivity (live on the current fly controls).

      Parameters

      • speed: number

      Returns void

    • 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.

      Parameters

      • enabled: boolean

      Returns void

    • Switch camera control type. Handles camera swap for ortho mode and dispatches camera-changed at this public call site.

      Parameters

      • type: ControlType

        Control type ('orbit', 'fly', or 'ortho')

      Returns void

    • 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.

      Returns void

    • Set fly controls inertial mode

      Parameters

      • inertial: boolean

      Returns void

    • Set fly controls rotation damping

      Parameters

      • damping: number

      Returns void

    • Get current scene scale (bounding box diagonal). Returns 0 if not yet set.

      Returns number