Warning, /firebird/packages/firebird-ng/api/src/lib/three-extension.ts is written in an unsupported language. File is not indexed.
0001 /**
0002 * The ThreeExtension contract: the narrow lifecycle interface through which
0003 * extensions hook the rendering machinery (scene lifecycle, frame loop).
0004 *
0005 * Boundary rule: if code turns *data* (event groups, fields, geometry) into
0006 * visuals, it is a painter — register it with `withPainter()`. If it hooks
0007 * the *machinery* — scene lifecycle, frame loop, input, services — it is a
0008 * ThreeExtension.
0009 */
0010
0011 import type { Type } from '@angular/core';
0012 import type * as THREE from 'three';
0013 import type { WebGPURenderer, ClippingGroup } from 'three/webgpu';
0014 import type { Event as FbEvent } from '@dexvis/firebird-core';
0015 import type { GeometrySlice, RenderView, RenderViewOptions } from './views';
0016
0017 /**
0018 * Everything an extension needs to work with the scene. Handed to
0019 * `onSceneInit` strictly AFTER the async renderer initialization resolved —
0020 * extension authors never hand-roll "defer until ready" logic.
0021 *
0022 * The context hands out real three.js objects, never wrappers. An author who
0023 * wants three.js verbatim builds a `Raycaster` from `camera` + pointer math.
0024 */
0025 export interface SceneContext {
0026 scene: THREE.Scene;
0027 /** Detector geometry container. Clipping applies here — never to sceneEvent. */
0028 sceneGeometry: ClippingGroup;
0029 /** Event data container. Never clipped. */
0030 sceneEvent: THREE.Group;
0031 /** Lights, axes, gizmos, measurement markers. */
0032 sceneHelpers: THREE.Group;
0033 /** The active camera (perspective or orthographic). */
0034 camera: THREE.PerspectiveCamera | THREE.OrthographicCamera;
0035 renderer: WebGPURenderer;
0036 /**
0037 * The shared canvas every view draws into. Do not attach pointer input
0038 * here: on multi-view pages the view containers sit above the canvas and
0039 * receive the events, so the canvas gets none. Listen on a view's
0040 * `container` instead (`mainView.container` for the display), and move
0041 * the listeners when the view changes containers on page switches
0042 * (`ViewOverlay.onViewContainerChange` reports it).
0043 */
0044 canvas: HTMLCanvasElement;
0045 /**
0046 * The render views sharing this scene. Live list: multi-view pages add and
0047 * remove views at runtime. `views[0]` is always the main view.
0048 */
0049 readonly views: readonly RenderView[];
0050 /**
0051 * The main view — the display page's camera/controls. Per-view overlays
0052 * (like the navigation cube) attach here via `mainView.addOverlay()`.
0053 */
0054 readonly mainView: RenderView;
0055 /** Adds a view of the shared scene (see RenderViewOptions for the contract). */
0056 addView(options: RenderViewOptions): RenderView;
0057 /** Removes a view added with addView. The main view cannot be removed. */
0058 removeView(view: RenderView): void;
0059 /**
0060 * The independently clipped geometry copy for projection views, or null
0061 * when no view uses one. Create it with `createGeometrySlice()`, pass it
0062 * with a `clipPlane` in the `addView` options to give a view its own cut,
0063 * and call `rebuildGeometrySlice()` after editing the loaded geometry
0064 * (the display rebuilds it after every geometry load).
0065 *
0066 * @experimental See `GeometrySlice`.
0067 */
0068 readonly geometrySlice: GeometrySlice | null;
0069 /** @experimental See `geometrySlice`. */
0070 createGeometrySlice(): GeometrySlice;
0071 /** @experimental See `geometrySlice`. */
0072 rebuildGeometrySlice(): void;
0073 /** @experimental See `geometrySlice`. */
0074 removeGeometrySlice(): void;
0075 /**
0076 * Adds an object to the event data: under `sceneEvent`, on
0077 * EVENT_DATA_LAYER with all its descendants (never clipped, drawn in
0078 * every view, over geometry in tracks-on-top views), and schedules a
0079 * frame. Remove it with `object.removeFromParent()` and `invalidate()`.
0080 * Objects added to `sceneEvent` directly keep layer 0 and disappear from
0081 * the second pass of tracks-on-top views.
0082 */
0083 addEventObject(object: THREE.Object3D): void;
0084 /**
0085 * Signal that you changed renderable state — the next animation frame
0086 * renders. The render loop is on-demand by default (config
0087 * `rendering.mode`): a mutation without an invalidate() shows up only
0088 * when something else triggers a render. Call it after mutating anything
0089 * the next frame must show. Cheap and idempotent.
0090 */
0091 invalidate(): void;
0092 }
0093
0094 /**
0095 * Per-frame context passed to `onFrame` inside the render loop.
0096 * Keep `onFrame` cheap: no allocation, no per-frame polling of app state.
0097 *
0098 * Rule: `onFrame` is for animation only. State changes must travel through
0099 * signals/effects, never by polling inside the frame loop. Under the
0100 * default on-demand scheduling, `onFrame` runs only on frames that render;
0101 * an animation sustains itself by calling `invalidate()` from its update
0102 * (start it with one seed `invalidate()`), and the chain ends by itself
0103 * when the animation stops updating.
0104 */
0105 export interface FrameContext {
0106 /** Milliseconds since the previous RENDERED frame. */
0107 deltaTime: number;
0108 renderer: WebGPURenderer;
0109 camera: THREE.PerspectiveCamera | THREE.OrthographicCamera;
0110 /** Same contract as SceneContext.invalidate. */
0111 invalidate(): void;
0112 }
0113
0114 /**
0115 * Lifecycle interface for rendering-machinery extensions. All methods are
0116 * optional; implement only what you need. Registered with
0117 * `withThreeExtension(MyExtension)` — the class is instantiated through DI,
0118 * so `inject()` works in its constructor.
0119 */
0120 export interface ThreeExtension {
0121 /** Called once, after the async ThreeService.init completed. */
0122 onSceneInit?(ctx: SceneContext): void;
0123 /**
0124 * Called before rendering, on every frame that renders. Keep it cheap. If
0125 * it throws, the error is logged and this extension's onFrame is not
0126 * called again; rendering and the other extensions continue.
0127 */
0128 onFrame?(ctx: FrameContext): void;
0129 /** Called when a new event (entry) was loaded and painted. */
0130 onEventLoaded?(event: FbEvent): void;
0131 /**
0132 * Called once at application teardown, when the root injector destroys
0133 * the rendering service. The scene and the extension outlive display
0134 * pages: leaving /display does not call this, and returning does not call
0135 * onSceneInit again. Remove listeners and objects here.
0136 */
0137 onDispose?(): void;
0138 }
0139
0140 /**
0141 * Loader shape for heavy extensions that must not sit in the initial bundle:
0142 * `withLazyThreeExtension(() => import('./vr').then(m => m.VrExtension))`.
0143 * The bundler splits each lazy extension into its own chunk; the extension
0144 * class itself is written identically in both cases.
0145 */
0146 export type LazyThreeExtensionLoader = () => Promise<Type<ThreeExtension>>;