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
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 // Phoenix API
0109 private facade: PhoenixThreeFacade = new PhoenixThreeFacade(new EventDisplay());
0110
0111 constructor(
0112 private controller: GameControllerService,
0113 private snackBar: MatSnackBar,
0114 public eventDisplay: EventDisplayService,
0115 private userConfig: LocalStorageService,
0116 private serverConfig: ServerConfigService,
0117 ) {
0118
0119 }
0120
0121
0122 async ngOnInit() {
0123 // Initialize the ThreeService scene/camera/renderer/controls
0124 this.eventDisplay.initThree('eventDisplay');
0125
0126 // The facade will be initialized in three.service
0127 this.facade.initializeScene()
0128
0129
0130 this.controller.buttonY.onPress.subscribe((value) => {
0131 if (value) {
0132 // TODO this.cycleGeometry();
0133 }
0134 });
0135 }
0136
0137
0138 // 2) AFTER VIEW INIT => handle resizing with DisplayShell or window
0139 ngAfterViewInit(): void {
0140
0141 // Load JSON based data files
0142 this.initDexEventSource();
0143
0144 // Load Root file based data files
0145 this.initRootData();
0146
0147 if (this.displayShellComponent) {
0148 const resizeInvoker = () => {
0149 setTimeout(() => {
0150 this.onRendererElementResize();
0151 }, 100);
0152 };
0153 this.displayShellComponent.onVisibilityChangeLeft.subscribe(resizeInvoker);
0154 this.displayShellComponent.onVisibilityChangeRight.subscribe(resizeInvoker);
0155 this.displayShellComponent.onEndResizeLeft.subscribe(() => this.onRendererElementResize());
0156 this.displayShellComponent.onEndResizeRight.subscribe(() => this.onRendererElementResize());
0157 }
0158
0159 this.initCubeViewportControl();
0160
0161 window.addEventListener('resize', () => {
0162 this.onRendererElementResize();
0163 });
0164
0165 // When sidebar is collapsed/opened, the main container, i.e. #eventDisplay offsetWidth is not yet updated.
0166 // This leads to a not proper resize processing. We add 100ms delay before calling a function
0167 const this_obj = this;
0168 const resizeInvoker = function () {
0169 setTimeout(() => {
0170 this_obj.onRendererElementResize();
0171 }, 100); // 100 milliseconds = 0.1 seconds
0172 };
0173 resizeInvoker();
0174
0175 // Loads the geometry (do it last as it might be long)
0176 this.initGeometry();
0177 }
0178
0179 // 3) UI - Toggling panes
0180 toggleLeftPane() {
0181 this.displayShellComponent?.toggleLeftPane();
0182 this.isLeftPaneOpen = !this.isLeftPaneOpen;
0183 }
0184
0185 toggleRightPane() {
0186 this.displayShellComponent?.toggleRightPane();
0187 this.isRightPaneOpen = !this.isRightPaneOpen;
0188 }
0189
0190 // 4) Method to initialize CubeViewportControl with the existing Three.js objects
0191 private initCubeViewportControl(): void {
0192 const {scene, camera, renderer} = this.eventDisplay.three;
0193 if (this.cubeControl && scene && camera && renderer) {
0194 // Pass the external scene, camera, and renderer to the cube control
0195 this.cubeControl.initWithExternalScene(scene, camera, renderer);
0196 this.cubeControl.gizmo.attachControls(this.eventDisplay.three.controls);
0197 this.cubeControl.gizmo.camera
0198 }
0199
0200 const thisPointer = this;
0201 this.eventDisplay.three.addFrameCallback(() => {
0202 if (thisPointer.cubeControl?.gizmo) {
0203 thisPointer.cubeControl.gizmo.render();
0204 }
0205 });
0206 }
0207
0208
0209 showError(message: string) {
0210 this.snackBar.open(message, 'Dismiss', {
0211 duration: 7000, // Auto-dismiss after X ms
0212 // verticalPosition: 'top', // Place at the top of the screen
0213 panelClass: ['error-snackbar']
0214 });
0215 }
0216
0217
0218 ngOnDestroy(): void {
0219 // Clear the custom controls when leaving the page
0220 }
0221
0222
0223 // Called when we want to recalculate the size of the canvas
0224 private onRendererElementResize() {
0225 let {width, height} = this.displayShellComponent.getMainAreaVisibleDimensions();
0226 console.log(`[RendererResize] New size: ${width}x${height} px`);
0227
0228 // Delegate resizing to ThreeService
0229 this.eventDisplay.three.setSize(width, height);
0230 if (this.cubeControl?.gizmo) {
0231 this.cubeControl.gizmo.update();
0232 }
0233 }
0234
0235 // 8) GEOMETRY TOGGLING
0236 // showAllGeometries() {
0237 // this.geometryGroupSwitchingIndex = ALL_GROUPS.length;
0238 // if (!this.geomService.subdetectors) return;
0239 // for (let detector of this.geomService.subdetectors) {
0240 // detector.geometry.visible = true;
0241 // }
0242 // }
0243 // showGeometryGroup(groupName: string) {
0244 // if (!this.geomService.subdetectors) return;
0245 // for (let detector of this.geomService.subdetectors) {
0246 // detector.geometry.visible = (detector.groupName === groupName);
0247 // }
0248 // }
0249 // cycleGeometry() {
0250 // this.geometryGroupSwitchingIndex++;
0251 // if (this.geometryGroupSwitchingIndex > ALL_GROUPS.length) {
0252 // this.geometryGroupSwitchingIndex = 0;
0253 // }
0254 // if (this.geometryGroupSwitchingIndex === ALL_GROUPS.length) {
0255 // this.showAllGeometries();
0256 // this.currentGeometry = 'All';
0257 // } else {
0258 // this.currentGeometry = ALL_GROUPS[this.geometryGroupSwitchingIndex];
0259 // this.showGeometryGroup(this.currentGeometry);
0260 // }
0261 // }
0262
0263 public formatCurrentTime(value: number): string {
0264 return value.toFixed(1);
0265 }
0266
0267 changeCurrentTime(event: Event) {
0268 if (!event) return;
0269 const input = event.target as HTMLInputElement;
0270 const value = parseFloat(input.value);
0271 this.eventDisplay.updateEventTime(value);
0272 }
0273
0274 // 10) SCENE TREE / UI
0275 private updateSceneTreeComponent() {
0276 // Example: rename lights
0277 const scene = this.eventDisplay.three.scene;
0278 if (this.geometryTreeComponent) {
0279 this.geometryTreeComponent.refreshSceneTree();
0280 }
0281 }
0282
0283 onDebugButton() {
0284 this.showError("Error message works");
0285 }
0286
0287
0288 selectedConfigItem: any = null;
0289
0290 onConfigureItemClicked(type: string) {
0291 if (type === 'track') {
0292 this.selectedConfigItem = {
0293 name: 'Track A',
0294 type: 'track',
0295 config: new TrackPainterConfig()
0296 };
0297 }
0298
0299 this.toggleRightPane();
0300 }
0301
0302 private initDexEventSource() {
0303
0304 // We set loadingDex=false to be safe
0305 this.loadingDex.set(false);
0306
0307 let dexUrl = this.userConfig.dexJsonEventSource.subject.getValue();
0308
0309 if (!dexUrl || dexUrl.trim().length === 0) {
0310 console.log("[main-display]: No event data source specified. Skip loadDexData.");
0311 }
0312 // Check if we have the same data
0313 else if (this.eventDisplay.lastLoadedDexUrl === dexUrl) {
0314 console.log(`[main-display]: Event data (DEX) url is the same as before: '${dexUrl}', skip loading.`);
0315 }
0316 // Try to load
0317 else {
0318 this.loadingDex.set(true);
0319 this.eventDisplay.loadDexData(dexUrl).catch(error => {
0320 const msg = `Error loading events: ${error}`;
0321 console.error(`[main-display]: ${msg}`);
0322 this.showError(msg);
0323 }).then(() => {
0324 console.log("[main-display]: Event data loaded.");
0325 this.updateSceneTreeComponent();
0326 }).finally(()=>{
0327 this.loadingDex.set(false); // switch off loading indicator
0328 });
0329 }
0330 }
0331
0332
0333 private initRootData() {
0334 let url = this.userConfig.rootEventSource.subject.getValue();
0335 let eventRange = this.userConfig.rootEventRange.subject.getValue();
0336
0337 // Do we have url?
0338 if (!url || url.trim().length === 0) {
0339 console.log("[main-display]: No Edm4Eic source specified. Nothing to load");
0340 return;
0341 }
0342
0343 // Do we have event Range?
0344 if (!eventRange || eventRange.trim().length === 0) {
0345 console.log("[main-display]: Event Range specified. Trying '0', to load the first event");
0346 eventRange = "0";
0347 }
0348
0349 // Check if we have the same data
0350 if (this.eventDisplay.lastLoadedRootUrl === url && this.eventDisplay.lastLoadedRootEventRange === eventRange) {
0351 console.log(`[main-display]: Edm url is the same as before: '${url}', eventRange: '${eventRange}' - skip loading.`);
0352 return;
0353 }
0354
0355 // Try to load
0356 else {
0357 this.loadingEdm.set(true);
0358 this.eventDisplay.loadRootData(url, eventRange).catch(error => {
0359 const msg = `Error loading events: ${error}`;
0360 console.error(`[main-display]: ${msg}`);
0361 this.showError(msg);
0362 }).then(() => {
0363 console.log("[main-display]: Event data loaded.");
0364 this.updateSceneTreeComponent();
0365 }).finally(()=>{
0366 this.loadingEdm.set(false); // switch off loading indicator
0367 });
0368 }
0369 }
0370
0371
0372 private initGeometry() {
0373 let url = this.userConfig.geometryUrl.value;
0374
0375 if (!url || url.trim().length === 0) {
0376 console.log("[main-display]: No geometry specified. Skip loadGeometry ");
0377 }
0378 // Check if we have the same data
0379 else if (this.eventDisplay.lastLoadedGeometryUrl === url) {
0380 console.log(`[main-display]: Geometry url is the same as before: '${url}', skip loading`);
0381 } else {
0382 // Load geometry
0383 this.loadingGeometry.set(true);
0384 this.eventDisplay.loadGeometry(url).catch(error => {
0385
0386 const msg = `Error loading geometry: ${error}`;
0387 console.error(`[main-display]: ${msg}`);
0388 this.showError("Error loading Geometry. Open 'Configure' to change. Press F12->Console for logs");
0389 }).then(() => {
0390 this.updateSceneTreeComponent();
0391 console.log("[main-display]: Geometry loaded");
0392
0393 }).finally(()=>{
0394 this.loadingGeometry.set(false); // switch off loading indicator
0395 });
0396 }
0397 }
0398 }