Warning, /firebird/packages/firebird-ng/display/src/lib/pages/main-display/main-display.component.ts is written in an unsupported language. File is not indexed.
0001 import {
0002 Component,
0003 OnInit,
0004 Input,
0005 ViewChild, OnDestroy, TemplateRef, signal, effect,
0006 ChangeDetectionStrategy, DestroyRef, inject
0007 } from '@angular/core';
0008 import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
0009
0010 import {GeometryService} from '../../services/geometry.service';
0011 import {GameControllerService} from '../../services/game-controller.service';
0012
0013 import {SceneTreeComponent} from '../../components/scene-tree/scene-tree.component';
0014 import {ModelTreeComponent} from '../../components/model-tree/model-tree.component';
0015 import {FirebirdShellComponent} from '../../components/firebird-shell/firebird-shell.component';
0016 import {ToolPanelComponent} from '../../components/tool-panel/tool-panel.component';
0017 import {EventSelectorComponent} from '../../components/event-selector/event-selector.component';
0018 import {GeometryClippingComponent} from '../../components/geometry-clipping/geometry-clipping.component';
0019 import {OpenEventComponent} from '../../components/open-event/open-event.component';
0020
0021 import {MatIcon} from '@angular/material/icon';
0022 import { MatIconButton} from '@angular/material/button';
0023 import {MatTooltip} from '@angular/material/tooltip';
0024
0025 import {PerfStatsComponent} from "../../components/perf-stats/perf-stats.component";
0026 import {EventDisplayService} from "../../services/event-display.service";
0027 import {EventTimeControlComponent} from "../../components/event-time-control/event-time-control.component";
0028 import {LegendWindowComponent} from "../../components/legend-window/legend-window.component";
0029 import {PainterConfigPanelComponent} from "../../components/painter-config-panel/painter-config-panel.component";
0030 import {ObjectRaycastComponent} from "../../components/object-raycast/object-raycast.component";
0031 import {MatProgressSpinner} from "@angular/material/progress-spinner";
0032 import {SceneExportComponent} from "../../components/scene-export/scene-export";
0033 import {AnimationSettingsComponent} from "../../components/animation-settings/animation-settings.component";
0034 import GUI from 'lil-gui';
0035 import {CommandBusService} from '@dexvis/app-features';
0036 import {DataModelService} from "../../services/data-model.service";
0037 import {RecordingMenuComponent} from "../../components/recording-menu/recording-menu.component";
0038 import {FirebirdDisplayComponent} from "../../components/firebird-display/firebird-display.component";
0039 import {ToolbarActionRegistration, injectCameraPresets, injectToolbarActions} from "@dexvis/firebird-ng/api";
0040
0041
0042
0043 /**
0044 * The display page: `<firebird-display>` in the Firebird shell, with the
0045 * toolbar (event selector, data selector, clipping, export, recording), the
0046 * model and scene trees, the painter panel, the time controls with the
0047 * toolbar actions packs contribute (withToolbarAction), and the camera debug
0048 * panel (lil-gui).
0049 */
0050 @Component({
0051 selector: 'app-main-display',
0052 templateUrl: './main-display.component.html',
0053 styleUrls: ['./main-display.component.scss'],
0054 changeDetection: ChangeDetectionStrategy.OnPush,
0055 imports: [
0056 MatIcon,
0057 MatTooltip,
0058 MatIconButton,
0059 SceneTreeComponent,
0060 ModelTreeComponent,
0061 FirebirdShellComponent,
0062 ToolPanelComponent,
0063 EventSelectorComponent,
0064 OpenEventComponent,
0065 GeometryClippingComponent,
0066 PerfStatsComponent,
0067 EventTimeControlComponent,
0068 LegendWindowComponent,
0069 PainterConfigPanelComponent,
0070 ObjectRaycastComponent,
0071 MatProgressSpinner,
0072 SceneExportComponent,
0073 AnimationSettingsComponent,
0074 RecordingMenuComponent,
0075 FirebirdDisplayComponent,
0076 ]
0077 })
0078 export class MainDisplayComponent implements OnInit, OnDestroy {
0079 /** Load the configured geometry and events on attach; false runs only the startup commands. */
0080 @Input() isAutoLoadOnInit = true;
0081
0082 @Input()
0083 eventDataImportOptions: string[] = []; // example, if you used them in UI
0084
0085 @ViewChild('displayHeaderControls', {static: true})
0086 displayHeaderControls!: TemplateRef<any>;
0087
0088 // For referencing child components
0089 @ViewChild(FirebirdShellComponent)
0090 displayShellComponent!: FirebirdShellComponent;
0091
0092 @ViewChild(SceneTreeComponent)
0093 geometryTreeComponent: SceneTreeComponent | null | undefined;
0094
0095 message = "";
0096
0097 loaded: boolean = false;
0098
0099 currentGeometry: string = 'All';
0100
0101 // UI toggles: two-way bound to the shell layout panes; the shell's own
0102 // toolbar toggle buttons update these too.
0103 leftPaneOpen = signal(false);
0104 rightPaneOpen = signal(false);
0105
0106 /** Left pane content: physics model tree, or the raw scene tree (debug). */
0107 leftTreeMode = signal<'model' | 'scene'>('model');
0108
0109 // Loading indicators (service-owned so every display page shows the same state)
0110 get loadingDex() { return this.eventDisplay.loadingDex; }
0111 get loadingEdm() { return this.eventDisplay.loadingEdm; }
0112 get loadingGeometry() { return this.eventDisplay.loadingGeometry; }
0113
0114 /** Camera debug panel (lil-gui), built once the scene exists and destroyed with the page. */
0115 private lilGui?: GUI;
0116 showGui = false;
0117
0118 private readonly destroyRef = inject(DestroyRef);
0119
0120 private readonly commandBus = inject(CommandBusService);
0121 /** Buttons packs add to the time controls (withToolbarAction). */
0122 readonly toolbarActions = injectToolbarActions();
0123 /** Camera presets for the debug panel's buttons. */
0124 private readonly cameraPresets = injectCameraPresets();
0125
0126 constructor(
0127 private controller: GameControllerService,
0128 public eventDisplay: EventDisplayService,
0129 public geomService: GeometryService,
0130 private dataService: DataModelService,
0131 ) {
0132 // Scene-tree debug view refresh on data arrival. Signal-driven so it
0133 // also covers command-driven loads (?dex=/?geometry=), which the old
0134 // per-load callbacks missed.
0135 effect(() => {
0136 this.geomService.geometry();
0137 this.updateSceneTreeComponent();
0138 });
0139 effect(() => {
0140 this.dataService.currentEntry();
0141 this.updateSceneTreeComponent();
0142 });
0143 }
0144
0145
0146 ngOnInit() {
0147 // The controller service is a root singleton: the subscription ends with
0148 // the page, or every visit would add one.
0149 this.controller.buttonY.onPress.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
0150 if (value) {
0151 // TODO this.cycleGeometry();
0152 }
0153 });
0154 }
0155
0156
0157 /**
0158 * The display is attached to `<firebird-display>`: the scene exists, so
0159 * the camera debug panel can bind to it.
0160 */
0161 onDisplayAttached(): void {
0162 // Camera debug panel. It sits in document.body and its .listen()
0163 // controllers poll every animation frame, so ngOnDestroy destroys it.
0164 const gui = new GUI();
0165 this.lilGui = gui;
0166 // One button per registered camera preset, through the command bus like
0167 // every other camera move
0168 for (const preset of this.cameraPresets) {
0169 const button = {go: () => this.runCommand({type: 'camera-preset', name: preset.name})};
0170 gui.add(button, 'go').name(`Camera to ${preset.label ?? preset.name}`);
0171 }
0172 gui.add(this, 'makeScreenshot').name('Make Screenshot');
0173
0174 gui.add(this.eventDisplay.three.perspectiveCamera.position, 'x').name('Camera x[mm]').decimals(2).listen();
0175 gui.add(this.eventDisplay.three.perspectiveCamera.position, 'y').name('Camera y[mm]').decimals(2).listen();
0176 gui.add(this.eventDisplay.three.perspectiveCamera.position, 'z').name('Camera z[mm]').decimals(2).listen();
0177
0178 gui.add(this.eventDisplay.three.controls.target, 'x').name("Pivot x[mm]").decimals(1).listen();
0179 gui.add(this.eventDisplay.three.controls.target, 'y').name("Pivot y[mm]").decimals(1).listen();
0180 gui.add(this.eventDisplay.three.controls.target, 'z').name("Pivot z[mm]").decimals(1).listen();
0181
0182 gui.add(this.eventDisplay.three, "showBVHDebug");
0183
0184 // GUI settings
0185 gui.domElement.style.top = '64px';
0186 gui.domElement.style.right = '120px';
0187 gui.domElement.style.display = this.showGui ? 'block' : 'none';
0188
0189 // Recording lives in the toolbar (app-recording-menu), not in this
0190 // debug GUI.
0191 }
0192
0193 // 3) UI - Toggling panes
0194 toggleLeftPane() {
0195 this.leftPaneOpen.update(v => !v);
0196 }
0197
0198 toggleRightPane() {
0199 this.rightPaneOpen.update(v => !v);
0200 }
0201
0202 ngOnDestroy(): void {
0203 this.lilGui?.destroy();
0204 this.lilGui = undefined;
0205 }
0206
0207 // 10) SCENE TREE / UI
0208 private updateSceneTreeComponent() {
0209 // Example: rename lights
0210 const scene = this.eventDisplay.three.scene;
0211 if (this.geometryTreeComponent) {
0212 this.geometryTreeComponent.refreshSceneTree();
0213 }
0214 }
0215
0216 toggleCameraControls() {
0217 this.showGui = !this.showGui;
0218
0219 // Toggle GUI visibility (the panel exists once the scene initialized)
0220 if (this.lilGui) {
0221 this.lilGui.domElement.style.display = this.showGui ? 'block' : 'none';
0222 }
0223 }
0224
0225
0226 onConfigureItemClicked(type: string) {
0227 // The right pane hosts the painter panel, driven by the shared selection.
0228 this.rightPaneOpen.set(true);
0229 }
0230
0231 runToolbarAction(action: ToolbarActionRegistration) {
0232 this.runCommand(action.command);
0233 }
0234
0235 /** Dispatches a UI command; a failure reaches the user's error channel. */
0236 private runCommand(command: {type: string, [key: string]: unknown}) {
0237 this.commandBus.dispatch({...command, source: 'ui'})
0238 .catch(error => this.eventDisplay.reportError(
0239 `Command '${command.type}' failed: ${error instanceof Error ? error.message : error}`));
0240 }
0241
0242 makeScreenshot() {
0243 const renderer = this.eventDisplay.three.renderer;
0244 // Render one frame to ensure the canvas is up to date
0245 renderer.render(this.eventDisplay.three.scene, this.eventDisplay.three.camera);
0246 // Use toDataURL for broad browser compatibility (works in Firefox)
0247 const dataUrl = renderer.domElement.toDataURL('image/png');
0248 const link = document.createElement('a');
0249 link.href = dataUrl;
0250 link.download = `firebird-screenshot-${Date.now()}.png`;
0251 document.body.appendChild(link);
0252 link.click();
0253 document.body.removeChild(link);
0254 }
0255
0256 }