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 }