Back to home page

EIC code displayed by LXR

 
 

    


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 {AfterViewInit, Component, HostListener, Input, OnInit, ViewChild} from '@angular/core';
0002 import {
0003   EventDataFormat,
0004   EventDataImportOption,
0005   EventDisplayService
0006 } from 'phoenix-ui-components';
0007 import {ClippingSetting, Configuration, PhoenixLoader, PhoenixMenuNode, PresetView} from 'phoenix-event-display';
0008 import * as THREE from 'three';
0009 import {Color, DoubleSide, InstancedBufferGeometry, Line, MeshLambertMaterial, MeshPhongMaterial,} from "three";
0010 import {ALL_GROUPS, GeometryService} from '../../services/geometry.service';
0011 import {ThreeGeometryProcessor} from "../../data-pipelines/three-geometry.processor";
0012 import * as TWEEN from '@tweenjs/tween.js';
0013 import {produceRenderOrder} from "jsroot/geom";
0014 import {getColorOrDefault} from "../../utils/three.utils";
0015 import {PhoenixThreeFacade} from "../../utils/phoenix-three-facade";
0016 import {GameControllerService} from "../../services/game-controller.service";
0017 import {IoOptionsComponent} from "../../components/io-options/io-options.component";
0018 import {ProcessTrackInfo, ThreeEventProcessor} from "../../data-pipelines/three-event.processor";
0019 import {UserConfigService} from "../../services/user-config.service";
0020 import {EicAnimationsManager} from "../../phoenix-overload/eic-animation-manager";
0021 import {MatSlider, MatSliderThumb} from "@angular/material/slider";
0022 import {MatIcon} from "@angular/material/icon";
0023 import {MatButton, MatIconButton} from "@angular/material/button";
0024 import {DecimalPipe, NgClass, NgForOf, NgIf} from "@angular/common";
0025 import {MatTooltip} from "@angular/material/tooltip";
0026 import {MatSnackBar} from "@angular/material/snack-bar"
0027 import {MatFormField} from "@angular/material/form-field";
0028 import {MatOption, MatSelect} from "@angular/material/select";
0029 import {DataModelService} from "../../services/data-model.service";
0030 import {AngularSplitModule} from "angular-split";
0031 import {SceneTreeComponent} from "../geometry-tree/scene-tree.component";
0032 import {DisplayShellComponent} from "../../components/display-shell/display-shell.component";
0033 import {DataModelPainter} from "../../painters/data-model-painter";
0034 import {ToolPanelComponent} from "../../components/tool-panel/tool-panel.component";
0035 import {NavConfigComponent} from "../../components/nav-config/nav-config.component";
0036 import {UrlService} from "../../services/url.service";
0037 import {EventSelectorComponent} from "../../components/event-selector/event-selector.component";
0038 import {AutoRotateComponent} from "../../components/auto-rotate/auto-rotate.component";
0039 import {DarkThemeComponent} from "../../components/dark-theme/dark-theme.component";
0040 import {ObjectClippingComponent} from "../../components/object-clipping/object-clipping.component";
0041 
0042 
0043 @Component({
0044   selector: 'app-test-experiment',
0045   templateUrl: './main-display.component.html',
0046   imports: [IoOptionsComponent, MatSlider, MatIcon, MatButton, MatSliderThumb, DecimalPipe, MatTooltip, MatFormField, MatSelect, MatOption, NgForOf, AngularSplitModule, SceneTreeComponent, NgClass, MatIconButton, DisplayShellComponent, ToolPanelComponent, NavConfigComponent, NgIf, EventSelectorComponent, AutoRotateComponent, DarkThemeComponent, ObjectClippingComponent],
0047   standalone: true,
0048   styleUrls: ['./main-display.component.scss']
0049 })
0050 export class MainDisplayComponent implements OnInit, AfterViewInit {
0051 
0052   @Input()
0053   eventDataImportOptions: EventDataImportOption[] = Object.values(EventDataFormat);
0054 
0055   currentTime = 0;
0056   maxTime = 200;
0057   minTime = 0;
0058   message = "";
0059 
0060 
0061   /** The root Phoenix menu node. */
0062   phoenixMenuRoot = new PhoenixMenuNode("Phoenix Menu");
0063 
0064   threeGeometryProcessor = new ThreeGeometryProcessor();
0065   threeEventProcessor = new ThreeEventProcessor();
0066 
0067   /** is geometry loaded */
0068   loaded: boolean = false;
0069 
0070   /** loading progress */
0071   loadingProgress: number = 0;
0072 
0073   /** The Default color of elements if not set */
0074   defaultColor: Color = new Color(0x2fd691);
0075 
0076   private geometryGroupSwitchingIndex = ALL_GROUPS.length;
0077 
0078 
0079   private renderer: THREE.Renderer|null = null;
0080   private camera: THREE.Camera|null = null;
0081   private scene: THREE.Scene|null = null;
0082   private stats: any|null = null;         // Stats JS display from UI manager
0083 
0084   private threeFacade: PhoenixThreeFacade;
0085   private trackInfos: ProcessTrackInfo[] | null = null;
0086   private tween: TWEEN.Tween<any> | null = null;
0087   private animationManager: EicAnimationsManager| null = null;
0088   currentGeometry: string = "All";
0089   private animateEventAfterLoad: boolean = false;
0090   protected eventsByName = new Map<string, any>();
0091   private eventsArray: any[] = [];
0092   selectedEventKey: string|undefined;
0093   private beamAnimationTime: number = 1000;
0094 
0095   isLeftPaneOpen: boolean = false;
0096   isDarkTheme = false;
0097 
0098   isPhoenixMenuOpen: boolean = false;
0099   isSmallScreen: boolean = window.innerWidth < 768;
0100 
0101   private painter: DataModelPainter = new DataModelPainter();
0102 
0103 
0104   constructor(
0105     private geomService: GeometryService,
0106     private eventDisplay: EventDisplayService,
0107     private controller: GameControllerService,
0108     private settings: UserConfigService,
0109     private dataService: DataModelService,
0110     private urlService: UrlService,
0111     private _snackBar: MatSnackBar) {
0112     this.threeFacade = new PhoenixThreeFacade(this.eventDisplay);
0113   }
0114 
0115   @ViewChild(DisplayShellComponent)
0116   displayShellComponent!: DisplayShellComponent;
0117 
0118   @ViewChild(SceneTreeComponent)
0119   geometryTreeComponent: SceneTreeComponent|null|undefined;
0120 
0121   toggleLeftPane() {
0122     this.displayShellComponent.toggleLeftPane();
0123     this.isLeftPaneOpen = !this.isLeftPaneOpen;
0124 
0125   }
0126 
0127   toggleRightPane() {
0128     this.displayShellComponent.toggleRightPane();
0129   }
0130 
0131   @HostListener('window:resize', ['$event'])
0132   onResize(event: any) {
0133     this.isSmallScreen = event.target.innerWidth < 768;
0134     if (!this.isSmallScreen) {
0135       this.isPhoenixMenuOpen = true;
0136     }
0137   }
0138 
0139   togglePhoenixMenu() {
0140     this.isPhoenixMenuOpen = !this.isPhoenixMenuOpen;
0141   }
0142 
0143 
0144   logRendererInfo() {
0145     let renderer = this.threeFacade.mainRenderer;
0146     console.log('Draw calls:', renderer.info.render.calls);
0147     console.log('Triangles:', renderer.info.render.triangles);
0148     console.log('Points:', renderer.info.render.points);
0149     console.log('Lines:', renderer.info.render.lines);
0150     console.log('Geometries in memory:', renderer.info.memory.geometries);
0151     console.log('Textures in memory:', renderer.info.memory.textures);
0152     console.log('Programs:', renderer.info?.programs?.length);
0153     console.log(renderer.info?.programs);
0154   }
0155 
0156   async loadGeometry(initiallyVisible=true, scale=10) {
0157 
0158     let {rootGeometry, threeGeometry} = await this.geomService.loadGeometry();
0159     if(!threeGeometry) return;
0160 
0161 
0162     let threeManager = this.eventDisplay.getThreeManager();
0163     let uiManager = this.eventDisplay.getUIManager();
0164     let openThreeManager: any = threeManager;
0165     let importManager = openThreeManager.importManager;
0166     const doubleSided = true;
0167 
0168     const sceneGeometry = threeManager.getSceneManager().getGeometries();
0169 
0170     // Set geometry scale
0171     if (scale) {
0172       threeGeometry.scale.setScalar(scale);
0173     }
0174 
0175     // Add root geometry to scene
0176     // console.log("CERN ROOT converted to Object3d: ", rootObject3d);
0177     sceneGeometry.add(threeGeometry);
0178 
0179     // Now we want to change the materials
0180     sceneGeometry.traverse( (child: any) => {
0181 
0182         if(child.type!=="Mesh" || !child?.material?.isMaterial) {
0183           return;
0184         }
0185 
0186         // Assuming `getObjectSize` is correctly typed and available
0187         child.userData["size"] = importManager.getObjectSize(child);
0188 
0189         // Handle the material of the child
0190 
0191         const color = getColorOrDefault(child.material, this.defaultColor);
0192         const side = doubleSided ? DoubleSide : child.material.side;
0193 
0194         child.material.dispose(); // Dispose the old material if it's a heavy object
0195 
0196         let opacity = threeGeometry.userData["opacity"] ?? 1;
0197         let transparent = opacity < 1;
0198 
0199         // child.material = new MeshPhongMaterial({
0200         //   color: color,
0201         //   shininess: 0,
0202         //   side: side,
0203         //   transparent: true,
0204         //   opacity: 0.7,
0205         //   blending: THREE.NormalBlending,
0206         //   depthTest: true,
0207         //   depthWrite: true,
0208         //   clippingPlanes: openThreeManager.clipPlanes,
0209         //   clipIntersection: true,
0210         //   clipShadows: false
0211         // });
0212 
0213         child.material = new MeshLambertMaterial({
0214           color: color,
0215           side: side,
0216           transparent: true,
0217           opacity: 0.7,
0218           blending: THREE.NormalBlending,
0219           depthTest: true,
0220           depthWrite: true,
0221           clippingPlanes: openThreeManager.clipPlanes,
0222           clipIntersection: true,
0223           clipShadows: false
0224         });
0225 
0226         // Material
0227         let name:string = child.name;
0228 
0229         if(! child.material?.clippingPlanes !== undefined) {
0230           child.material.clippingPlanes = openThreeManager.clipPlanes;
0231         }
0232 
0233         if(! child.material?.clipIntersection !== undefined) {
0234           child.material.clipIntersection = true;
0235         }
0236 
0237         if(! child.material?.clipShadows !== undefined) {
0238           child.material.clipShadows = false;
0239         }
0240     });
0241 
0242     // HERE WE DO POSTPROCESSING STEP
0243     this.threeGeometryProcessor.process(this.geomService.subdetectors);
0244 
0245     // Now we want to change the materials
0246     sceneGeometry.traverse( (child: any) => {
0247 
0248       if(!child?.material?.isMaterial) {
0249         return;
0250       }
0251 
0252       if(child.material?.clippingPlanes !== undefined) {
0253         child.material.clippingPlanes = openThreeManager.clipPlanes;
0254       }
0255 
0256       if(child.material?.clipIntersection !== undefined) {
0257         child.material.clipIntersection = true;
0258       }
0259 
0260       if(child.material?.clipShadows !== undefined) {
0261         child.material.clipShadows = false;
0262       }
0263     });
0264 
0265     let renderer  = openThreeManager.rendererManager;
0266     // Set render priority
0267     let scene = threeManager.getSceneManager().getScene();
0268     scene.background = new THREE.Color( 0x3F3F3F );
0269     renderer.getMainRenderer().sortObjects = false;
0270 
0271     let camera = openThreeManager.controlsManager.getMainCamera();
0272     // camera.far = 5000;
0273     produceRenderOrder(scene, camera.position, 'ray');
0274   }
0275 
0276   produceRenderOrder() {
0277 
0278     console.log("produceRenderOrder. scene: ", this.scene, " camera ", this.camera);
0279     produceRenderOrder(this.scene, this.camera?.position, 'ray');
0280   }
0281 
0282   logGamepadStates () {
0283     const gamepads = navigator.getGamepads();
0284 
0285     for (const gamepad of gamepads) {
0286       if (gamepad) {
0287         console.log(`Gamepad connected at index ${gamepad.index}: ${gamepad.id}.`);
0288         console.log(`Timestamp: ${gamepad.timestamp}`);
0289         console.log('Axes states:');
0290         gamepad.axes.forEach((axis, index) => {
0291           console.log(`Axis ${index}: ${axis.toFixed(4)}`);
0292         });
0293         console.log('Button states:');
0294         gamepad.buttons.forEach((button, index) => {
0295           console.log(`Button ${index}: ${button.pressed ? 'pressed' : 'released'}, value: ${button.value}`);
0296         });
0297       }
0298     }
0299   };
0300 
0301   rotateCamera(xAxisChange: number, yAxisChange: number) {
0302     let orbitControls = this.threeFacade.activeOrbitControls;
0303     let camera = this.threeFacade.mainCamera;
0304 
0305     const offset = new THREE.Vector3(); // Offset of the camera from the target
0306     const quat = new THREE.Quaternion().setFromUnitVectors(camera.up, new THREE.Vector3(0, 1, 0));
0307     const quatInverse = quat.clone().invert();
0308 
0309     const currentPosition = camera.position.clone().sub(orbitControls.target);
0310     currentPosition.applyQuaternion(quat); // Apply the quaternion
0311 
0312     // Spherical coordinates
0313     const spherical = new THREE.Spherical().setFromVector3(currentPosition);
0314 
0315     // Adjusting spherical coordinates
0316     spherical.theta -= xAxisChange * 0.023; // Azimuth angle change
0317     spherical.phi += yAxisChange * 0.023; // Polar angle change, for rotating up/down
0318 
0319     // Ensure phi is within bounds to avoid flipping
0320     spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
0321 
0322     // Convert back to Cartesian coordinates
0323     const newPostion = new THREE.Vector3().setFromSpherical(spherical);
0324     newPostion.applyQuaternion(quatInverse);
0325 
0326     camera.position.copy(newPostion.add(orbitControls.target));
0327     camera.lookAt(orbitControls.target);
0328     orbitControls.update();
0329   }
0330 
0331   zoom(factor: number) {
0332     let orbitControls = this.threeFacade.activeOrbitControls;
0333     let camera = this.threeFacade.mainCamera;
0334     orbitControls.object.position.subVectors(camera.position, orbitControls.target).multiplyScalar(factor).add(orbitControls.target);
0335     orbitControls.update();
0336   }
0337 
0338   handleGamepadInputV2 () {
0339     this.controller.animationLoopHandler();
0340   }
0341 
0342   logCamera() {
0343     console.log(this.threeFacade.mainCamera);
0344   }
0345 
0346 
0347   handleGamepadInputV1 () {
0348 
0349     // Update stats display that showing FPS, etc.
0350     if (this.stats) {
0351       this.stats.update();
0352     }
0353 
0354     this.controller.animationLoopHandler();
0355 
0356     const gamepads = navigator.getGamepads();
0357     for (const gamepad of gamepads) {
0358       if (gamepad) {
0359         // Example: Using left joystick to control OrbitControls
0360         // Axis 0: Left joystick horizontal (left/right)
0361         // Axis 1: Left joystick vertical (up/down)
0362         const xAxis = gamepad.axes[0];
0363         const yAxis = gamepad.axes[1];
0364 
0365         if (Math.abs(xAxis) > 0.1 || Math.abs(yAxis) > 0.1) {
0366           this.rotateCamera(xAxis, yAxis);
0367         }
0368 
0369         // Zooming using buttons
0370         const zoomInButton = gamepad.buttons[2];
0371         const zoomOutButton = gamepad.buttons[0];
0372 
0373         if (zoomInButton.pressed) {
0374           this.zoom(0.99);
0375         }
0376 
0377         if (zoomOutButton.pressed) {
0378           this.zoom(1.01);
0379         }
0380 
0381         break; // Only use the first connected gamepad
0382       }
0383     }
0384   };
0385 
0386   updateProjectionMatrix() {
0387     let camera = this.threeFacade.mainCamera;
0388     camera.updateProjectionMatrix();
0389   }
0390 
0391   /**
0392    * Receives an object containing one event and builds the different collections
0393    * of physics objects.
0394    * @param eventData Object containing the event data.
0395    */
0396   public buildEventDataFromJSON(eventData: any) {
0397 
0398     console.time("[buildEventDataFromJSON] BUILD EVENT");
0399     let openEventDisplay = (this.eventDisplay as any);
0400 
0401     // Reset labels
0402     openEventDisplay.resetLabels();
0403     // Creating UI folder
0404     openEventDisplay.ui.addEventDataFolder();
0405     openEventDisplay.ui.addLabelsFolder();
0406     // Clearing existing event data
0407     openEventDisplay.graphicsLibrary.clearEventData();
0408     // Build data and add to scene
0409     openEventDisplay.configuration.eventDataLoader.buildEventData(
0410       eventData,
0411       openEventDisplay.graphicsLibrary,
0412       openEventDisplay.ui,
0413       openEventDisplay.infoLogger,
0414     );
0415     console.timeEnd("[buildEventDataFromJSON] BUILD EVENT");
0416     console.time("[buildEventDataFromJSON] onDisplayedEventChange");
0417     openEventDisplay.onDisplayedEventChange.forEach((callback:any) => {
0418       return callback(eventData);
0419     });
0420     console.timeEnd("[buildEventDataFromJSON] onDisplayedEventChange");
0421 
0422 
0423     // Reload the event data state in Phoenix menu
0424     // openEventDisplay.ui.loadEventFolderPhoenixMenuState();
0425   }
0426 
0427 
0428 
0429 
0430   ngOnInit() {
0431     let eventSource = this.settings.trajectoryEventSource.value;
0432     eventSource = this.urlService.resolveDownloadUrl(eventSource);
0433     let eventConfig = {eventFile: "https://firebird-eic.org/py8_all_dis-cc_beam-5x41_minq2-100_nevt-5.evt.json.zip", eventType: "zip"};
0434     if( eventSource != "no-events" && !eventSource.endsWith("edm4hep.json")) {
0435       let eventType = eventSource.endsWith("zip") ? "zip" : "json";
0436       let eventFile = eventSource;
0437       eventConfig = {eventFile, eventType};
0438     }
0439 
0440     if (typeof Worker !== 'undefined') {
0441       // Create a new
0442       const worker = new Worker(new URL('../../workers/event-loader.worker.ts', import.meta.url));
0443       worker.onmessage = ({ data }) => {
0444         console.log(`Result from worker: ${data}`);
0445         console.log(data);
0446 
0447         for(let key in data) {
0448           this.eventsByName.set(key, data[key]);
0449           this.eventsArray.push(data[key]);
0450         }
0451         //this.buildEventDataFromJSON(this.eventsArray[0]);
0452         this.eventDisplay.parsePhoenixEvents(data);
0453         console.log(this);
0454         console.log(`Loading event: `);
0455         console.log(data);
0456       };
0457       worker.postMessage(eventConfig.eventFile);
0458     } else {
0459       // Web workers are not supported in this environment.
0460     }
0461 
0462     // Create the event display configuration
0463     const configuration: Configuration = {
0464       eventDataLoader: new PhoenixLoader(),
0465       presetViews: [
0466         // simple preset views, looking at point 0,0,0 and with no clipping
0467         new PresetView('Left View', [0, 0, -12000], [0, 0, 0], 'left-cube'),
0468         new PresetView('Center View', [-500, 12000, 0], [0, 0, 0], 'top-cube'),
0469         new PresetView('Perspective + clip', [-8000, 8000, -3000], [0, 0, 0], 'top-cube', ClippingSetting.On, 45, 120),
0470         // more fancy view, looking at point 0,0,5000 and with some clipping
0471         new PresetView('Perspective2 + clip', [-4500, 8000, -6000], [0, 0, -5000], 'right-cube', ClippingSetting.On, 90, 90)
0472       ],
0473       // default view with x, y, z of the camera and then x, y, z of the point it looks at
0474       defaultView: [-2500, 0, -8000, 0, 0 ,0],
0475 
0476       phoenixMenuRoot: this.phoenixMenuRoot,
0477       // Event data to load by default
0478       // defaultEventFile: eventConfig
0479       // defaultEventFile: {
0480       //   // (Assuming the file exists in the `src/assets` directory of the app)
0481       //   //eventFile: 'assets/herwig_18x275_5evt.json',
0482       //   //eventFile: 'assets/events/py8_all_dis-cc_beam-18x275_minq2-1000_nevt-20.evt.json',
0483       //   //eventFile: 'assets/events/py8_dis-cc_mixed.json.zip',
0484       //   eventFile: 'https://firebird-eic.org/py8_all_dis-cc_beam-5x41_minq2-100_nevt-5.evt.json.zip',
0485       //   eventType: 'zip'   // or zip
0486       // },
0487     }
0488 
0489     // Initialize the event display
0490     this.eventDisplay.init(configuration);
0491 
0492 
0493     this.controller.buttonB.onPress.subscribe(value => {
0494       this.onControllerBPressed(value);
0495     });
0496 
0497     this.controller.buttonRT.onPress.subscribe(value => {
0498       this.onControllerRTPressed(value);
0499     });
0500 
0501     this.controller.buttonLT.onPress.subscribe(value => {
0502       this.onControllerLTPressed(value);
0503     });
0504 
0505     this.controller.buttonY.onPress.subscribe(value => {
0506       this.onControllerYPressed(value);
0507     });
0508 
0509     // let uiManager = this.eventDisplay.getUIManager();
0510     let openThreeManager: any = this.eventDisplay.getThreeManager();
0511     let threeManager = this.eventDisplay.getThreeManager();
0512 
0513     // Replace animation manager with EIC animation manager:
0514 
0515     // Animations manager (!) DANGER ZONE (!) But we have to, right?
0516     // Deadline approaches and meteor will erase the humanity if we are not in time...
0517     openThreeManager.animationsManager = this.animationManager = new EicAnimationsManager(
0518       openThreeManager.sceneManager.getScene(),
0519       openThreeManager.controlsManager.getActiveCamera(),
0520       openThreeManager.rendererManager,
0521     );
0522 
0523 
0524     this.renderer  = openThreeManager.rendererManager.getMainRenderer();
0525     this.scene = threeManager.getSceneManager().getScene() as THREE.Scene;
0526     this.camera = openThreeManager.controlsManager.getMainCamera() as THREE.Camera;
0527 
0528     this.painter.setThreeSceneParent(openThreeManager.sceneManager.getEventData());
0529 
0530     // // GUI
0531     // const globalPlane = new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0.1 );
0532     //
0533     // const gui = new GUI({
0534     //   container: document.getElementById("lil-gui-place") ?? undefined,
0535     // });
0536 
0537     // gui.title("Dev Controls");
0538     // gui.add(this, "produceRenderOrder");
0539     // gui.add(this, "logGamepadStates").name( 'Log controls' );
0540     // gui.add(this, "logCamera").name( 'Log camera' );
0541     // gui.add(this, "updateProjectionMatrix").name( 'Try to screw up the camera =)' );
0542     // gui.close();
0543 
0544     // Set default clipping
0545     this.eventDisplay.getUIManager().setClipping(true);
0546     this.eventDisplay.getUIManager().rotateOpeningAngleClipping(180);
0547     this.eventDisplay.getUIManager().rotateStartAngleClipping(90);
0548 
0549     this.eventDisplay.listenToDisplayedEventChange(event => {
0550       this.updateSceneTreeComponent();
0551       console.log("listenToDisplayedEventChange");
0552       console.log(event);
0553       this.trackInfos = null;
0554 
0555       let mcTracksGroup = threeManager.getSceneManager().getObjectByName("mc_tracks");
0556       if(mcTracksGroup) {
0557         console.time("Process tracks on event load");
0558         this.trackInfos = this.threeEventProcessor.processMcTracks(mcTracksGroup);
0559 
0560         let minTime = Infinity;
0561         let maxTime = 0;
0562         for(let trackInfo of this.trackInfos) {
0563           if(trackInfo.startTime < minTime) minTime = trackInfo.startTime;
0564           if(trackInfo.endTime > maxTime) maxTime = trackInfo.endTime;
0565         }
0566 
0567         this.maxTime = maxTime;
0568         this.minTime = minTime;
0569 
0570         this.message = `Tracks: ${this.trackInfos.length}`;
0571         if(this.trackInfos && this.animateEventAfterLoad) {
0572           for (let trackInfo of this.trackInfos) {
0573             trackInfo.trackNode.visible = false;
0574           }
0575         }
0576         console.timeEnd("Process tracks on event load");
0577 
0578       }
0579       if(this.animateEventAfterLoad) {
0580         this.animateWithCollision();
0581       }
0582     })
0583 
0584     // Display event loader
0585     this.eventDisplay.getLoadingManager().addLoadListenerWithCheck(() => {
0586       console.log('Loading default configuration.');
0587       this.loaded = true;
0588     });
0589 
0590     this.eventDisplay
0591       .getLoadingManager().toLoad.push("MyGeometry");
0592 
0593 
0594     this.eventDisplay
0595       .getLoadingManager()
0596       .addProgressListener((progress) => (this.loadingProgress = progress));
0597 
0598     this.stats = (this.eventDisplay.getUIManager() as any).stats;
0599 
0600 
0601     threeManager.setAnimationLoop(()=>{this.handleGamepadInputV1()});
0602 
0603 
0604     this.loadGeometry().then(jsonGeom => {
0605 
0606       this.updateSceneTreeComponent();
0607 
0608       this.eventDisplay.getLoadingManager().itemLoaded("MyGeometry");
0609     }).catch(reason=> {
0610       console.error("ERROR LOADING GEOMETRY");
0611       console.log(reason);
0612     });
0613 
0614     this.dataService.loadEdm4EicData().then(data => {
0615       this.updateSceneTreeComponent();
0616       console.log("loadEdm4EicData data:");
0617       console.log(data);
0618       if(data == null) {
0619         console.warn("DataService.loadEdm4EicData() Received data is null or undefined");
0620         return;
0621       }
0622 
0623       if(data.entries?.length ?? 0 > 0) {
0624         this.painter.setThreeSceneParent(openThreeManager.sceneManager.getEventData());
0625         this.painter.setEntry(data.entries[0]);
0626         this.painter.paint(this.currentTime);
0627         this.updateSceneTreeComponent();
0628       } else {
0629         console.warn("DataService.loadEdm4EicData() Received data had no entries");
0630         console.log(data);
0631       }
0632     })
0633     //
0634     this.dataService.loadDexData().then(data => {
0635       this.updateSceneTreeComponent();
0636       if(data == null) {
0637         console.warn("DataService.loadDexData() Received data is null or undefined");
0638         return;
0639       }
0640 
0641       if(data.entries?.length ?? 0 > 0) {
0642         this.painter.setThreeSceneParent(openThreeManager.sceneManager.getEventData());
0643         this.painter.setEntry(data.entries[0]);
0644         this.painter.paint(this.currentTime);
0645         this.updateSceneTreeComponent();
0646       } else {
0647         console.warn("DataService.loadDexData() Received data had no entries");
0648         console.log(data);
0649       }
0650 
0651       //console.log("loaded data model");
0652       //console.log(data);
0653     });
0654 
0655     document.addEventListener('keydown', (e) => {
0656       if ((e as KeyboardEvent).key === 'Enter') {
0657         // do something..
0658       }
0659       if ((e as KeyboardEvent).key === 'q') {
0660         this.nextRandomEvent();
0661       }
0662       if ((e as KeyboardEvent).key === 'r') {
0663         this.logRendererInfo();
0664       }
0665       console.log((e as KeyboardEvent).key);
0666 
0667     });
0668 
0669 
0670   }
0671 
0672 
0673   ngAfterViewInit(): void {
0674 
0675     // When sidebar is collapsed/opened, the main container, i.e. #eventDisplay offsetWidth is not yet updated.
0676     // This leads to a not proper resize  processing. We add 100ms delay before calling a function
0677     const this_obj = this
0678     const resizeInvoker = function(){
0679       setTimeout(() => {
0680         this_obj.onRendererElementResize();
0681       }, 100);  // 100 milliseconds = 0.1 seconds
0682     };
0683 
0684     this.displayShellComponent.onVisibilityChangeLeft.subscribe(resizeInvoker);
0685     this.displayShellComponent.onVisibilityChangeRight.subscribe(resizeInvoker);
0686 
0687     // This works good without 100mv delay
0688     this.displayShellComponent.onEndResizeLeft.subscribe(()=> {this.onRendererElementResize();})
0689     this.displayShellComponent.onEndResizeRight.subscribe(()=> {this.onRendererElementResize();})
0690 
0691     window.addEventListener('resize', () => {
0692       this.onRendererElementResize();
0693     });
0694   }
0695 
0696   private onRendererElementResize() {
0697     const renderer = this.threeFacade.mainRenderer;
0698     const camera = this.threeFacade.mainCamera;
0699     const rendererElement = renderer.domElement;
0700     if(rendererElement == null) {
0701       return;
0702     }
0703 
0704     // This is the element in which Three.js canvas is located
0705     const outerElement = document.getElementById('eventDisplay');
0706     if(outerElement == null) {
0707       return;
0708     }
0709 
0710     if(this.displayShellComponent == null) {
0711       return;
0712     }
0713 
0714     // Calculate adjusted dimensions
0715     let headerHeight =  0;
0716     const footerHeight =  0; // TODO?
0717     const sidePanelWidth = this.displayShellComponent.leftPaneWidth;
0718 
0719     // We use padding to
0720     let element = document.getElementById('eventDisplay');
0721     if(element) {
0722       let computedStyle = window.getComputedStyle(element);
0723       headerHeight = parseFloat(computedStyle.paddingTop) ?? 0;
0724     }
0725 
0726     const adjustedWidth = outerElement.offsetWidth;
0727     const adjustedHeight = outerElement.offsetHeight - headerHeight - footerHeight;
0728     console.log(`[RendererResize] New size: ${adjustedWidth}x${adjustedHeight} px`)
0729 
0730     // Update renderer size
0731     renderer.setSize(adjustedWidth, adjustedHeight);
0732 
0733     if (camera.isOrthographicCamera) {
0734       camera.left = adjustedWidth / -2;
0735       camera.right = adjustedWidth / 2;
0736       camera.top = adjustedHeight / 2;
0737       camera.bottom = adjustedHeight / -2;
0738     } else {
0739       camera.aspect = adjustedWidth / adjustedHeight;
0740     }
0741     camera.updateProjectionMatrix();
0742   }
0743 
0744   private onControllerBPressed(value: boolean) {
0745     if(value) {
0746       this.beamAnimationTime = 1800;
0747       this.nextRandomEvent("5x41");
0748     }
0749   }
0750 
0751   private onControllerRTPressed(value: boolean) {
0752 
0753     if(value) {
0754       this.beamAnimationTime = 1200;
0755       this.nextRandomEvent("10x100");
0756     }
0757   }
0758 
0759   private onControllerLTPressed(value: boolean) {
0760     if(value) {
0761       this.beamAnimationTime = 700;
0762       this.nextRandomEvent("18x275");
0763     }
0764 
0765   }
0766 
0767   private onControllerYPressed(value: boolean) {
0768     if(value) this.cycleGeometry();
0769   }
0770 
0771   changeCurrentTime(event: Event) {
0772     if(!event) return;
0773     const input = event.target as HTMLInputElement;
0774     const value = parseInt(input.value, 10);
0775     this.currentTime = value;
0776 
0777     this.processCurrentTimeChange();
0778 
0779     //this.updateParticlePosition(value);
0780   }
0781   timeStepBack($event: MouseEvent) {
0782     if(this.currentTime > this.minTime) this.currentTime--;
0783     if(this.currentTime < this.minTime) this.currentTime = this.minTime;
0784     this.processCurrentTimeChange();
0785   }
0786 
0787   timeStep($event: MouseEvent) {
0788     if(this.currentTime < this.maxTime) this.currentTime++;
0789     if(this.currentTime > this.maxTime) this.currentTime = this.maxTime;
0790     this.processCurrentTimeChange();
0791   }
0792 
0793   public formatCurrentTime (value: number): string {
0794     return value.toFixed(1);
0795   }
0796 
0797   private processCurrentTimeChange() {
0798     this.painter.paint(this.currentTime);
0799     let partialTracks: ProcessTrackInfo[] = [];
0800     if(this.trackInfos) {
0801       for (let trackInfo of this.trackInfos) {
0802         if(trackInfo.startTime > this.currentTime) {
0803           trackInfo.trackNode.visible = false;
0804         }
0805         else
0806         {
0807           trackInfo.trackNode.visible = true;
0808           trackInfo.newLine.geometry.instanceCount=trackInfo.positions.length;
0809 
0810           if(trackInfo.endTime > this.currentTime) {
0811             partialTracks.push(trackInfo)
0812           }
0813           else {
0814             // track should be visible fully
0815             trackInfo.newLine.geometry.instanceCount=Infinity;
0816           }
0817         }
0818       }
0819     }
0820 
0821 
0822     if(partialTracks.length > 0) {
0823       for(let trackInfo of partialTracks) {
0824         let geometryPosCount = trackInfo.positions.length;
0825 
0826         //if (!geometryPosCount || geometryPosCount < 10) continue;
0827 
0828         let trackProgress = (this.currentTime - trackInfo.startTime)/(trackInfo.endTime-trackInfo.startTime);
0829         let roundedProgress = Math.round(geometryPosCount*trackProgress*2)/2;      // *2/2 to stick to 0.5 rounding
0830 
0831         //(trackInfo.newLine.geometry as InstancedBufferGeometry). = drawCount;(0, roundedProgress);
0832         trackInfo.newLine.geometry.instanceCount=roundedProgress;
0833       }
0834     }
0835   }
0836 
0837   animateCurrentTime(targetTime: number, duration: number): void {
0838     if(this.tween) {
0839       this.stopAnimation();
0840     }
0841     this.tween = new TWEEN.Tween({ currentTime: this.currentTime })
0842       .to({ currentTime: targetTime }, duration)
0843       .onUpdate((obj) => {
0844         this.currentTime = obj.currentTime;
0845         this.processCurrentTimeChange(); // Assuming this method updates your display
0846       })
0847       //.easing(TWEEN.Easing.Quadratic.In) // This can be changed to other easing functions
0848       .start();
0849 
0850     //this.animate();
0851   }
0852 
0853 
0854   animateTime() {
0855     this.animateCurrentTime(this.maxTime, (this.maxTime-this.currentTime)*200 )
0856 
0857   }
0858 
0859   stopAnimation(): void {
0860     if (this.tween) {
0861       this.tween.stop(); // Stops the tween if it is running
0862       this.tween = null; // Remove reference
0863     }
0864   }
0865 
0866   exitTimedDisplay() {
0867     this.stopAnimation();
0868     this.rewindTime();
0869     this.painter.paint(null);
0870     this.animateEventAfterLoad = false;
0871     if(this.trackInfos) {
0872       for (let trackInfo of this.trackInfos) {
0873         trackInfo.trackNode.visible = true;
0874         trackInfo.newLine.geometry.instanceCount=Infinity;
0875       }
0876     }
0877   }
0878 
0879   rewindTime() {
0880     this.currentTime = 0;
0881   }
0882 
0883   animateWithCollision() {
0884     this.stopAnimation();
0885     this.rewindTime();
0886     if(this.trackInfos) {
0887       for (let trackInfo of this.trackInfos) {
0888         trackInfo.trackNode.visible = false;
0889       }
0890     }
0891     this.animationManager?.collideParticles(this.beamAnimationTime, 30, 5000, new Color(0xAAAAAA),
0892       () => {
0893         this.animateTime();
0894     });
0895   }
0896 
0897   showGeometryGroup(groupName: string) {
0898     if(!this.geomService.subdetectors) return;
0899     for(let detector of this.geomService.subdetectors) {
0900       if(detector.groupName === groupName) {
0901         detector.geometry.visible = true;
0902       } else {
0903         detector.geometry.visible = false;
0904       }
0905     }
0906 
0907   }
0908 
0909   showAllGeometries() {
0910     this.geometryGroupSwitchingIndex = ALL_GROUPS.length
0911     if(!this.geomService.subdetectors) return;
0912     for(let detector of this.geomService.subdetectors) {
0913       detector.geometry.visible = true;
0914     }
0915   }
0916 
0917   cycleGeometry() {
0918     this.geometryGroupSwitchingIndex ++;
0919     if(this.geometryGroupSwitchingIndex > ALL_GROUPS.length) {
0920       this.geometryGroupSwitchingIndex = 0;
0921     }
0922 
0923     if(this.geometryGroupSwitchingIndex === ALL_GROUPS.length) {
0924       this.showAllGeometries();
0925       this.currentGeometry = "All";
0926     } else {
0927       this.currentGeometry = ALL_GROUPS[this.geometryGroupSwitchingIndex];
0928       this.showGeometryGroup(this.currentGeometry);
0929     }
0930   }
0931 
0932   protected nextRandomEvent(energyStr="18x275") {
0933     const name = `event_18x275_minq2_100_${Math.floor(Math.random() * 10)}`
0934     console.log(name); // This will log a random index from 0 to 3
0935 
0936     let eventNames=[];
0937     for(const eventName of this.eventsByName.keys()) {
0938       if(eventName.includes(energyStr)) {
0939         eventNames.push(eventName);
0940       }
0941     }
0942 
0943     if(eventNames.length === 0) {
0944       console.warn(`this.eventsByName that has ${this.eventsByName.size} elements, doesn't have keys with energy: ${energyStr}`);
0945       console.log(this.eventsByName);
0946       return;
0947     }
0948 
0949     let eventIndex = Math.floor(Math.random() * eventNames.length);
0950     if(eventIndex < 0 || eventIndex >= eventNames.length) {
0951       console.warn(`if(eventIndex < 0 || eventIndex >= eventNames.length) eventIndex=${eventIndex}`);
0952     }
0953 
0954     const eventName = eventNames[eventIndex];
0955 
0956     // Handle existing animations
0957     this.stopAnimation();
0958     if(this.trackInfos) {
0959       for (let trackInfo of this.trackInfos) {
0960         trackInfo.trackNode.visible = false;
0961       }   }
0962 
0963     this._snackBar.open(`Showing event: ${eventName}`, 'Dismiss', {
0964       duration: 2000,  // Duration in milliseconds after which the snack-bar will auto dismiss
0965       horizontalPosition: 'right',  // 'start' | 'center' | 'end' | 'left' | 'right'
0966       verticalPosition: 'top',    // 'top' | 'bottom'
0967     });
0968 
0969     this.animateEventAfterLoad = true;
0970 
0971     this.eventDisplay.loadEvent(eventName);
0972 
0973     if(this.trackInfos && this.animateEventAfterLoad) {
0974       for (let trackInfo of this.trackInfos) {
0975         trackInfo.trackNode.visible = false;
0976       }
0977     }
0978 
0979 
0980     //  this.buildEventDataFromJSON(eventIndex);
0981 
0982   }
0983 
0984   onUserSelectedEvent() {
0985 
0986     let event = this.eventsByName.get(this.selectedEventKey ?? "");
0987     if(event === undefined) {
0988       console.warn(`User selected event ${this.selectedEventKey} which is not found in eventsByName collection.
0989       Collection has ${this.eventsByName.size} elements `);
0990       return;
0991     }
0992     console.log(`User selected event ${this.selectedEventKey} `);
0993     this.buildEventDataFromJSON(event);
0994   }
0995 
0996   private updateSceneTreeComponent() {
0997     // Name scene lights
0998     if (this.scene) {
0999       if (this.scene.children.length > 2) {
1000         if (this.scene.children[0]) {
1001           this.scene.children[0].name = "Ambient light";
1002         }
1003         if (this.scene.children[1]) {
1004           this.scene.children[1].name = "Direct. light";
1005         }
1006       }
1007     }
1008 
1009     if(this.geometryTreeComponent) {
1010       this.geometryTreeComponent.refreshScheneTree();
1011     }
1012 
1013   }
1014 }