Skip to content

EnvironmentService

Manages scene lighting, HDRI environment maps, soft contact shadows, and viewer backgrounds. On init it creates the built-in "outdoor" preset, applies it as the default environment, sets up ambient lighting, and applies viewer.environment.background when that config is present. DOM-backed backgrounds are rendered behind the WebGL canvas for transparent, solid, gradient, and image backgrounds; THREE.Texture backgrounds are applied directly to the Three.js scene.

Implementation Layout

The environment service is split across src/services/environment:

FileResponsibility
EnvironmentService.tsPublic service API, preset/HDRI orchestration, lighting, event emission, and lifecycle coordination.
ShadowSystem.tsContact-shadow render pipeline and shadow GPU resources.
ShadowAutoUpdateController.tsEvent listeners, debounce/throttle state, and frame-callback scheduling for automatic shadow refreshes.
EnvironmentBackgroundManager.tsDOM background layer, background normalization, image loading, canvas export drawing.
types.tsEnvironmentPreset, LightingConfig, HDRIConfig, and ShadowConfig.

Access

Registered under the name "environment". No service dependencies.

EnvironmentService is not exported from the SDK entry point. Reach it by its registered name and use the class only as a type.

ts
import type { EnvironmentService } from "@optellix/xviewr-sdk";

await viewer.ready();
const env = viewer.getService<EnvironmentService>("environment");

env?.setBackground({
  type: "gradient",
  shape: "radial",
  startColor: "#1a1a2e",
  endColor: "#16213e",
});

Public API

Lifecycle

  • init(context: ServiceContext): Promise<void>
  • destroy(): Promise<void>
  • getStatus(): ServiceStatus

Environment Presets

  • setEnvironment(preset: string): Promise<void> - loads the named preset's HDRI when one is configured, updates scene.environment, applies the preset intensity through HDRIConfig, reapplies the active background mode, and emits environment:changed. Built-in preset: "outdoor". Throws if the preset is not registered.
  • registerPreset(name: string, preset: EnvironmentPreset): void - adds a custom preset.
  • getPresets(): string[] - returns all registered preset names.
  • getEnvironmentMap(): THREE.Texture | null - returns the current PMREM-generated environment texture.

HDRI

  • setHDRI(url: string): Promise<void> - loads a .hdr file by URL, PMREM-processes it, replaces the current environment map, reapplies the active background mode, and emits environment:hdri-changed.
  • setHDRIConfig(config: Partial<HDRIConfig>): void - patches the stored HDRI config and applies intensity, rotation, background blurriness, and background intensity to the scene.
  • getHDRIConfig(): HDRIConfig
  • setShowEnvironmentBackground(show: boolean): void - when true, renders the HDRI as the scene background. When false, the configured viewer background takes over.
  • getShowEnvironmentBackground(): boolean

Lighting

  • setLighting(config: LightingConfig): void - updates ambient and directional lights. Creates lights if they do not yet exist.

Shadows

  • setShadows(enabled: boolean, config?: ShadowConfig): void - enables or disables contact shadows, configures auto-update behavior, creates the shadow system on first enable, updates renderer.shadowMap.enabled, and emits environment:shadows-changed.
  • requestShadowUpdate(reason?: string): void - requests a debounced contact-shadow refresh. This is useful for integrations that know a visible object changed but do not need an immediate render-target update.
  • updateShadows(): void - immediately re-renders the contact-shadow map.

The shadow system uses a bottom-up depth projection from below the model, then applies separable horizontal and vertical Gaussian blur passes into ping-pong render targets. Existing config names remain supported:

  • intensity maps to contact-shadow opacity.
  • blurRadius maps to blur amount.
  • shadowMapSize maps to render-target resolution.

Auto-update is enabled by default. The service listens only to existing high-signal events that imply visible scene bounds may have changed:

  • model finalization: model:processing-complete, model:unloaded
  • transforms: transformcontrols:transform-applied, transformcontrols:reset
  • visibility: tree:node-visibility-changed
  • explosion transforms: explosion:applied, explosion:reset, explosion:depth-applied
  • animation lifecycle events: starts/resumes mark animation as active; stops/pauses/completions trigger one final update

During active animations, shadows update on a throttled interval controlled by animationUpdateMs. Calling updateShadows() clears any pending debounced update before rendering immediately.

Background

  • setBackground(background: ViewerBackgroundInput): void - stores the viewer background, applies it when HDRI background mode is off, and emits environment:background-changed. Accepts a config object, THREE.Color, THREE.Texture, or null (transparent).
  • getBackground(): ViewerBackgroundConfig | null - returns the normalized public background config. THREE.Color inputs return a solid-color config; THREE.Texture inputs return null.
  • drawBackgroundToCanvas(context: CanvasRenderingContext2D, width: number, height: number): void - draws the current solid, gradient, or image background into an off-screen canvas context. Transparent and texture backgrounds are skipped. Used by canvas export.

