Warning, /firebird/firebird-ng/src/app/pages/main-display/main-display.component.ts is written in an unsupported language. File is not indexed.
0001 import {
0002 Component,
0003 OnInit,
0004 AfterViewInit,
0005 Input,
0006 ViewChild, OnDestroy, TemplateRef, ElementRef, signal
0007 } from '@angular/core';
0008
0009 import {ALL_GROUPS} from '../../services/geometry.service';
0010 import {GameControllerService} from '../../services/game-controller.service';
0011 import {LocalStorageService} from '../../services/local-storage.service';
0012
0013 import {SceneTreeComponent} from '../../components/scene-tree/scene-tree.component';
0014 import {ShellComponent} from '../../components/shell/shell.component';
0015 import {ToolPanelComponent} from '../../components/tool-panel/tool-panel.component';
0016 import {EventSelectorComponent} from '../../components/event-selector/event-selector.component';
0017 import {ObjectClippingComponent} from '../../components/object-clipping/object-clipping.component';
0018 import {PhoenixThreeFacade} from "../../utils/phoenix-three-facade";
0019
0020 import {MatSnackBar} from '@angular/material/snack-bar';
0021 import {MatIcon} from '@angular/material/icon';
0022 import { MatIconButton} from '@angular/material/button';
0023 import {MatTooltip} from '@angular/material/tooltip';
0024 import {EventDisplay} from "phoenix-event-display";
0025
0026 import {PerfStatsComponent} from "../../components/perf-stats/perf-stats.component";
0027 import {EventDisplayService} from "../../services/event-display.service";
0028 import {EventTimeControlComponent} from "../../components/event-time-control/event-time-control.component";
0029 import {ServerConfigService} from "../../services/server-config.service";
0030 import {CubeViewportControlComponent} from "../../components/cube-viewport-control/cube-viewport-control.component";
0031 import {LegendWindowComponent} from "../../components/legend-window/legend-window.component";
0032 import {PainterConfigPageComponent} from "../../services/configurator/painter-config-page.component";
0033 import {NgIf} from "@angular/common";
0034 import {TrackPainterConfig} from "../../services/track-painter-config";
0035 import {ObjectRaycastComponent} from "../../components/object-raycast/object-raycast.component";
0036 import {MatProgressSpinner} from "@angular/material/progress-spinner";
0037 import GUI from 'lil-gui';
0038
0039 /**
0040 * This MainDisplayComponent:
0041 * - Initializes and uses ThreeService (which sets up scene, camera, controls, etc.).
0042 * - Loads geometry via GeometryService, attaches it to scene.
0043 * - Loads event data (Dex or custom) via DataModelService, builds objects in "EventData" group.
0044 * - Uses EicAnimationsManager for collisions/expansions.
0045 * - Has leftover UI logic for sliders, time stepping, left/right pane toggling, etc.
0046 * - Has *no* references to phoenix-event-display or eventDisplay.
0047 */
0048 @Component({
0049 selector: 'app-main-display',
0050 templateUrl: './main-display.component.html',
0051 styleUrls: ['./main-display.component.scss'],
0052 imports: [
0053 MatIcon,
0054 MatTooltip,
0055 MatIconButton,
0056 SceneTreeComponent,
0057 ShellComponent,
0058 ToolPanelComponent,
0059 EventSelectorComponent,
0060 ObjectClippingComponent,
0061 PerfStatsComponent,
0062 EventTimeControlComponent,
0063 CubeViewportControlComponent,
0064 LegendWindowComponent,
0065 PainterConfigPageComponent,
0066 NgIf,
0067 ObjectRaycastComponent,
0068 MatProgressSpinner,
0069 ]
0070 })
0071 export class MainDisplayComponent implements OnInit, AfterViewInit, OnDestroy {
0072 @Input()
0073 eventDataImportOptions: string[] = []; // example, if you used them in UI
0074
0075 @ViewChild('displayHeaderControls', {static: true})
0076 displayHeaderControls!: TemplateRef<any>;
0077
0078 @ViewChild('eventDisplay')
0079 eventDisplayDiv!: ElementRef;
0080
0081 // For referencing child components
0082 @ViewChild(ShellComponent)
0083 displayShellComponent!: ShellComponent;
0084
0085 @ViewChild(SceneTreeComponent)
0086 geometryTreeComponent: SceneTreeComponent | null | undefined;
0087
0088 @ViewChild(CubeViewportControlComponent)
0089 private cubeControl!: CubeViewportControlComponent;
0090
0091 message = "";
0092
0093 loaded: boolean = false;
0094
0095 // The geometry group switching index, used in cycleGeometry()
0096 private geometryGroupSwitchingIndex = ALL_GROUPS.length;
0097 currentGeometry: string = 'All';
0098
0099 // UI toggles
0100 isLeftPaneOpen: boolean = false;
0101 isRightPaneOpen: boolean = false;
0102
0103 // Loading indicators
0104 loadingDex = signal(false);
0105 loadingEdm = signal(false);
0106 loadingGeometry = signal(false);
0107
0108 // lil GUI for right panel
0109 lilGui = new GUI();
0110 showGui = false;
0111
0112 // Phoenix API
0113 private facade: PhoenixThreeFacade = new PhoenixThreeFacade(new EventDisplay());
0114
0115 constructor(
0116 private controller: GameControllerService,
0117 private snackBar: MatSnackBar,
0118 public eventDisplay: EventDisplayService,
0119 private userConfig: LocalStorageService,
0120 private serverConfig: ServerConfigService,
0121 ) {
0122
0123 }
0124
0125
0126 async ngOnInit() {
0127 // Initialize the ThreeService scene/camera/renderer/controls
0128 this.eventDisplay.initThree('eventDisplay');
0129
0130 // The facade will be initialized in three.service
0131 this.facade.initializeScene()
0132
0133
0134 this.controller.buttonY.onPress.subscribe((value) => {
0135 if (value) {
0136 // TODO this.cycleGeometry();
0137 }
0138 });
0139 }
0140
0141
0142 // 2) AFTER VIEW INIT => handle resizing with DisplayShell or window
0143 ngAfterViewInit(): void {
0144
0145 // Load JSON based data files
0146 this.initDexEventSource();
0147
0148 // Load Root file based data files
0149 this.initRootData();
0150
0151 if (this.displayShellComponent) {
0152 const resizeInvoker = () => {
0153 setTimeout(() => {
0154 this.onRendererElementResize();
0155 }, 100);
0156 };
0157 this.displayShellComponent.onVisibilityChangeLeft.subscribe(resizeInvoker);
0158 this.displayShellComponent.onVisibilityChangeRight.subscribe(resizeInvoker);
0159 this.displayShellComponent.onEndResizeLeft.subscribe(() => this.onRendererElementResize());
0160 this.displayShellComponent.onEndResizeRight.subscribe(() => this.onRendererElementResize());
0161 }
0162
0163 this.initCubeViewportControl();
0164
0165 window.addEventListener('resize', () => {
0166 this.onRendererElementResize();
0167 });
0168
0169 // When sidebar is collapsed/opened, the main container, i.e. #eventDisplay offsetWidth is not yet updated.
0170 // This leads to a not proper resize processing. We add 100ms delay before calling a function
0171 const this_obj = this;
0172 const resizeInvoker = function () {
0173 setTimeout(() => {
0174 this_obj.onRendererElementResize();
0175 }, 100); // 100 milliseconds = 0.1 seconds
0176 };
0177 resizeInvoker();
0178
0179 // Loads the geometry (do it last as it might be long)
0180 this.initGeometry();
0181
0182 // Init gui
0183 this.lilGui.add(this.eventDisplay.three.perspectiveCamera.position, 'x').listen();
0184 this.lilGui.add(this.eventDisplay.three.perspectiveCamera.position, 'y').listen();
0185 this.lilGui.add(this.eventDisplay.three.perspectiveCamera.position, 'z').listen();
0186
0187 // GUI settings
0188 this.lilGui.domElement.style.top = '64px';
0189 this.lilGui.domElement.style.right = '120px';
0190 this.lilGui.domElement.style.display = 'none';
0191
0192 }
0193
0194 // 3) UI - Toggling panes
0195 toggleLeftPane() {
0196 this.displayShellComponent?.toggleLeftPane();
0197 this.isLeftPaneOpen = !this.isLeftPaneOpen;
0198 }
0199
0200 toggleRightPane() {
0201 this.displayShellComponent?.toggleRightPane();
0202 this.isRightPaneOpen = !this.isRightPaneOpen;
0203 }
0204
0205 // 4) Method to initialize CubeViewportControl with the existing Three.js objects
0206 private initCubeViewportControl(): void {
0207 const {scene, camera, renderer} = this.eventDisplay.three;
0208 if (this.cubeControl && scene && camera && renderer) {
0209 // Pass the external scene, camera, and renderer to the cube control
0210 this.cubeControl.initWithExternalScene(scene, camera, renderer);
0211 this.cubeControl.gizmo.attachControls(this.eventDisplay.three.controls);
0212 this.cubeControl.gizmo.camera
0213 }
0214
0215 const thisPointer = this;
0216 this.eventDisplay.three.addFrameCallback(() => {
0217 if (thisPointer.cubeControl?.gizmo) {
0218 thisPointer.cubeControl.gizmo.render();
0219 }
0220 });
0221 }
0222
0223
0224 showError(message: string) {
0225 this.snackBar.open(message, 'Dismiss', {
0226 duration: 7000, // Auto-dismiss after X ms
0227 // verticalPosition: 'top', // Place at the top of the screen
0228 panelClass: ['error-snackbar']
0229 });
0230 }
0231
0232
0233 ngOnDestroy(): void {
0234 // Clear the custom controls when leaving the page
0235 }
0236
0237
0238 // Called when we want to recalculate the size of the canvas
0239 private onRendererElementResize() {
0240 let {width, height} = this.displayShellComponent.getMainAreaVisibleDimensions();
0241 console.log(`[RendererResize] New size: ${width}x${height} px`);
0242
0243 // Delegate resizing to ThreeService
0244 this.eventDisplay.three.setSize(width, height);
0245 if (this.cubeControl?.gizmo) {
0246 this.cubeControl.gizmo.update();
0247 }
0248 }
0249
0250 // 8) GEOMETRY TOGGLING
0251 // showAllGeometries() {
0252 // this.geometryGroupSwitchingIndex = ALL_GROUPS.length;
0253 // if (!this.geomService.subdetectors) return;
0254 // for (let detector of this.geomService.subdetectors) {
0255 // detector.geometry.visible = true;
0256 // }
0257 // }
0258 // showGeometryGroup(groupName: string) {
0259 // if (!this.geomService.subdetectors) return;
0260 // for (let detector of this.geomService.subdetectors) {
0261 // detector.geometry.visible = (detector.groupName === groupName);
0262 // }
0263 // }
0264 // cycleGeometry() {
0265 // this.geometryGroupSwitchingIndex++;
0266 // if (this.geometryGroupSwitchingIndex > ALL_GROUPS.length) {
0267 // this.geometryGroupSwitchingIndex = 0;
0268 // }
0269 // if (this.geometryGroupSwitchingIndex === ALL_GROUPS.length) {
0270 // this.showAllGeometries();
0271 // this.currentGeometry = 'All';
0272 // } else {
0273 // this.currentGeometry = ALL_GROUPS[this.geometryGroupSwitchingIndex];
0274 // this.showGeometryGroup(this.currentGeometry);
0275 // }
0276 // }
0277
0278 public formatCurrentTime(value: number): string {
0279 return value.toFixed(1);
0280 }
0281
0282 changeCurrentTime(event: Event) {
0283 if (!event) return;
0284 const input = event.target as HTMLInputElement;
0285 const value = parseFloat(input.value);
0286 this.eventDisplay.updateEventTime(value);
0287 }
0288
0289 // 10) SCENE TREE / UI
0290 private updateSceneTreeComponent() {
0291 // Example: rename lights
0292 const scene = this.eventDisplay.three.scene;
0293 if (this.geometryTreeComponent) {
0294 this.geometryTreeComponent.refreshSceneTree();
0295 }
0296 }
0297
0298 onDebugButton() {
0299 this.showGui = !this.showGui;
0300
0301 // Toggle GUI visibility
0302 const guiElement = this.lilGui.domElement;
0303 if (this.showGui) {
0304 guiElement.style.display = 'block';
0305 } else {
0306 guiElement.style.display = 'none';
0307 }
0308 }
0309
0310 toggleAnimationCycling() {
0311 if (this.eventDisplay.animationIsCycling()) {
0312 this.eventDisplay.stopAnimationCycling();
0313 } else {
0314 this.eventDisplay.startAnimationCycling();
0315 }
0316 }
0317
0318
0319 selectedConfigItem: any = null;
0320
0321 onConfigureItemClicked(type: string) {
0322 if (type === 'track') {
0323 this.selectedConfigItem = {
0324 name: 'Track A',
0325 type: 'track',
0326 config: new TrackPainterConfig()
0327 };
0328 }
0329
0330 this.toggleRightPane();
0331 }
0332
0333 private initDexEventSource() {
0334
0335 // We set loadingDex=false to be safe
0336 this.loadingDex.set(false);
0337
0338 let dexUrl = this.userConfig.dexJsonEventSource.subject.getValue();
0339
0340 if (!dexUrl || dexUrl.trim().length === 0) {
0341 console.log("[main-display]: No event data source specified. Skip loadDexData.");
0342 }
0343 // Check if we have the same data
0344 else if (this.eventDisplay.lastLoadedDexUrl === dexUrl) {
0345 console.log(`[main-display]: Event data (DEX) url is the same as before: '${dexUrl}', skip loading.`);
0346 }
0347 // Try to load
0348 else {
0349 this.loadingDex.set(true);
0350 this.eventDisplay.loadDexData(dexUrl).catch(error => {
0351 const msg = `Error loading events: ${error}`;
0352 console.error(`[main-display]: ${msg}`);
0353 this.showError(msg);
0354 }).then(() => {
0355 console.log("[main-display]: Event data loaded.");
0356 this.updateSceneTreeComponent();
0357 }).finally(()=>{
0358 this.loadingDex.set(false); // switch off loading indicator
0359 });
0360 }
0361 }
0362
0363
0364 private initRootData() {
0365 let url = this.userConfig.rootEventSource.subject.getValue();
0366 let eventRange = this.userConfig.rootEventRange.subject.getValue();
0367
0368 // Do we have url?
0369 if (!url || url.trim().length === 0) {
0370 console.log("[main-display]: No Edm4Eic source specified. Nothing to load");
0371 return;
0372 }
0373
0374 // Do we have event Range?
0375 if (!eventRange || eventRange.trim().length === 0) {
0376 console.log("[main-display]: Event Range specified. Trying '0', to load the first event");
0377 eventRange = "0";
0378 }
0379
0380 // Check if we have the same data
0381 if (this.eventDisplay.lastLoadedRootUrl === url && this.eventDisplay.lastLoadedRootEventRange === eventRange) {
0382 console.log(`[main-display]: Edm url is the same as before: '${url}', eventRange: '${eventRange}' - skip loading.`);
0383 return;
0384 }
0385
0386 // Try to load
0387 else {
0388 this.loadingEdm.set(true);
0389 this.eventDisplay.loadRootData(url, eventRange).catch(error => {
0390 const msg = `Error loading events: ${error}`;
0391 console.error(`[main-display]: ${msg}`);
0392 this.showError(msg);
0393 }).then(() => {
0394 console.log("[main-display]: Event data loaded.");
0395 this.updateSceneTreeComponent();
0396 }).finally(()=>{
0397 this.loadingEdm.set(false); // switch off loading indicator
0398 });
0399 }
0400 }
0401
0402
0403 private initGeometry() {
0404 let url = this.userConfig.geometryUrl.value;
0405
0406 if (!url || url.trim().length === 0) {
0407 console.log("[main-display]: No geometry specified. Skip loadGeometry ");
0408 }
0409 // Check if we have the same data
0410 else if (this.eventDisplay.lastLoadedGeometryUrl === url) {
0411 console.log(`[main-display]: Geometry url is the same as before: '${url}', skip loading`);
0412 } else {
0413 // Load geometry
0414 this.loadingGeometry.set(true);
0415 this.eventDisplay.loadGeometry(url).catch(error => {
0416
0417 const msg = `Error loading geometry: ${error}`;
0418 console.error(`[main-display]: ${msg}`);
0419 this.showError("Error loading Geometry. Open 'Configure' to change. Press F12->Console for logs");
0420 }).then(() => {
0421 this.updateSceneTreeComponent();
0422 console.log("[main-display]: Geometry loaded");
0423
0424 }).finally(()=>{
0425 this.loadingGeometry.set(false); // switch off loading indicator
0426 });
0427 }
0428 }
0429
0430 animateWithCollision() {
0431 this.eventDisplay.animateWithCollision();
0432 }
0433 }