Back to home page

EIC code displayed by LXR

 
 

    


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