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