forked from HSF/phoenix
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathevent-display.ts
697 lines (641 loc) · 21.9 KB
/
event-display.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
import { ThreeManager } from './managers/three-manager';
import { UIManager } from './managers/ui-manager';
import { InfoLogger } from './helpers/info-logger';
import { Configuration } from './extras/configuration';
import { StateManager } from './managers/state-manager';
import { LoadingManager } from './managers/loading-manager';
import { URLOptionsManager } from './managers/url-options-manager';
import { ActiveVariable } from './helpers/active-variable';
declare global {
/**
* Window interface for adding objects to the window object.
*/
interface Window {
/** EventDisplay object containing event display related functions. */
EventDisplay: any;
}
}
/**
* Phoenix event display class for managing detector geometries and event data.
*/
export class EventDisplay {
/** Configuration for preset views and event data loader. */
private configuration: Configuration;
/** An object containing event data. */
private eventsData: any;
/** Array containing callbacks to be called when events change. */
private onEventsChange: ((events: any) => void)[] = [];
/** Array containing callbacks to be called when the displayed event changes. */
private onDisplayedEventChange: ((nowDisplayingEvent: any) => void)[] = [];
/** Three manager for three.js operations. */
private graphicsLibrary: ThreeManager;
/** Info logger for storing event display logs. */
private infoLogger: InfoLogger;
/** UI manager for UI menu. */
private ui: UIManager;
/** Loading manager for loadable resources */
private loadingManager: LoadingManager;
/** State manager for managing event display state. */
private stateManager: StateManager;
/**
* Create the Phoenix event display and intitialize all the elements.
* @param configuration Configuration used to customize different aspects.
*/
constructor(configuration?: Configuration) {
this.loadingManager = new LoadingManager();
this.infoLogger = new InfoLogger();
this.graphicsLibrary = new ThreeManager(this.infoLogger);
this.ui = new UIManager(this.graphicsLibrary);
if (configuration) {
this.init(configuration);
}
}
/**
* Initialize all the Phoenix event display elements.
* @param configuration Configuration used to customize different aspects.
*/
public init(configuration: Configuration) {
this.configuration = configuration;
// Initialize the three manager with configuration
this.graphicsLibrary.init(configuration);
// Initialize the UI with configuration
this.ui.init(configuration);
// Set up for the state manager
this.getStateManager().setEventDisplay(this);
// Animate loop
const uiLoop = () => {
this.ui.updateUI();
};
this.graphicsLibrary.setAnimationLoop(uiLoop);
// Process and apply URL options
if (configuration.allowUrlOptions !== false) {
const urlOptionsManager = new URLOptionsManager(this, configuration);
urlOptionsManager.applyOptions();
}
// Allow adding elements through console
this.enableEventDisplayConsole();
// Allow keyboard controls
this.enableKeyboardControls();
}
/**
* Initialize VR.
* @param onSessionEnded Callback when the VR session ends.
*/
public initVR(onSessionEnded?: () => void) {
this.graphicsLibrary.initVRSession(onSessionEnded);
}
/**
* End VR and remove VR settings.
*/
public endVR() {
this.graphicsLibrary.endVRSession();
}
/**
* Receives an object containing all the eventKeys and saves it.
* Then it loads by default the first event.
* @param eventsData Object containing the event data.
* @returns Array of strings containing the keys of the eventsData object.
*/
public parsePhoenixEvents(eventsData: any): string[] {
this.eventsData = eventsData;
const eventKeys = this.configuration.eventDataLoader.getEventsList(
eventsData
);
this.loadEvent(eventKeys[0]);
this.onEventsChange.forEach((callback) => callback(eventKeys));
return eventKeys;
}
/**
* Receives an object containing one event and builds the different collections
* of physics objects.
* @param eventData Object containing the event data.
*/
public buildEventDataFromJSON(eventData: any) {
// Creating UI folder
this.ui.addEventDataFolder();
this.ui.addLabelsFolder();
// Clearing existing event data
this.graphicsLibrary.clearEventData();
// Build data and add to scene
this.configuration.eventDataLoader.buildEventData(
eventData,
this.graphicsLibrary,
this.ui,
this.infoLogger
);
this.onDisplayedEventChange.forEach((callback) => callback(eventData));
// Reload the event data state in Phoenix menu
this.ui.loadEventFolderPMState();
}
/**
* Receives a string representing the key of an event and loads
* the event associated with that key.
* @param eventKey String that represents the event in the eventsData object.
*/
public loadEvent(eventKey: any) {
const event = this.eventsData[eventKey];
if (event) {
this.buildEventDataFromJSON(event);
}
}
/**
* Get the three manager responsible for three.js functions.
* @returns The three.js manager.
*/
public getThreeManager() {
return this.graphicsLibrary;
}
/**
* Get the UI manager responsible for UI related functions.
* @returns The UI manager.
*/
public getUIManager() {
return this.ui;
}
/**
* Get the info logger containing event display logs.
* @returns The info logger instance being used by the event display.
*/
public getInfoLogger() {
return this.infoLogger;
}
/**
* Get the loading manager for managing loadable items.
* @returns The loading manager.
*/
public getLoadingManager() {
return this.loadingManager;
}
/**
* Get the state manager that manages event display's state.
* @returns The state manager.
*/
public getStateManager() {
if (!this.stateManager) {
this.stateManager = new StateManager();
}
return this.stateManager;
}
// **********************
// * LOADING GEOMETRIES *
// **********************
/**
* Loads an OBJ (.obj) geometry from the given filename
* and adds it to the dat.GUI menu.
* @param filename Path to the geometry.
* @param name Name given to the geometry.
* @param color Color to initialize the geometry.
* @param menuNodeName Name of the node in Phoenix menu to add the geometry to. Use > as a separator for specifying the hierarchy for sub-folders.
* @param doubleSided If true, render both sides of the material.
* @param initiallyVisible Whether the geometry is initially visible or not.
* @param setFlat Whether object should be flat-shaded or not.
* @returns Promise for loading the geometry.
*/
public loadOBJGeometry(
filename: string,
name: string,
color: any,
menuNodeName?: string,
doubleSided?: boolean,
initiallyVisible: boolean = true,
setFlat: boolean = true
): Promise<unknown> {
this.loadingManager.addLoadableItem(`obj_geom_${name}`);
this.ui.addGeometry(name, color, menuNodeName, initiallyVisible);
this.infoLogger.add(name, 'Loaded OBJ geometry');
return this.graphicsLibrary.loadOBJGeometry(
filename,
name,
color,
doubleSided,
initiallyVisible,
setFlat
);
}
/**
* Parses and loads an OBJ geometry from the given content
* and adds it to the dat.GUI menu.
* @param content Content of the OBJ geometry.
* @param name Name given to the geometry.
* @param menuNodeName Name of the node in Phoenix menu to add the geometry to. Use > as a separator for specifying the hierarchy for sub-folders.
* @param initiallyVisible Whether the geometry is initially visible or not.
*/
public parseOBJGeometry(
content: string,
name: string,
menuNodeName?: string,
initiallyVisible: boolean = true
) {
this.loadingManager.addLoadableItem(`parse_obj_${name}`);
this.graphicsLibrary.parseOBJGeometry(content, name, initiallyVisible);
this.ui.addGeometry(name, 0x000fff, menuNodeName, initiallyVisible);
}
/**
* Exports scene to OBJ file format.
*/
public exportToOBJ() {
this.graphicsLibrary.exportSceneToOBJ();
this.infoLogger.add('Exported scene to OBJ');
}
/**
* Parse and load an event from the Phoenix file format (.phnx).
* @param input Content containing the JSON with event data
* and other configuration.
* @returns Promise for loading the geometry.
*/
public parsePhoenixDisplay(input: any): Promise<unknown> {
const phoenixScene = JSON.parse(input);
if (phoenixScene.sceneConfiguration && phoenixScene.scene) {
// Creating UI folder
this.ui.addEventDataFolder();
this.ui.addLabelsFolder();
// Clearing existing event data
this.graphicsLibrary.clearEventData();
// Add to scene
this.loadSceneConfiguration(phoenixScene.sceneConfiguration);
this.loadingManager.addLoadableItem(`parse_phnx_${name}`);
return this.graphicsLibrary.parsePhnxScene(phoenixScene.scene);
}
}
/**
* Exports scene as phoenix format, allowing to load it later and recover the saved configuration.
*/
public exportPhoenixDisplay() {
this.graphicsLibrary.exportPhoenixScene();
}
/**
* Parses and loads a geometry in GLTF (.gltf) format.
* @param input Data of the GLTF (.gltf) file.
* @param name Name given to the geometry.
* @returns Promise for loading the geometry.
*/
public parseGLTFGeometry(
input: string | ArrayBuffer,
name: string
): Promise<unknown> {
this.loadingManager.addLoadableItem(`parse_gltf_${name}`);
this.ui.addGeometry(name, undefined);
this.infoLogger.add(name, 'Parsed GLTF geometry');
return this.graphicsLibrary.parseGLTFGeometry(input, name);
}
/**
* Loads a GLTF (.gltf) scene/geometry from the given URL.
* and adds it to the dat.GUI menu.
* @param url URL to the GLTF (.gltf) file.
* @param name Name of the loaded scene/geometry.
* @param menuNodeName Name of the node in Phoenix menu to add the geometry to. Use > as a separator for specifying the hierarchy for sub-folders.
* @param scale Scale of the geometry.
* @param initiallyVisible Whether the geometry is initially visible or not.
* @returns Promise for loading the geometry.
*/
public loadGLTFGeometry(
url: any,
name: string,
menuNodeName?: string,
scale?: number,
initiallyVisible: boolean = true
): Promise<unknown> {
this.loadingManager.addLoadableItem(`gltf_geom_${name}`);
this.ui.addGeometry(name, undefined, menuNodeName, initiallyVisible);
this.infoLogger.add(name, 'Loaded GLTF geometry');
return this.graphicsLibrary.loadGLTFGeometry(
url,
name,
scale,
initiallyVisible
);
}
/**
* Loads geometries from JSON.
* @param json JSON or URL to JSON file of the geometry.
* @param name Name of the geometry or group of geometries.
* @param menuNodeName Name of the node in Phoenix menu to add the geometry to. Use > as a separator for specifying the hierarchy for sub-folders.
* @param scale Scale of the geometry.
* @param doubleSided Renders both sides of the material.
* @param initiallyVisible Whether the geometry is initially visible or not.
* @returns Promise for loading the geometry.
*/
public loadJSONGeometry(
json: string | object,
name: string,
menuNodeName?: string,
scale?: number,
doubleSided?: boolean,
initiallyVisible: boolean = true
): Promise<unknown> {
this.loadingManager.addLoadableItem(`json_geom_${name}`);
this.ui.addGeometry(name, undefined, menuNodeName, initiallyVisible);
this.infoLogger.add(name, 'Loaded JSON geometry');
return this.graphicsLibrary.loadJSONGeometry(
json,
name,
scale,
doubleSided,
initiallyVisible
);
}
/**
* Load JSON geometry from JSRoot.
* @param JSROOT JSRoot object containing all the JSROOT functions.
* @param url URL of the JSRoot geometry file.
* @param name Name of the geometry.
* @param menuNodeName Name of the node in Phoenix menu to add the geometry to. Use > as a separator for specifying the hierarchy for sub-folders.
* @param scale Scale of the geometry.
* @param doubleSided Renders both sides of the material.
* @param initiallyVisible Whether the geometry is initially visible or not.
*/
public loadRootJSONGeometry(
JSROOT: any,
url: string,
name: string,
menuNodeName?: string,
scale?: number,
doubleSided?: boolean,
initiallyVisible: boolean = true
) {
this.loadingManager.addLoadableItem('root_json_geom');
JSROOT.NewHttpRequest(url, 'object', (obj: any) => {
this.loadJSONGeometry(
JSROOT.GEO.build(obj, { dflt_colors: true }).toJSON(),
name,
menuNodeName,
scale,
doubleSided,
initiallyVisible
);
this.loadingManager.itemLoaded('root_json_geom');
}).send();
}
/**
* Load ROOT geometry from JSRoot.
* @param JSROOT JSRoot object containing all the JSROOT functions.
* @param url URL of the JSRoot file.
* @param objectName Name of the object inside the ".root" file.
* @param name Name of the geometry.
* @param menuNodeName Name of the node in Phoenix menu to add the geometry to. Use > as a separator for specifying the hierarchy for sub-folders.
* @param scale Scale of the geometry.
* @param doubleSided Renders both sides of the material.
* @param initiallyVisible Whether the geometry is initially visible or not.
*/
public loadRootGeometry(
JSROOT: any,
url: string,
objectName: string,
name: string,
menuNodeName?: string,
scale?: number,
doubleSided?: boolean,
initiallyVisible: boolean = true
) {
if (url.indexOf('.root') > 0) {
JSROOT.openFile(url).then((file: any) => {
file.readObject(objectName).then((obj: any) => {
this.loadJSONGeometry(
JSROOT.GEO.build(obj, { dflt_colors: true }).toJSON(),
name,
menuNodeName,
scale,
doubleSided,
initiallyVisible
);
});
});
}
}
/**
* Build Geometry from thr passed parameters, where
* @param parameters
*/
public buildGeometryFromParameters(parameters: any): void {
this.graphicsLibrary.addGeometryFromParameters(parameters);
}
/**
* Zoom all the cameras by a specific zoom factor.
* The factor may either be greater (zoom in) or smaller (zoom out) than 1.
* @param zoomFactor The factor to zoom by.
* @param zoomTime The time it takes for a zoom animation to complete.
*/
public zoomTo(zoomFactor: number, zoomTime: number) {
this.graphicsLibrary.zoomTo(zoomFactor, zoomTime);
}
/**
* Processes event data and geometry for Loading the scene
* from Phoenix file format (.phnx).
* @param sceneConfiguration Scene configuration containingevent data and detector geometry.
*/
private loadSceneConfiguration(sceneConfiguration: {
eventData: {};
geometries: [];
}) {
for (const objectType of Object.keys(sceneConfiguration.eventData)) {
const { typeFolder, typeFolderPM } = this.ui.addEventDataTypeFolder(
objectType
);
const collections = sceneConfiguration.eventData[objectType];
for (const collection of collections) {
this.ui.addCollection({ typeFolder, typeFolderPM }, collection);
}
}
for (const geom of sceneConfiguration.geometries) {
this.ui.addGeometry(geom, '#ffffff');
}
}
/**
* Get all the objects inside a collection.
* @param collectionName Key of the collection that will be retrieved.
* @returns Object containing all physics objects from the desired collection.
*/
public getCollection(collectionName: string) {
return this.configuration.eventDataLoader.getCollection(collectionName);
}
/**
* Get the different collections for the current stored event.
* @returns List of strings, each representing a collection of the event displayed.
*/
public getCollections(): string[] {
return this.configuration.eventDataLoader.getCollections();
}
/**
* Add a callback to onDisplayedEventChange array to call
* the callback on changes to the displayed event.
* @param callback Callback to be added to the onDisplayedEventChange array.
*/
public listenToDisplayedEventChange(callback: (event) => any) {
this.onDisplayedEventChange.push(callback);
}
/**
* Add a callback to onEventsChange array to call
* the callback on changes to the events.
* @param callback Callback to be added to the onEventsChange array.
*/
public listenToLoadedEventsChange(callback: (events) => any) {
this.onEventsChange.push(callback);
}
/**
* Get metadata associated to the displayed event (experiment info, time, run, event...).
* @returns Metadata of the displayed event.
*/
public getEventMetadata(): any[] {
return this.configuration.eventDataLoader.getEventMetadata();
}
/**
* Enables calling specified event display methods in console.
*/
private enableEventDisplayConsole() {
// Defining an EventDisplay object in window to access methods through console
window.EventDisplay = {
loadGLTFGeometry: (sceneUrl: string, name: string) => {
this.loadGLTFGeometry(sceneUrl, name);
},
loadOBJGeometry: (
filename: string,
name: string,
colour: any,
menuNodeName: string,
doubleSided: boolean
) => {
this.loadOBJGeometry(filename, name, colour, menuNodeName, doubleSided);
},
loadJSONGeometry: (
json: string | object,
name: string,
menuNodeName: string,
scale?: number,
doubleSided?: boolean,
initiallyVisible: boolean = true
) => {
this.loadJSONGeometry(
json,
name,
menuNodeName,
scale,
doubleSided,
initiallyVisible
);
},
buildGeometryFromParameters: (parameters: object) =>
this.buildGeometryFromParameters(parameters),
};
}
/**
* Sets the renderer to be used to render the event display on the overlayed canvas.
* @param overlayCanvas An HTML canvas on which the overlay renderer is to be set.
*/
public setOverlayRenderer(overlayCanvas: HTMLCanvasElement) {
this.graphicsLibrary.setOverlayRenderer(overlayCanvas);
}
/**
* Initializes the object which will show information of the selected geometry/event data.
* @param selectedObject Object to display the data.
*/
public allowSelection(selectedObject: { name: string; attributes: any[] }) {
this.graphicsLibrary.setSelectedObjectDisplay(selectedObject);
}
/**
* Toggles the ability of selecting geometries/event data by clicking on the screen.
* @param enable Value to enable or disable the functionality.
*/
public enableSelecting(enable: boolean) {
this.graphicsLibrary.enableSelecting(enable);
}
/**
* Fixes the camera position of the overlay view.
* @param fixed Whether the overlay view is to be fixed or not.
*/
public fixOverlayView(fixed: boolean) {
this.graphicsLibrary.fixOverlayView(fixed);
}
/**
* Get the uuid of the currently selected object.
* @returns uuid of the currently selected object.
*/
public getActiveObjectId(): ActiveVariable<string> {
return this.graphicsLibrary.getActiveObjectId();
}
/**
* Move the camera to look at the object with the given uuid
* and highlight it.
* @param uuid uuid of the object.
*/
public lookAtObject(uuid: string) {
this.graphicsLibrary.lookAtObject(uuid);
this.graphicsLibrary.highlightObject(uuid);
}
/**
* Highlight the object with the given uuid by giving it an outline.
* @param uuid uuid of the object.
*/
public highlightObject(uuid: string) {
this.graphicsLibrary.highlightObject(uuid);
}
/**
* Enable keyboard controls for the event display.
*/
public enableKeyboardControls() {
this.ui.enableKeyboardControls();
this.graphicsLibrary.enableKeyboardControls();
}
/**
* Animate the camera through the event scene.
* @param startPos Start position of the translation animation.
* @param tweenDuration Duration of each tween in the translation animation.
* @param onAnimationEnd Callback when the last animation ends.
*/
public animateThroughEvent(
startPos: number[],
tweenDuration: number,
onAnimationEnd?: () => void
) {
this.graphicsLibrary.animateThroughEvent(
startPos,
tweenDuration,
onAnimationEnd
);
}
/**
* Animate the propagation and generation of event data with particle collison.
* @param tweenDuration Duration of the animation tween.
* @param onEnd Function to call when all animations have ended.
*/
public animateEventWithCollision(tweenDuration: number, onEnd?: () => void) {
this.graphicsLibrary.animateEventWithCollision(tweenDuration, onEnd);
}
/**
* Animate the propagation and generation of event data
* using clipping planes after particle collison.
* @param tweenDuration Duration of the animation tween.
* @param onEnd Function to call when all animations have ended.
*/
public animateClippingWithCollision(
tweenDuration: number,
onEnd?: () => void
) {
this.graphicsLibrary.animateClippingWithCollision(tweenDuration, onEnd);
}
/**
* Add label to a 3D object.
* @param label Label to add to the event object.
* @param collection Collection the event object is a part of.
* @param indexInCollection Event object's index in collection.
* @param uuid UUID of the three.js object.
*/
public addLabelToObject(
label: string,
collection: string,
indexInCollection: number,
uuid: string
) {
const labelId = this.configuration.eventDataLoader.addLabelToEventObject(
label,
collection,
indexInCollection
);
// Remove the label if the string is empty
if (!label) {
this.ui.removeLabel(labelId, true);
return;
}
this.ui.addLabel(labelId);
this.graphicsLibrary.addLabelToObject(label, uuid, labelId);
}
}