When HDRI background mode is enabled, setBackground() still stores the requested viewer background, but the HDRI remains visible until setShowEnvironmentBackground(false) is called.

Image backgrounds load asynchronously. Non-data: and non-blob: URLs are requested with crossOrigin = "anonymous". If loading or decoding fails, the manager keeps the fallback color when one is configured and emits environment:background-error.

Types

EnvironmentPreset

ts
interface EnvironmentPreset {
  name: string;
  hdriPath: string | null;
  intensity: number;
}

HDRIConfig

ts
interface HDRIConfig {
  intensity: number;
  rotation: number; // degrees
  backgroundBlurriness: number;
  backgroundIntensity: number;
}

LightingConfig

ts
interface LightingConfig {
  ambient?: {
    color: string | number;
    intensity: number;
  };
  directional?: Array<{
    color: string | number;
    intensity: number;
    position: [number, number, number];
    castShadow?: boolean;
    shadowMapSize?: number;
  }>;
}

ShadowConfig

ts
interface ShadowConfig {
  // Backward-compatible names
  intensity?: number;
  blurRadius?: number;
  shadowMapSize?: number;

  // Contact-shadow options
  resolution?: number;
  blur?: number;
  opacity?: number;
  smooth?: boolean;
  color?: string | number | THREE.Color;
  padding?: number;
  groundOffset?: number;
  ambientFill?: number;
  ambientFalloff?: number;
  maxPollAttempts?: number;
  pollDelay?: number;
  autoUpdate?: boolean;
  updateDebounceMs?: number;
  animationUpdateMs?: number;
}
OptionDefaultDescription
intensity / opacity0.6Overall opacity of the rendered shadow plane. intensity is the legacy name.
blurRadius / blur2.5Blur amount for the separable Gaussian blur pass. blurRadius is the legacy name.
shadowMapSize / resolution512Width and height of the shadow render targets. shadowMapSize is the legacy name.
smoothtrueRuns a second lighter blur pass for softer edges.
color"#000000"Shadow and ambient-fill color.
padding1.2Multiplier applied to model X/Z bounds when sizing the shadow receiver.
groundOffset0.01Vertical offset above the model's minimum Y to avoid z-fighting.
ambientFill0Optional radial fill under the contact shadow.
ambientFalloff0.5Controls how quickly the ambient fill fades toward receiver edges.
maxPollAttempts20Number of startup attempts to wait for valid model bounds.
pollDelay500Delay in milliseconds between startup bounds checks.
autoUpdatetrueRegisters event listeners and a frame callback to refresh shadows automatically.
updateDebounceMs75Debounce window for event-driven shadow refreshes.
animationUpdateMs125Throttle interval for shadow refreshes while animation is active.

ViewerBackgroundInput

ts
type ViewerBackgroundInput =
  | { type: "transparent" }
  | { type: "solid"; color: string }
  | {
      type: "gradient";
      shape?: "vertical" | "radial";
      startColor: string;
      endColor: string;
    }
  | {
      type: "image";
      url: string;
      mode?: "fit" | "fill" | "tile";
      fallbackColor?: string;
    }
  | THREE.Color
  | THREE.Texture
  | null;

Events

EventPayload
environment:changed{ preset: string; intensity: number }
environment:shadows-changed{ enabled: boolean }
environment:background-changed{ background: ViewerBackgroundInput }

The typed EventMap currently declares the three events above, but its environment:background-changed payload is narrower than the implementation. The following events are also emitted by the service implementation but are not currently declared in EventMap:

EventPayload
environment:hdri-changed{ url: string }
environment:hdri-config-changed{ config: HDRIConfig }
environment:background-mode-changed{ showEnvironment: boolean }
environment:background-error{ background: ViewerBackgroundConfig | null; error: Error }

Example

ts
const env = viewer.getService<EnvironmentService>("environment");
if (!env) return;

env.setBackground({
  type: "gradient",
  shape: "radial",
  startColor: "#ffffff",
  endColor: "#cccccc",
});

viewer.on("model:loaded", () => {
  env.setShadows(true, {
    intensity: 0.45,
    blurRadius: 4,
    shadowMapSize: 1024,
    padding: 1.2,
    autoUpdate: true,
  });
});

await env.setHDRI("/assets/studio.hdr");
env.setHDRIConfig({ intensity: 1.5, rotation: 45 });
env.setShowEnvironmentBackground(true);

XViewr SDK and xConvert CLI documentation.