Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/packages/firebird-ng/display/src/lib/extensions/viewport-gizmo.extension.ts is written in an unsupported language. File is not indexed.

0001 /**
0002  * Camera navigation cube (viewport gizmo), wired as a builtin ThreeExtension.
0003  *
0004  * The cube shows the camera orientation and IS a camera control: click a face,
0005  * edge or corner to animate there, drag to orbit, roll the view with the
0006  * curved-arrow buttons, or return to the home view. Rendering, axis
0007  * conventions and the WebGPU handling live in `@dexvis/viewport-gizmo`
0008  * (dexvis/viewport-gizmo submodule); this file binds it to Firebird's main
0009  * RenderView as a view overlay: the overlay renders after the view's scene
0010  * render and follows the view's camera, container and resizes. An extension
0011  * that wants its own overlay uses the same seam (`view.addOverlay`).
0012  *
0013  * Loaded through `withLazyThreeExtension` — the gizmo pulls three.js code and
0014  * must stay out of the initial bundle.
0015  */
0016 
0017 import { Injectable, inject } from '@angular/core';
0018 import { ViewportGizmo } from '@dexvis/viewport-gizmo';
0019 import type { OrthographicCamera, PerspectiveCamera } from 'three';
0020 
0021 import type { RenderView, SceneContext, ThreeExtension, ViewOverlay } from '@dexvis/firebird-ng/api';
0022 import { CommandBusService } from '@dexvis/app-features';
0023 
0024 @Injectable()
0025 export class ViewportGizmoExtension implements ThreeExtension {
0026   private commandBus = inject(CommandBusService);
0027 
0028   private gizmo?: ViewportGizmo;
0029   private view?: RenderView;
0030   private overlay?: ViewOverlay;
0031   private lastCamera?: PerspectiveCamera | OrthographicCamera;
0032 
0033   onSceneInit(ctx: SceneContext): void {
0034     const view = ctx.mainView;
0035     this.view = view;
0036 
0037     this.gizmo = new ViewportGizmo(view.camera, ctx.renderer, {
0038       // Overlay positioning is relative to the view container.
0039       container: view.container,
0040       size: 90,
0041       placement: 'top-right',
0042       offset: { top: 10, right: 20 },
0043       background: { color: 0x444444, hover: { color: 0x444444 } },
0044       corners: { color: 0x333333, hover: { color: 0x4bac84 } },
0045       // HENP axis convention: beam along Z, Y up, X toward the accelerator
0046       // center. The front view has Y up and Z pointing right on the screen
0047       // (camera on -X, +X goes into the screen); the top/bottom views keep Z
0048       // pointing right, which puts X toward the top of the screen when viewed
0049       // from above (poleUp).
0050       poleUp: { top: [1, 0, 0], bottom: [-1, 0, 0] },
0051       nx: { label: 'Front' },
0052       x: { label: 'Back' },
0053       z: { label: 'Right' },
0054       nz: { label: 'Left' },
0055       y: { label: 'Top' },
0056       ny: { label: 'Bottom' },
0057     });
0058 
0059     this.gizmo.attachControls(view.controls);
0060     this.lastCamera = view.camera;
0061 
0062     // Home goes through the command bus like any other camera move, so the
0063     // same view is reachable from deep links and batch: camera-preset:home.
0064     this.gizmo.addEventListener('home', () => {
0065       void this.commandBus.dispatch({ type: 'camera-preset', name: 'home', source: 'ui' });
0066     });
0067 
0068     // Render-on-demand chain for cube interactions: clicks and view
0069     // transitions emit 'change' per step (the transition advances inside the
0070     // overlay render), so each step schedules the next frame until the
0071     // animation settles.
0072     this.gizmo.addEventListener('start', () => ctx.invalidate());
0073     this.gizmo.addEventListener('change', () => ctx.invalidate());
0074 
0075     // View overlay: renders after the view's scene render (the cube must
0076     // draw on top) and re-anchors when the view is resized (pane toggles,
0077     // window resizes — the view calls onViewResize from updateViewport).
0078     this.overlay = {
0079       render: () => this.renderGizmo(),
0080       onViewResize: () => this.gizmo?.update(false),
0081       // The cube follows the view across page layouts (single display,
0082       // quad view): its overlay div moves into the view's new container.
0083       onViewContainerChange: (changed) => this.gizmo?.setContainer(changed.container),
0084       dispose: () => this.disposeGizmo(),
0085     };
0086     view.addOverlay(this.overlay);
0087   }
0088 
0089   onDispose(): void {
0090     if (this.view && this.overlay) {
0091       this.view.removeOverlay(this.overlay);
0092     }
0093     this.disposeGizmo();
0094     this.view = undefined;
0095     this.overlay = undefined;
0096   }
0097 
0098   private disposeGizmo(): void {
0099     this.gizmo?.dispose();
0100     this.gizmo = undefined;
0101   }
0102 
0103   private renderGizmo(): void {
0104     const gizmo = this.gizmo;
0105     const view = this.view;
0106     if (!gizmo || !view) return;
0107 
0108     // Follow perspective/orthographic camera switches
0109     const camera = view.camera;
0110     if (camera !== this.lastCamera) {
0111       this.lastCamera = camera;
0112       gizmo.camera = camera;
0113       gizmo.update(false);
0114     }
0115 
0116     gizmo.cameraUpdate();
0117     gizmo.render();
0118   }
0119 }