@babylonjs/viewer
Version:
The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.
1,156 lines (1,154 loc) • 76.3 kB
TypeScript
import { HotSpotQuery, Nullable, IDisposable, IColor4Like, IReadonlyObservable } from '@babylonjs/core/index.js';
import { Observable } from '@babylonjs/core/Misc/observable.js';
import { EngineContext } from '@babylonjs/lite';
import * as lit from 'lit';
import { LitElement, CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
import * as lit_html from 'lit-html';
/**
* Flags for selectively resetting parts of the viewer state.
*/
type ResetFlag = "source" | "environment" | "camera" | "animation" | "post-processing" | "material-variant" | "shadow";
declare const shadowQualityOptions: readonly ["none", "normal", "high"];
/**
* Shadow quality levels.
*/
type ShadowQuality = (typeof shadowQualityOptions)[number];
declare const toneMappingOptions: readonly ["none", "standard", "aces", "neutral"];
/**
* Tone mapping modes.
*/
type ToneMapping = (typeof toneMappingOptions)[number];
declare const ssaoOptions: readonly ["enabled", "disabled", "auto"];
/**
* Screen-space ambient occlusion options.
*/
type SSAOOptions = (typeof ssaoOptions)[number];
/**
* Camera orbit as [alpha, beta, radius].
*/
type CameraOrbit = [alpha: number, beta: number, radius: number];
/**
* Camera target as [x, y, z].
*/
type CameraTarget = [x: number, y: number, z: number];
/**
* Camera auto-orbit configuration.
*/
type CameraAutoOrbit = {
/**
* Whether the camera should automatically orbit around the model when idle.
*/
enabled: boolean;
/**
* The speed at which the camera orbits around the model when idle.
*/
speed: number;
/**
* The delay in milliseconds before the camera starts orbiting around the model when idle.
*/
delay: number;
};
/**
* Environment configuration parameters.
*/
type EnvironmentParams = {
/**
* The intensity of the environment lighting.
*/
intensity: number;
/**
* The blur applied to the environment lighting.
*/
blur: number;
/**
* The rotation of the environment lighting in radians.
*/
rotation: number;
};
/**
* Shadow configuration parameters.
*/
type ShadowParams = {
/**
* The quality of shadow being used.
*/
quality: ShadowQuality;
};
/**
* Post-processing configuration.
*/
type PostProcessing = {
/**
* The tone mapping to use for rendering the scene.
*/
toneMapping: ToneMapping;
/**
* The contrast applied to the scene.
*/
contrast: number;
/**
* The exposure applied to the scene.
*/
exposure: number;
/**
* Whether to enable screen space ambient occlusion (SSAO).
*/
ssao: SSAOOptions;
};
/**
* Options for controlling which parts of the environment to update.
*/
type EnvironmentOptions = Partial<Readonly<{
/**
* Whether to use the environment for lighting (e.g. IBL).
*/
lighting: boolean;
/**
* Whether to use the environment for the skybox.
*/
skybox: boolean;
}>>;
/**
* Options for loading an environment.
*/
type LoadEnvironmentOptions = EnvironmentOptions & Partial<Readonly<{
/**
* Specifies the extension of the environment texture to load.
* This must be specified when the extension cannot be determined from the url.
*/
extension: string;
}>>;
/**
* @internal `LoadEnvironmentOptions` after the base class has resolved the optional `lighting` and
* `skybox` flags to definite booleans (defaults to `true` for both when omitted, otherwise honors the
* caller's choice). Engine-specific extras such as `extension` are forwarded as-is. Passed to the
* subclass `_loadEnvironmentImpl` so it doesn't repeat the default-resolution logic.
*/
type ResolvedLoadEnvironmentOptions = Omit<LoadEnvironmentOptions, "lighting" | "skybox"> & {
readonly lighting: boolean;
readonly skybox: boolean;
};
/**
* A hot spot query specifying either a surface point or a fixed world position.
*/
type ViewerHotSpotQuery = ({
/**
* The type of the hot spot.
*/
type: "surface";
/**
* The index of the mesh within the loaded model.
*/
meshIndex: number;
} & HotSpotQuery) | {
/**
* The type of the hot spot.
*/
type: "world";
/**
* The fixed world space position of the hot spot.
*/
position: [x: number, y: number, z: number];
/**
* The fixed world space normal of the hot spot.
*/
normal: [x: number, y: number, z: number];
};
/**
* A hot spot definition with an optional camera pose.
*/
type HotSpot = ViewerHotSpotQuery & {
/**
* An optional camera pose to associate with the hotspot.
*/
cameraOrbit?: CameraOrbit;
};
/**
* Provides the result of a hot spot query.
*/
declare class ViewerHotSpotResult {
/**
* 2D canvas position in pixels.
*/
readonly screenPosition: [x: number, y: number];
/**
* 3D world coordinates.
*/
readonly worldPosition: [x: number, y: number, z: number];
/**
* Visibility range is [-1..1]. A value of 0 means camera eye is on the plane.
*/
visibility: number;
}
/**
* Backend-agnostic options for loading a model.
* @remarks
* The full Viewer accepts the wider LoadAssetContainerOptions from core.
* This type captures the subset that both backends support.
*/
type ViewerLoadModelOptions = Partial<Readonly<{
/**
* The file extension to use for determining the loader plugin (e.g. ".glb", ".gltf").
*/
pluginExtension: string;
/**
* If true, load glTF files using the OpenPBR material instead of the default PBR material.
* Overrides the corresponding constructor option for this load.
* @experimental
*/
useOpenPBR: boolean;
}>>;
/**
* Backend-agnostic options shared by all viewer implementations.
*/
type ViewerBaseOptions = Partial<{
/**
* The default clear color of the scene.
*/
clearColor: [r: number, g: number, b: number, a?: number];
/**
* When enabled, rendering will be suspended when no scene state driven by the Viewer has changed.
* This can reduce resource CPU/GPU pressure when the scene is static.
* Enabled by default.
*/
autoSuspendRendering: boolean;
/**
* The default source model to load into the viewer.
*/
source: string;
/**
* The file extension to use for determining the loader plugin for the default source model (e.g. ".glb", ".obj").
* @remarks
* If not set, the extension is inferred from the source URL when possible. This is needed for sources whose
* extension cannot be inferred from the URL (e.g. data URLs or extension-less URLs).
*/
pluginExtension: string;
/**
* The default environment to load into the viewer for lighting (IBL).
*/
environmentLighting: string;
/**
* The default environment to load into the viewer for the skybox.
*/
environmentSkybox: string;
/**
* The default environment configuration.
*/
environmentConfig: Partial<EnvironmentParams>;
/**
* The default camera orbit.
* @remarks The default camera orbit is restored when a new model is loaded.
*/
cameraOrbit: Partial<CameraOrbit>;
/**
* The default camera target.
* @remarks The default camera target is restored when a new model is loaded.
*/
cameraTarget: Partial<CameraTarget>;
/**
* Automatically rotates a 3D model or scene without requiring user interaction.
* @remarks The default camera auto orbit is restored when a new model is loaded.
*/
cameraAutoOrbit: Partial<CameraAutoOrbit>;
/**
* Whether to play the default animation immediately after loading.
* @remarks The default animation auto play is restored when a new model is loaded.
*/
animationAutoPlay: boolean;
/**
* The default speed of the animation.
* @remarks The default animation speed is restored when a new model is loaded.
*/
animationSpeed: number;
/**
* The default selected animation.
* @remarks The default selected animation is restored when a new model is loaded.
*/
selectedAnimation: number;
/**
* The default post processing configuration.
*/
postProcessing: Partial<PostProcessing>;
/**
* Shadow configuration.
*/
shadowConfig: Partial<ShadowParams>;
/**
* The default selected material variant.
* @remarks The default material variant is restored when a new model is loaded.
*/
selectedMaterialVariant: string;
/**
* The default hotspots.
*/
hotSpots: Record<string, HotSpot>;
/**
* Boolean indicating if the scene must use right-handed coordinates system.
*/
useRightHandedSystem: boolean;
/**
* If true, load glTF files using the OpenPBR material instead of the default PBR material.
* @experimental
*/
useOpenPBR: boolean;
/**
* Called when a fatal error occurs that prevents the viewer from functioning.
*/
onFaulted: (error: Error) => void;
}>;
/**
* The subset of the Viewer API that ViewerElementBase depends on.
* Both the full Babylon.js Viewer and ViewerLite implement this contract.
*/
interface IViewer extends IDisposable {
/**
* Fired when the environment has changed.
*/
readonly onEnvironmentChanged: IReadonlyObservable<void>;
/**
* Fired when the environment configuration has changed.
*/
readonly onEnvironmentConfigurationChanged: IReadonlyObservable<void>;
/**
* Fired when an error occurs while loading the environment.
*/
readonly onEnvironmentError: IReadonlyObservable<unknown>;
/**
* Fired when the shadows configuration changes.
*/
readonly onShadowsConfigurationChanged: IReadonlyObservable<void>;
/**
* Fired when the post processing state changes.
*/
readonly onPostProcessingChanged: IReadonlyObservable<void>;
/**
* Fired when a model is loaded into the viewer (or unloaded from the viewer).
*/
readonly onModelChanged: IReadonlyObservable<Nullable<string | File | ArrayBufferView>>;
/**
* Fired when an error occurs while loading a model.
*/
readonly onModelError: IReadonlyObservable<unknown>;
/**
* Fired when progress changes on loading activity.
*/
readonly onLoadingProgressChanged: IReadonlyObservable<void>;
/**
* Fired when the camera auto orbit state changes.
*/
readonly onCameraAutoOrbitChanged: IReadonlyObservable<void>;
/**
* Fired when the selected animation changes.
*/
readonly onSelectedAnimationChanged: IReadonlyObservable<void>;
/**
* Fired when the animation speed changes.
*/
readonly onAnimationSpeedChanged: IReadonlyObservable<void>;
/**
* Fired when the selected animation is playing or paused.
*/
readonly onIsAnimationPlayingChanged: IReadonlyObservable<void>;
/**
* Fired when the current point on the selected animation timeline changes.
*/
readonly onAnimationProgressChanged: IReadonlyObservable<void>;
/**
* Fired when the selected material variant changes.
*/
readonly onSelectedMaterialVariantChanged: IReadonlyObservable<void>;
/**
* Fired when the hot spots object changes to a complete new object instance.
*/
readonly onHotSpotsChanged: IReadonlyObservable<void>;
/**
* Fired when the cameras as hot spots property changes.
*/
readonly onCamerasAsHotSpotsChanged: IReadonlyObservable<void>;
/**
* Fired after each frame is rendered.
*/
readonly onAfterRenderObservable: IReadonlyObservable<void>;
/**
* Fired when the clear color changes.
*/
readonly onClearColorChanged: IReadonlyObservable<void>;
/**
* Gets or sets the clear color (background color) of the viewer.
*/
clearColor: IColor4Like;
/**
* Gets the camera auto-orbit configuration.
*/
get cameraAutoOrbit(): Readonly<CameraAutoOrbit>;
/**
* Sets the camera auto-orbit configuration. Only specified fields are updated.
*/
set cameraAutoOrbit(value: Partial<Readonly<CameraAutoOrbit>>);
/**
* Resets the camera to its default state.
* @param reframe If true, reframes the camera to fit the model. If undefined, automatically determined.
*/
resetCamera(reframe?: boolean): void;
/**
* Updates the camera pose.
* @param pose The new pose of the camera. Any unspecified values are left unchanged.
*/
updateCamera(pose: {
alpha?: number;
beta?: number;
radius?: number;
targetX?: number;
targetY?: number;
targetZ?: number;
}): void;
/**
* Gets the environment configuration.
*/
get environmentConfig(): Readonly<EnvironmentParams>;
/**
* Sets the environment configuration. Only specified fields are updated.
*/
set environmentConfig(value: Partial<Readonly<EnvironmentParams>>);
/**
* Loads an environment from the specified URL.
* @param url The URL of the environment to load.
* @param options The options for loading the environment.
* @param abortSignal An optional signal that can be used to abort the load.
*/
loadEnvironment(url: string, options?: LoadEnvironmentOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* Resets the environment to its default state.
* @param options The options to use when resetting the environment.
* @param abortSignal An optional signal that can be used to abort the reset.
*/
resetEnvironment(options?: EnvironmentOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* Gets the post-processing configuration.
*/
get postProcessing(): Readonly<PostProcessing>;
/**
* Sets the post-processing configuration. Only specified fields are updated.
*/
set postProcessing(value: Partial<Readonly<PostProcessing>>);
/**
* Gets the current shadow configuration.
*/
readonly shadowConfig: Readonly<ShadowParams>;
/**
* Updates the shadow configuration.
* @param value The new shadow configuration.
* @param abortSignal Optional signal that can be used to abort the update.
*/
updateShadows(value: Partial<Readonly<ShadowParams>>, abortSignal?: AbortSignal): Promise<void>;
/**
* Loads a 3D model from the specified source.
* @param source The source of the model to load.
* @param options The options for loading the model.
* @param abortSignal An optional signal that can be used to abort the load.
*/
loadModel(source: string | File | ArrayBufferView, options?: ViewerLoadModelOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* Unloads the current 3D model if one is loaded.
* @param abortSignal An optional signal that can be used to abort the reset.
*/
resetModel(abortSignal?: AbortSignal): Promise<void>;
/**
* The list of animation names for the currently loaded model.
*/
readonly animations: readonly string[];
/**
* Gets or sets the index of the selected animation.
*/
selectedAnimation: number;
/**
* Gets or sets the speed scale at which animations are played.
*/
animationSpeed: number;
/**
* True if an animation is currently playing.
*/
readonly isAnimationPlaying: boolean;
/**
* Gets or sets the current point on the selected animation timeline, normalized between 0 and 1.
*/
animationProgress: number;
/**
* Toggles between playing and pausing the selected animation.
*/
toggleAnimation(): void;
/**
* Plays the selected animation.
*/
playAnimation(): void;
/**
* Pauses the selected animation.
*/
pauseAnimation(): Promise<void>;
/**
* The list of material variant names for the currently loaded model.
*/
readonly materialVariants: readonly string[];
/**
* Gets or sets the selected material variant.
*/
selectedMaterialVariant: Nullable<string>;
/**
* Gets or sets the hot spots configuration.
*/
hotSpots: Record<string, HotSpot>;
/**
* Gets or sets whether cameras embedded in the model should be exposed as hot spots.
*/
camerasAsHotSpots: boolean;
/**
* Queries a named hot spot and returns its screen and world positions.
* @param name The name of the hot spot to query.
* @param result The result object to populate.
* @returns True if the hot spot was found.
*/
queryHotSpot(name: string, result: ViewerHotSpotResult): boolean;
/**
* Updates the camera to focus on a named hotspot.
* @param name The name of the hotspot to focus on.
* @returns True if the hotspot was found and the camera was updated.
*/
focusHotSpot(name: string): boolean;
/**
* True if a model is currently loaded.
*/
readonly isModelLoaded: boolean;
/**
* The current loading progress. False when not loading, true when loading with indeterminate progress, or a number between 0 and 1.
*/
readonly loadingProgress: boolean | number;
/**
* Resets the viewer to its initial state based on the options passed in to the constructor.
* @param flags The flags that specify which parts of the viewer to reset. If no flags are provided, all parts will be reset.
*/
reset(...flags: ResetFlag[]): void;
/**
* Disposes the viewer and releases all resources.
*/
dispose(): void;
}
/**
* Common base for the full Babylon.js {@link Viewer} and the lite Viewer.
*
* Encapsulates the pieces that are identical between both engine backends:
* - The 18 public observables exposed by the viewer surface area
* - In-flight load operation tracking (used to compute aggregate `loadingProgress`)
* - The `_throwIfDisposedOrAborted` helper used at the start of every async operation
* - The disposed flag and observable teardown in `dispose()`
*
* Subclasses are responsible for everything engine-specific (scene/engine creation,
* model + environment loading orchestration, camera, post-processing, shadows, etc.)
* and for declaring `implements IViewer` themselves so the public API contract is
* verified at the leaf class level.
*/
declare abstract class ViewerBase {
/**
* Fired when the environment has changed.
*/
readonly onEnvironmentChanged: Observable<void>;
/**
* Fired when the environment configuration has changed.
*/
readonly onEnvironmentConfigurationChanged: Observable<void>;
/**
* Fired when an error occurs while loading the environment.
*/
readonly onEnvironmentError: Observable<unknown>;
/**
* Fired when the shadows configuration changes.
*/
readonly onShadowsConfigurationChanged: Observable<void>;
/**
* Fired when the post processing state changes.
*/
readonly onPostProcessingChanged: Observable<void>;
/**
* Fired when a model is loaded into the viewer (or unloaded from the viewer).
* @remarks
* The event argument is the source that was loaded, or null if no model is loaded.
*/
readonly onModelChanged: Observable<Nullable<string | File | ArrayBufferView<ArrayBufferLike>>>;
/**
* Fired when an error occurs while loading a model.
*/
readonly onModelError: Observable<unknown>;
/**
* Fired when progress changes on loading activity.
*/
readonly onLoadingProgressChanged: Observable<void>;
/**
* Fired when the camera auto orbit state changes.
*/
readonly onCameraAutoOrbitChanged: Observable<void>;
/**
* Fired when the selected animation changes.
*/
readonly onSelectedAnimationChanged: Observable<void>;
/**
* Fired when the animation speed changes.
*/
readonly onAnimationSpeedChanged: Observable<void>;
/**
* Fired when the selected animation is playing or paused.
*/
readonly onIsAnimationPlayingChanged: Observable<void>;
/**
* Fired when the current point on the selected animation timeline changes.
*/
readonly onAnimationProgressChanged: Observable<void>;
/**
* Fired when the selected material variant changes.
*/
readonly onSelectedMaterialVariantChanged: Observable<void>;
/**
* Fired when the hot spots object changes to a complete new object instance.
*/
readonly onHotSpotsChanged: Observable<void>;
/**
* Fired when the cameras as hot spots property changes.
*/
readonly onCamerasAsHotSpotsChanged: Observable<void>;
/**
* Fired after each frame is rendered.
*/
readonly onAfterRenderObservable: Observable<void>;
/**
* Fired when the clear color changes.
*/
readonly onClearColorChanged: Observable<void>;
/**
* @internal Tracks in-flight load operations (model + environment + shadows) so that
* `loadingProgress` can return either an aggregate progress number, `true` (indeterminate),
* or `false` (no operations in flight).
*/
protected readonly _loadOperations: Set<Readonly<{
progress: Nullable<number>;
}>>;
/** @internal True after `dispose()` has been called. */
protected _isDisposed: boolean;
/**
* @internal Backend-agnostic viewer options stored at construction time. Subclasses declare this
* with their own (narrower) options type that extends {@link ViewerBaseOptions} and assign it from
* their own constructor (typically via a parameter property).
*/
protected abstract readonly _options?: Readonly<ViewerBaseOptions>;
/**
* The current loading progress. False when no load is in flight, true when at least one
* load is in flight with indeterminate progress, or a number between 0 and 1 representing
* the average of all in-flight loads' progress.
*/
get loadingProgress(): boolean | number;
/**
* Begin tracking a new load operation. Subclasses call this at the start of an async load
* and dispose the returned handle when it completes (or fails). The handle exposes a
* `progress` setter that, when assigned, fires `onLoadingProgressChanged`.
* @returns A handle that can be disposed when the operation completes; the `progress` setter
* updates the aggregate `loadingProgress` as the operation runs.
*/
protected _beginLoadOperation(): IDisposable & {
progress: Nullable<number>;
};
/**
* @internal Throws if the viewer has been disposed or any of the supplied abort signals
* are aborted. Used at the start of every async operation to bail out early.
* @param abortSignals Optional abort signals to check.
*/
protected _throwIfDisposedOrAborted(...abortSignals: (Nullable<AbortSignal> | undefined)[]): void;
/** Lock guarding lighting-side environment loads. */
private readonly _loadEnvironmentLightingLock;
/** Abort controller for the currently in-flight lighting-side load (null when none). */
private _loadEnvironmentLightingAbortController;
/** Lock guarding skybox-side environment loads. */
private readonly _loadEnvironmentSkyboxLock;
/** Abort controller for the currently in-flight skybox-side load (null when none). */
private _loadEnvironmentSkyboxAbortController;
/**
* @internal The abort signal of the currently in-flight lighting-side load, or `undefined` if
* none. Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async
* work (shadows, post-processing, etc.) when the user starts a new lighting load.
*/
protected get _loadEnvironmentLightingAbortSignal(): AbortSignal | undefined;
/**
* @internal The abort signal of the currently in-flight skybox-side load, or `undefined` if
* none. Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async
* work (shadows, post-processing, etc.) when the user starts a new skybox load.
*/
protected get _loadEnvironmentSkyboxAbortSignal(): AbortSignal | undefined;
/**
* Loads an environment from the specified URL. The lighting and skybox sides have
* independent locks and abort controllers so concurrent requests for one side don't
* cancel an in-flight load for the other.
* @param url The URL of the environment to load.
* @param options Selects which sides to update (defaults to both) and forwards engine-specific extras.
* @param abortSignal Optional signal that can be used to abort the load externally.
* @returns A promise that resolves when the environment has finished loading.
*/
loadEnvironment(url: string, options?: LoadEnvironmentOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* Removes the loaded environment. By default removes both lighting and skybox; pass `options`
* to remove only one side. Subclasses (notably the full Viewer) may override to add backend-specific
* fallback behavior such as substituting a default environment for lighting when the scene contains
* PBR materials.
* @param options Selects which sides to remove (defaults to both).
* @param abortSignal Optional signal that can be used to abort the operation externally.
* @returns A promise that resolves when the environment has finished resetting.
*/
resetEnvironment(options?: EnvironmentOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* @internal Internal helper exposing the dual-lock orchestration with a nullable URL.
* Used by the public `loadEnvironment` (with a string URL) and by subclass `resetEnvironment`
* implementations (which pass `undefined` to clear or `"auto"` to load defaults).
*
* Subclasses should NOT override this — override the abstract `_loadEnvironmentImpl` instead.
*/
protected _updateEnvironment(url: Nullable<string | undefined>, options?: LoadEnvironmentOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* @internal Engine-specific environment loading. Subclasses implement this with their actual
* texture loading / scene mutation logic. The base class handles only the surrounding lock +
* abort-prev orchestration and the composite abort signal; everything else (`onEnvironmentChanged` /
* `onEnvironmentError` notifications, snapshot-helper bracketing, etc.) is the impl's responsibility.
*
* Implementations should:
* - Throw on failure. They should fire `onEnvironmentError` themselves before throwing if they
* want external observers to be notified.
* - Fire `onEnvironmentChanged` on success.
* - Periodically re-check abort by calling `throwIfAborted(abortSignal, compositeAbortSignal)`
* at safe points within the load (e.g. after long-running awaits).
*
* @param url Trimmed URL string, `undefined` (caller asked to clear), or `null`.
* @param options Resolved options — `lighting` and `skybox` are guaranteed booleans indicating
* which sides the caller is updating; engine-specific extras (e.g. `extension`) are forwarded as-is.
* @param abortSignal The caller's external abort signal (or `undefined`).
* @param compositeAbortSignal Signal that fires when ALL relevant internal load operations have aborted.
*/
protected abstract _loadEnvironmentImpl(url: Nullable<string | undefined>, options: ResolvedLoadEnvironmentOptions, abortSignal: AbortSignal | undefined, compositeAbortSignal: AbortSignal): Promise<void>;
/** @internal Current environment intensity. Initialized from options in subclass constructors. */
protected _environmentIntensity: number;
/** @internal Current environment skybox blur. Initialized from options in subclass constructors. */
protected _environmentBlur: number;
/** @internal Current environment rotation in radians. Initialized from options in subclass constructors. */
protected _environmentRotation: number;
get environmentConfig(): Readonly<EnvironmentParams>;
set environmentConfig(value: Partial<Readonly<EnvironmentParams>>);
/**
* @internal Push the current `_environmentIntensity` value into the engine's environment state.
* Called by the public `environmentConfig` setter only when the value changes.
*/
protected abstract _applyEnvironmentIntensity(): void;
/**
* @internal Push the current `_environmentBlur` value into the engine's environment state.
* Called by the public `environmentConfig` setter only when the value changes.
*/
protected abstract _applyEnvironmentBlur(): void;
/**
* @internal Push the current `_environmentRotation` value into the engine's environment state.
* Called by the public `environmentConfig` setter only when the value changes.
*/
protected abstract _applyEnvironmentRotation(): void;
/** @internal Initialized from options in subclass constructors. */
protected _autoOrbitEnabled: boolean;
/** @internal Initialized from options in subclass constructors. */
protected _autoOrbitSpeed: number;
/** @internal Initialized from options in subclass constructors. */
protected _autoOrbitDelay: number;
get cameraAutoOrbit(): Readonly<CameraAutoOrbit>;
set cameraAutoOrbit(value: Partial<Readonly<CameraAutoOrbit>>);
/**
* @internal Push the current `_autoOrbitEnabled` value into engine state.
* Called by the public `cameraAutoOrbit` setter only when the value changes.
*/
protected abstract _applyCameraAutoOrbitEnabled(): void;
/**
* @internal Push the current `_autoOrbitSpeed` value into engine state.
* Called by the public `cameraAutoOrbit` setter only when the value changes.
*/
protected abstract _applyCameraAutoOrbitSpeed(): void;
/**
* @internal Push the current `_autoOrbitDelay` value into engine state.
* Called by the public `cameraAutoOrbit` setter only when the value changes.
*/
protected abstract _applyCameraAutoOrbitDelay(): void;
/**
* @internal The current scene clear color, stored as a stable mutable record so consumers
* holding a reference returned by the `clearColor` getter see updates from the setter and from
* `reset("environment")`. The setter mutates this object in-place rather than replacing it.
*/
protected readonly _clearColor: IColor4Like;
/**
* The viewer clear color (e.g. background).
*/
get clearColor(): Readonly<IColor4Like>;
set clearColor(value: Readonly<IColor4Like>);
/**
* @internal Push the current `_clearColor` value into the engine's scene state. Called by the
* public `clearColor` setter; subclasses may also call this directly during construction to sync
* engine state to the initial field values.
*/
protected abstract _applyClearColor(): void;
/** @internal Pure state — no engine state. Subclasses initialize via the public `hotSpots` setter in their constructor body. */
private _hotSpots;
/**
* The set of defined hotspots.
*/
get hotSpots(): Record<string, HotSpot>;
set hotSpots(value: Record<string, HotSpot>);
/** Lock guarding model loads (and resets). */
private readonly _loadModelLock;
/** Abort controller for the currently in-flight model load (null when none). */
private _loadModelAbortController;
/**
* @internal The abort signal of the currently in-flight model load, or `undefined` if none.
* Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async work
* (shadows, environment fallback, etc.) when the user starts a new model load.
*/
protected get _loadModelAbortSignal(): AbortSignal | undefined;
/**
* Loads a 3D model from the specified source.
* @param source The source of the model to load.
* @param options Engine-specific options for loading the model.
* @param abortSignal Optional signal that can be used to abort the load externally.
* @returns A promise that resolves when the model has finished loading.
*/
loadModel(source: string | File | ArrayBufferView, options?: ViewerLoadModelOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* Unloads the current 3D model if one is loaded.
* @param abortSignal Optional signal that can be used to abort the reset.
* @returns A promise that resolves when the current model has been unloaded.
*/
resetModel(abortSignal?: AbortSignal): Promise<void>;
/**
* @internal Internal helper exposing the model load orchestration with `source: undefined` meaning
* "unload the current model". Subclasses should NOT override this — override `_loadModelImpl` instead.
*/
protected _updateModel(source: string | File | ArrayBufferView | undefined, options?: ViewerLoadModelOptions, abortSignal?: AbortSignal): Promise<void>;
/**
* @internal Engine-specific model loading. Subclasses implement this with their actual model
* loading logic. The base class handles only the surrounding lock + abort-prev orchestration;
* everything else (load-operation progress tracking, `onModelChanged` / `onModelError`
* notifications, snapshot-helper bracketing) is the impl's responsibility.
*
* Implementations should:
* - Throw on failure. They should fire `onModelError` themselves before throwing if they want
* external observers to be notified.
* - Fire `onModelChanged` on success.
* - Manage their own `_beginLoadOperation` / dispose pair if they want to contribute to
* `loadingProgress`.
* - Periodically re-check abort by calling `throwIfAborted(abortSignal, internalAbortSignal)`
* at safe points within the load (e.g. after long-running awaits).
* - Treat `source === undefined` as "unload the current model" — this is how `resetModel` flows
* through. They should still fire `onModelChanged(null)` so consumers see the unload.
*
* @param source Source URL/File/ArrayBufferView, or `undefined` to unload the current model.
* @param options Caller's options (or undefined). May contain engine-specific extras.
* @param abortSignal The caller's external abort signal (or `undefined`).
* @param internalAbortSignal Signal that fires when a NEWER model load supersedes this one.
*/
protected abstract _loadModelImpl(source: string | File | ArrayBufferView | undefined, options: ViewerLoadModelOptions | undefined, abortSignal: AbortSignal | undefined, internalAbortSignal: AbortSignal): Promise<void>;
/**
* @internal Optional post-lock hook invoked AFTER the model load lock is released, allowing
* subclasses to do follow-up work that needs other locks (e.g. environment fallback). The base
* skips this hook if the load was superseded by a newer one before we got here.
*
* Implementations that await additional work should re-check `internalAbortSignal.aborted`
* after each await to avoid acting on stale state (a newer load may have started during the
* await window).
*
* Default: no-op.
*/
protected _afterLoadModel(source: string | File | ArrayBufferView | undefined, options: ViewerLoadModelOptions | undefined, abortSignal: AbortSignal | undefined, internalAbortSignal: AbortSignal): Promise<void>;
/** Lock guarding shadow updates. */
private readonly _updateShadowsLock;
/** Abort controller for the currently in-flight shadow update (null when none). */
private _shadowsAbortController;
/**
* @internal The abort signal of the currently in-flight shadow update, or `undefined` if none.
* Subclasses can use this in `_throwIfDisposedOrAborted` to bail out of dependent async work
* when the user starts a new shadow update.
*/
protected get _shadowsAbortSignal(): AbortSignal | undefined;
/**
* @internal The currently committed shadow quality. Subclasses initialize this from their
* options in their constructor and read it in their `_loadModelImpl` etc. The base class
* commits a new value here only after `_updateShadowsImpl` succeeds, so failed/aborted
* shadow updates don't leave this field out of sync with engine state.
*/
protected _shadowQuality: ShadowQuality;
/** @internal */
abstract get selectedMaterialVariant(): Nullable<string>;
/** @internal */
abstract set selectedMaterialVariant(value: Nullable<string>);
/**
* Gets the current shadow configuration.
*/
get shadowConfig(): Readonly<ShadowParams>;
/**
* Updates the shadow configuration. Skips work if the requested value matches the currently
* committed one. Subclasses can override this to validate the requested quality (e.g. throw on
* unsupported combinations) before delegating to `super.updateShadows(value, abortSignal)`.
* @param value The new shadow configuration.
* @param abortSignal Optional signal that can be used to abort the update externally.
* @returns A promise that resolves when the shadow update completes.
*/
updateShadows(value: Partial<Readonly<ShadowParams>>, abortSignal?: AbortSignal): Promise<void>;
/**
* Runs the engine-specific shadow update at the given quality under the shared lock, with
* abort-prev semantics. Subclasses should call this (rather than `_updateShadowsImpl` directly)
* when they need to re-run the shadow setup (e.g. after a model change or environment change).
* The public `updateShadows` also routes through this helper.
* @param quality The shadow quality to apply. Defaults to the currently committed quality
* (`this._shadowQuality`), which is the right choice for re-running shadow setup without
* changing the committed quality. The public `updateShadows` passes a resolved new quality.
* @param abortSignal Optional external abort signal.
* @returns A promise that resolves when the shadow update completes.
*/
protected _updateShadows(quality?: ShadowQuality, abortSignal?: AbortSignal): Promise<void>;
/**
* @internal Engine-specific shadow setup. Subclasses implement this with their actual shadow
* generation logic. The base class handles all surrounding orchestration: lock acquisition,
* abort-prev semantics, quality resolution, and the success-only commit of `_shadowQuality`.
*
* Implementations should:
* - Throw on failure; the base class propagates the error to the caller without committing the new quality.
* - Use `quality` (not `this._shadowQuality`, which still holds the pre-update value) to drive the setup.
* - Periodically re-check abort by calling `throwIfAborted(abortSignal, internalAbortSignal)` at safe points.
*/
protected abstract _updateShadowsImpl(quality: ShadowQuality, abortSignal: AbortSignal | undefined, internalAbortSignal: AbortSignal): Promise<void>;
/**
* Disposes the viewer and releases shared resources (observables, disposed flag).
* Subclasses MUST override this method to dispose their own engine-specific state
* (engine, scene, abort controllers, models, etc.) and call `super.dispose()` last
* so observable consumers see the engine-specific notifications before observables clear.
*
* Subclasses should also early-return if `_isDisposed` is already true.
*/
dispose(): void;
/**
* Resets the viewer to its initial state based on the options passed in to the constructor.
* @param flags The flags that specify which parts of the viewer to reset. If no flags are provided, all parts will be reset.
* - "source": Reset the loaded model.
* - "environment": Reset environment related state.
* - "shadow": Reset shadow related state.
* - "animation": Reset animation related state.
* - "camera": Reset camera related state.
* - "post-processing": Reset post-processing related state.
* - "material-variant": Reset material variant related state.
*/
reset(...flags: ResetFlag[]): void;
/**
* @internal
* Orchestrates the reset operation in canonical flag order. The {@link interpolate} parameter is
* forwarded to per-flag hooks (currently only `_resetCamera`) so internal callers can reset
* without camera animation.
*/
protected _reset(interpolate: boolean, ...flags: ResetFlag[]): void;
/**
* @internal Resets the loaded model to the source specified at construction (or no model if no source was specified).
*/
protected _resetModel(): void;
/** @internal */
protected abstract _resetEnvironment(): void;
/**
* @internal Resets the shadow configuration to the value specified at construction.
*/
protected _resetShadows(): void;
/** @internal */
protected abstract _resetAnimation(): void;
/**
* @internal
* @param interpolate If true, animate camera transitions when supported. Subclasses without bounds-based
* reframing may ignore this parameter.
*/
protected abstract _resetCamera(interpolate: boolean): void;
/** @internal */
protected abstract _resetPostProcessing(): void;
/**
* @internal Resets the selected material variant to the value specified at construction (or null if not specified).
*/
protected _resetMaterialVariant(): void;
}
/**
* The options for the Lite Viewer.
*/
type ViewerOptions = ViewerBaseOptions;
/**
* Options for {@link Viewer.loadModel} on the Lite Viewer.
*/
type LoadModelOptions = ViewerLoadModelOptions;
/**
* Options for creating a Lite Viewer bound to a canvas.
*/
type CanvasViewerOptions = ViewerBaseOptions;
/**
* The default options for the Lite Viewer.
*/
declare const DefaultViewerOptions: {
readonly clearColor: [0, 0, 0, 0];
readonly autoSuspendRendering: true;
readonly environmentConfig: {
readonly intensity: 1;
readonly blur: 0.3;
readonly rotation: 0;
};
readonly environmentLighting: "auto";
readonly environmentSkybox: "none";
readonly cameraAutoOrbit: {
readonly enabled: false;
readonly delay: 2000;
readonly speed: 0.05;
};
readonly animationAutoPlay: false;
readonly animationSpeed: 1;
readonly shadowConfig: {
readonly quality: "none";
};
readonly postProcessing: {
readonly toneMapping: "neutral";
readonly contrast: 1;
readonly exposure: 1;
readonly ssao: "auto";
};
readonly useRightHandedSystem: false;
readonly useOpenPBR: false;
};
/**
* A lightweight implementation of the {@link IViewer} interface built on the Babylon Lite API.
*
* @remarks
* Babylon Lite is a WebGPU-only engine that provides a subset of the full Babylon.js feature set.
* Features that are not available in Lite (SSAO, "high" shadow quality, hot spots, File/ArrayBufferView model sources)
* will log warnings and fall back gracefully.
*/
declare class Viewer extends ViewerBase implements IViewer {
private readonly _engine;
protected readonly _options?: ViewerOptions | undefined;
private readonly _scene;
private readonly _camera;
private _detachControl;
private _renderLoopRunning;
private _autoOrbitIdleTime;
private _lastPointerTime;
/** The currently-loaded lighting URL ("auto" resolves to the embedded default). null = no lighting loaded. */
private _currentLightingUrl;
/** The currently-loaded skybox URL ("auto" resolves to the embedded default). null = no skybox loaded. */
private _currentSkyboxUrl;
private _toneMapping;
private _contrast;
private _exposure;
private _ssaoOption;
/** Serializes the async PBR-pipeline rebuilds triggered by image-processing updates
* (`setSceneImageProcessing`) and environment relights (`rebuildScenePbrPipelines`), so overlapping
* changes can't run concurrent rebuilds (which race on the scene's renderable list and leak). */
private readonly _pbrRebuildLock;
private _shadowGenerator;
private _shadowLight;
private _shadowGround;
private _container;
/** GPU picker for double-click focus, created lazily on first double-click. Disposed with the viewer. */
private _picker;
/** The source that was passed to the most recent {@link loadModel} call, for notifications. */
private _modelSource;
/**
* True once the first model load has built its Lite material group (via `registerScene`). Because
* `_scene` is created once and never recreated, and glTF models all share Lite's singleton PBR group
* builder, later model loads reuse that already-built group: their meshes are enqueued into the
* per-frame material-swap queue and the running render loop materializes them, so those loads must
* NOT re-register the scene (re-registration clears the swap queue and would drop the model). See the
* (re-)registration decision in {@link _loadModelImpl}.
*/
private _modelMaterialGroupBuilt;
/** Cached animation-aware model bounds for the current model. Reset on unload. See {@link _computeModelBounds}. */
private _cachedModelBounds;
private _selectedAnimation;
private _animationSpeed;
private _wasPlaying;
private _lastProgress;
private _selectedMaterialVariant;
private _camerasAsHotSpots;
/**
* Aborts the in-flight camera interpolation (from {@link focusHotSpot}) when a new one starts or
* the viewer is disposed. Null when no interpolation is running.
*/
private _cameraInterpolationAbort;
private _defaultAlpha;
private _defaultBeta;
private _defaultRadius;
private _defaultTarget;
/**
* Creates a new Viewer instance.
* @param _engine The Babylon Lite engine context.
* @param _options Optional viewer configuration.
*/
constructor(_engine: EngineContext, _options?: ViewerOptions | undefined);
/** @internal */
protected _applyClearColor(): void;
/** @internal Lite stores auto-orbit state on the base class fields and consults them in its idle loop. No engine push needed. */
protected _applyCameraAutoOrbitEnabled(): void;
/** @internal Lite stores auto-orbit state on the base class fields. */
protected _applyCameraAutoOrbitSpeed(): void;
/** @internal Lite stores auto-orbit state on the base class fields. */
protected _applyCameraAutoOrbitDelay(): void;
resetCamera(reframe?: boolean): void;
/**
* Shared implementation of camera reset. Resolves the reframe default (matching the full Viewer:
* reframe to model bounds when the selected animation differs from the default, otherwise return to
* the explicit default pose) and moves the camera there, optionally animating the transition.
* @param reframe Whether to reframe to model bounds; when undefined, decided from animation state.
* @param interpolate Whether to animate the camera to the reset pose.
*/
private _resetCameraCore;
updateCamera(pose: {
alpha?: number;
beta?: number;
radius?: number;
targetX?: number;
targetY?: number;
targetZ?: number;
}): void;
/**
* Moves the camera to a goal pose, either by animating (via {@link interpolateArcRotateCamera}) or by
* snapping directly. Either way, any in-flight interpolation is first canceled so it can't fight the
* new pose. Omitted or NaN goal fields keep the camera's current value for that channel.
* @param goal The destination camera pose.
* @param interpolate Whether to animate the transition.
*/
private _moveCameraTo;
/**
* Frames the camera to the loaded model's bounds, matching the full Viewer's framing math. Near/far
* planes and zoom limits are applied immediately; the orbit pose is moved (snapped or animated) via
* {@link _moveCameraTo}.
*
* When `applyDefaultPoseOverrides` is true, the bounds-derived orbit pose is overridden per-channel by
* any explicit `cameraOrbit`/`cameraTarget` options — mirroring the full Viewer's
* `_resetCamera` -> `_reframeCameraFromBounds`. With no such options this equals the pure bounds
* framing used on model load, so a reset returns to exactly the load-time framing.
* @param interpolate Whether to animate the camera to the framing pose.
* @param applyDefaultPoseOverrides Whether to override the bounds pose with explicit camera options.
* @returns True if the model had bounds and the camera was framed; false if there is no model to frame.
*/
private _frameCameraToModel;
/**
* Compute the aggregate world-space bounding box of the loaded model, accounting for
* animation.
*
* Delegates to Lite's {@link computeMaxExtents}, which steps through the currently-selected
* animation group and unions every sampled pose. This captures the full swept volume of
* node (TRS), skeletal, and morph-target animation — so skinned models like the
* acrobaticPlane glTF frame correctly instead of reporting their (much smaller) bind-pose
* AABB. Meshes are gathered with {@link getContainerMeshes} so Viewer-added meshes (e.g. the
* shadow-receiver disc) are excluded.
*
* The result is cached for the lifetime of the loaded model (reset in
* `_unloadCurrentModel`) so the two consumers — `_frameCameraToModel` (camera target +
* radius + near/far planes) and `_setupShadows` (light positioning, ground placement,
* frustum sizing) — share a single animation sweep rather than stepping it twice.
*
* @returns aggregate `min`, `max`, `center`, and bounding-sphere `radius`
* (= half the diagonal), or `null` if the model has no bounds info.
*/
private _computeModelBounds;
/** @inte