Warning, /firebird/firebird-ng/src/app/services/event-display.service.ts is written in an unsupported language. File is not indexed.
0001 import {computed, effect, Injectable, linkedSignal, Signal, signal, WritableSignal} from '@angular/core';
0002 import {Group as TweenGroup, Tween} from '@tweenjs/tween.js';
0003 import {ThreeService} from './three.service';
0004 import {GeometryService} from './geometry.service';
0005 import {DataModelService} from './data-model.service';
0006 import {UserConfigService} from './user-config.service';
0007 import {UrlService} from './url.service';
0008
0009
0010 import {disposeHierarchy} from '../utils/three.utils';
0011 import {ThreeEventProcessor} from '../data-pipelines/three-event.processor';
0012 import {DataModelPainter, DisplayMode} from '../painters/data-model-painter';
0013 import {AnimationManager} from "../animation/animation-manager";
0014 import {initComponentFactories} from "../model/default-components-init";
0015
0016
0017 @Injectable({
0018 providedIn: 'root',
0019 })
0020 export class EventDisplayService {
0021
0022 private eventsByName = new Map<string, any>();
0023 private eventsArray: any[] = [];
0024 selectedEventKey: string | undefined;
0025
0026 // Time
0027 //private eventDisplayMode: WritableSignal<DisplayMode> = signal(DisplayMode.Timeless);
0028 public eventTime: WritableSignal<number|null> = signal(0);
0029
0030
0031 public maxTime = 200;
0032 public minTime = 0;
0033
0034 // Time animation
0035 private tweenGroup = new TweenGroup();
0036 private tween: Tween<any> | null = null;
0037 private beamAnimationTime: number = 1000;
0038
0039 // Geometry
0040 private animateEventAfterLoad: boolean = false;
0041 private trackInfos: any | null = null; // Replace 'any' with the actual type
0042
0043 // Painter that draws the event
0044 private painter: DataModelPainter = new DataModelPainter();
0045
0046 // Animation manager
0047 private animationManager: AnimationManager;
0048
0049 /** The last successfully loaded Event Data url. Switches to null on every new load attempt */
0050 public lastLoadedDexUrl:string|null = "";
0051
0052 /** The last successfully loaded Geometry url. Switches to null on every new load attempt */
0053 public lastLoadedGeometryUrl:string|null = "";
0054
0055 constructor(
0056 public three: ThreeService,
0057 private geomService: GeometryService,
0058 private settings: UserConfigService,
0059 private dataService: DataModelService,
0060 private urlService: UrlService
0061 ) {
0062
0063 // Add event model factories (things that decode json to objects)
0064 initComponentFactories();
0065
0066 // Connect painter to its scene place
0067 this.painter.setThreeSceneParent(this.three.sceneEvent);
0068
0069 // Connect animation manager with threejs components
0070 this.animationManager = new AnimationManager(this.three.scene, this.three.camera, this.three.renderer);
0071
0072 // On time change
0073 effect(() => {
0074 console.log("[eventDisplay] Time change effect start")
0075 const time = this.eventTime();
0076 this.painter.paint(time);
0077 console.log("[eventDisplay] Time change effect end")
0078 }, {debugName: "EventDisplayService.OnTimeChange"});
0079
0080 effect(() => {
0081 //this.processCurrentTimeChange(this.eventTime());
0082 const geometry = this.geomService.geometry();
0083 }, {debugName: "EventDisplayService.OnTimeChange"});
0084
0085 // On current entry change
0086 effect(() => {
0087 console.log("[eventDisplay] Entry change effect start")
0088 let event = this.dataService.currentEntry();
0089
0090 // Make sure to clean-up even if event is null
0091 // this.painter.cleanupCurrentEntry();
0092
0093 if(event === null || this.painter.getEntry() == event) return;
0094 this.painter.setEntry(event);
0095 this.painter.paint(null);
0096
0097 console.log("[eventDisplay] Entry change effect end")
0098 }, {debugName: "EventDisplayService.OnEventChange"});
0099 }
0100
0101 // ****************************************************
0102 // *************** THREE SETUP ************************
0103 // ****************************************************
0104
0105 /**
0106 * Initialize the default three.js scene
0107 * @param container
0108 */
0109 initThree(container: string|HTMLElement) {
0110 this.three.init(container);
0111 this.painter.setThreeSceneParent(this.three.sceneEvent);
0112 this.three.startRendering();
0113
0114 // We need this to update the animation group
0115 this.three.addFrameCallback(()=> {
0116 this.tweenGroup.update();
0117 })
0118 }
0119
0120
0121 // ****************************************************
0122 // *************** TIME *******************************
0123 // ****************************************************
0124
0125 public updateEventTime(time: number) {
0126 this.eventTime.set(time);
0127 }
0128
0129 getMaxTime(): number {
0130 return this.maxTime;
0131 }
0132
0133 getMinTime(): number {
0134 return this.minTime;
0135 }
0136
0137 animateTime() {
0138
0139 let time = this.eventTime() ?? this.minTime;
0140 this.animateCurrentTime(
0141 this.maxTime,
0142 (this.maxTime - time) * 200
0143 );
0144 }
0145
0146 stopTimeAnimation(): void {
0147 if (this.tween) {
0148 this.tween.stop(); // Stops the tween if it is running
0149 this.tween = null; // Remove reference
0150 }
0151 }
0152
0153 rewindTime() {
0154 this.updateEventTime(0);
0155 }
0156
0157 animateCurrentTime(targetTime: number, duration: number): void {
0158 if (this.tween) {
0159 this.stopTimeAnimation();
0160 }
0161
0162 this.tween = new Tween({ currentTime: this.eventTime() ?? this.minTime }, this.tweenGroup)
0163 .to({ currentTime: targetTime }, duration)
0164 .onUpdate((obj) => {
0165 console.log(obj.currentTime);
0166 this.eventTime.set(obj.currentTime);
0167 })
0168 // .easing(TWEEN.Easing.Quadratic.In) // This can be changed to other easing functions
0169 .start();
0170 }
0171
0172 animateWithCollision() {
0173 this.stopTimeAnimation();
0174 this.rewindTime();
0175 if (this.trackInfos) {
0176 for (let trackInfo of this.trackInfos) {
0177 trackInfo.trackNode.visible = false;
0178 }
0179 }
0180 // TODO
0181 // this.animationManager?.collideParticles(
0182 // this.beamAnimationTime,
0183 // 30,
0184 // 5000,
0185 // new Color(0xaaaaaa),
0186 // () => {
0187 // this.animateTime();
0188 // }
0189 // );
0190 }
0191
0192 timeStepBack() {
0193 // Check if we need to switch to timed display mode
0194
0195 const time = this.eventTime() ?? this.minTime;
0196
0197 if (time > this.minTime) this.updateEventTime(time - 1);
0198 if (time <= this.minTime) this.updateEventTime(this.minTime);
0199 }
0200
0201 timeStep() {
0202 // Check if we need to switch to timed display mode
0203
0204 const time = this.eventTime();
0205 if(time === null) return;
0206
0207 if (time < this.maxTime) this.updateEventTime(time + 1);
0208 if (time > this.maxTime) this.updateEventTime(this.maxTime);
0209 }
0210
0211 exitTimedDisplay() {
0212
0213 this.stopTimeAnimation();
0214 this.eventTime.set(null);
0215 this.animateEventAfterLoad = false;
0216 if (this.trackInfos) {
0217 for (let trackInfo of this.trackInfos) {
0218 trackInfo.trackNode.visible = true;
0219 trackInfo.newLine.geometry.instanceCount = Infinity;
0220 }
0221 }
0222 }
0223
0224 // ****************************************************
0225 // *************** DATA LOADING ***********************
0226 // ****************************************************
0227
0228 /**
0229 * Load geometry
0230 */
0231 async loadGeometry(url:string, scale = 10, clearGeometry=true) {
0232 this.lastLoadedGeometryUrl = null;
0233 let { rootGeometry, threeGeometry } = await this.geomService.loadGeometry(url);
0234 if (!threeGeometry) return;
0235
0236 // Set geometry scale
0237 if (scale) {
0238 threeGeometry.scale.setScalar(scale);
0239 }
0240
0241 const sceneGeo = this.three.sceneGeometry;
0242
0243 // There should be only one geometry if clearGeometry=true
0244 if(clearGeometry && sceneGeo.children.length > 0) {
0245 disposeHierarchy(sceneGeo, /* disposeSelf= */ false);
0246 }
0247
0248 this.geomService.postProcessing(threeGeometry, this.three.clipPlanes);
0249
0250 sceneGeo.children.push(threeGeometry);
0251 this.lastLoadedGeometryUrl = url;
0252 }
0253
0254 /**
0255 * Load events
0256 * @private
0257 */
0258 private loadEvents() {
0259 let eventSource = this.settings.dexJsonEventSource.value;
0260 eventSource = this.urlService.resolveDownloadUrl(eventSource);
0261 let eventConfig = {
0262 eventFile:
0263 'https://firebird-eic.org/py8_all_dis-cc_beam-5x41_minq2-100_nevt-5.evt.json.zip',
0264 eventType: 'zip',
0265 };
0266 if (
0267 eventSource != 'no-events' &&
0268 !eventSource.endsWith('edm4hep.json')
0269 ) {
0270 let eventType = eventSource.endsWith('zip') ? 'zip' : 'json';
0271 let eventFile = eventSource;
0272 eventConfig = { eventFile, eventType };
0273 }
0274
0275 if (typeof Worker !== 'undefined') {
0276 // Create a new
0277 const worker = new Worker(
0278 new URL('../workers/event-loader.worker.ts', import.meta.url)
0279 );
0280 worker.onmessage = ({ data }) => {
0281 for (let key in data) {
0282 this.eventsByName.set(key, data[key]);
0283 this.eventsArray.push(data[key]);
0284 }
0285 };
0286 worker.postMessage(eventConfig.eventFile);
0287 } else {
0288 // Web workers are not supported in this environment.
0289 }
0290 }
0291
0292 public loadEvent(eventName: string) {
0293 const event = this.eventsByName.get(eventName);
0294 if (event) {
0295 this.buildEventDataFromJSON(event);
0296 }
0297 }
0298
0299
0300 async loadDexData(url:string) {
0301 this.lastLoadedDexUrl = null;
0302 const data = await this.dataService.loadDexData(url);
0303 if (data == null) {
0304 console.warn(
0305 'DataService.loadDexData() Received data is null or undefined'
0306 );
0307 return;
0308 }
0309
0310 if (data.entries?.length ?? 0 > 0) {
0311 this.painter.setEntry(data.entries[0]);
0312 this.painter.paint(this.eventTime());
0313 this.lastLoadedDexUrl = url;
0314 } else {
0315 console.warn('DataService.loadDexData() Received data had no entries');
0316 console.log(data);
0317 }
0318 }
0319
0320 // ****************************************************
0321 // *************** EVENTS *****************************
0322 // ****************************************************
0323
0324 /**
0325 * Process current time change
0326 * @param value
0327 * @private
0328 */
0329 private processCurrentTimeChange(value: number|null) {
0330
0331 }
0332
0333 public buildEventDataFromJSON(eventData: any) {
0334 const threeEventProcessor = new ThreeEventProcessor();
0335
0336 console.time('[buildEventDataFromJSON] BUILD EVENT');
0337
0338 this.three.sceneEvent.clear();
0339
0340 // Use the ThreeService to handle object groups
0341 const eventDataGroup = this.three.sceneEvent;
0342
0343 // Event data collections by type
0344 for (const collectionType in eventData) {
0345 const collectionsOfType = eventData[collectionType];
0346
0347 for (const collectionName in collectionsOfType) {
0348 const collection = collectionsOfType[collectionName];
0349
0350 // // THREE.Group for this collection
0351 // const collectionGroup = new THREE.Group();
0352 // collectionGroup.name = collectionName;
0353 // eventDataGroup.add(collectionGroup);
0354 //
0355 // for (const item of collection) {
0356 // // Object for each item
0357 // const object = threeEventProcessor.makeObject(
0358 // collectionType,
0359 // collectionName,
0360 // item
0361 // );
0362 //
0363 // if (object) {
0364 // collectionGroup.add(object);
0365 // }
0366 // }
0367 }
0368 }
0369
0370 // Post-processing for specific event data types
0371 const mcTracksGroup = eventDataGroup.getObjectByName('mc_tracks');
0372 if (mcTracksGroup) {
0373 this.trackInfos = threeEventProcessor.processMcTracks(mcTracksGroup);
0374
0375 let minTime = Infinity;
0376 let maxTime = 0;
0377 for (const trackInfo of this.trackInfos) {
0378 if (trackInfo.startTime < minTime) minTime = trackInfo.startTime;
0379 if (trackInfo.endTime > maxTime) maxTime = trackInfo.endTime;
0380 }
0381
0382 this.maxTime = maxTime;
0383 this.minTime = minTime;
0384
0385 console.log(`Tracks: ${this.trackInfos.length}`);
0386 if (this.trackInfos && this.animateEventAfterLoad) {
0387 for (const trackInfo of this.trackInfos) {
0388 trackInfo.trackNode.visible = false;
0389 }
0390 }
0391 console.timeEnd('Process tracks on event load');
0392 }
0393
0394 // Update event metadata (not really used for now)
0395 // this.eventMetadata = {
0396 // eventNumber: eventData.eventNumber,
0397 // runNumber: eventData.runNumber,
0398 // startTime: eventData.startTime * 1000, // Convert UNIX time to milliseconds
0399 // };
0400
0401 console.timeEnd('[buildEventDataFromJSON] BUILD EVENT');
0402
0403 if (this.animateEventAfterLoad) {
0404 this.animateWithCollision();
0405 }
0406 }
0407 }