Back to home page

EIC code displayed by LXR

 
 

    


Warning, /firebird/firebird-ng/src/app/phoenix-overload/eic-animation-manager.ts is written in an unsupported language. File is not indexed.

0001 import { Easing, Tween } from '@tweenjs/tween.js';
0002 import {
0003   TubeGeometry,
0004   BufferGeometry,
0005   Vector3,
0006   Color,
0007   MeshBasicMaterial,
0008   Mesh,
0009   SphereGeometry,
0010   Sphere,
0011   Object3D,
0012   BufferAttribute,
0013   Scene,
0014   Camera,
0015   Plane,
0016   Group,
0017 } from 'three';
0018 import {RendererManager, SceneManager} from "phoenix-event-display";
0019 import {TracksMesh} from "phoenix-event-display/dist/loaders/objects/tracks";
0020 
0021 
0022 /** Type for animation preset. */
0023 export interface AnimationPreset {
0024   /** Positions with duration and easing of each tween forming a path. */
0025   positions: { position: number[]; duration: number; easing?: any }[];
0026   /** Time after which to start the event collision animation. */
0027   animateEventAfterInterval?: number;
0028   /** Duration of the event collision. */
0029   collisionDuration?: number;
0030   /** Name of the Animation */
0031   name: string;
0032 }
0033 
0034 /**
0035  * Manager for managing animation related operations using three.js and tween.js.
0036  */
0037 export class EicAnimationsManager {
0038   /**
0039    * Constructor for the animation manager.
0040    * @param scene Three.js scene containing all the objects and event data.
0041    * @param activeCamera Currently active camera.
0042    * @param rendererManager Manager for managing event display's renderer related functions.
0043    */
0044   constructor(
0045     private scene: Scene,
0046     private activeCamera: Camera,
0047     private rendererManager: RendererManager,
0048   ) {
0049     this.animateEvent = this.animateEvent.bind(this);
0050     this.animateEventWithClipping = this.animateEventWithClipping.bind(this);
0051   }
0052 
0053   /**
0054    * Get the camera tween for animating camera to a position.
0055    * @param pos End position of the camera tween.
0056    * @param duration Duration of the tween.
0057    * @param easing Animation easing of the tween if any.
0058    * @returns Tween object of the camera animation.
0059    */
0060   public getCameraTween(
0061     pos: number[],
0062     duration: number = 1000,
0063     easing?: typeof Easing.Linear.None,
0064   ) {
0065     const tween = new Tween(this.activeCamera.position).to(
0066       { x: pos[0], y: pos[1], z: pos[2] },
0067       duration,
0068     );
0069 
0070     if (easing) {
0071       tween.easing(easing);
0072     }
0073 
0074     return tween;
0075   }
0076 
0077   /**
0078    * Animate the camera through the event scene.
0079    * @param startPos Start position of the translation animation.
0080    * @param tweenDuration Duration of each tween in the translation animation.
0081    * @param onAnimationEnd Callback when the last animation ends.
0082    */
0083   public animateThroughEvent(
0084     startPos: number[],
0085     tweenDuration: number,
0086     onAnimationEnd?: () => void,
0087   ) {
0088     // Move to start
0089     const start = this.getCameraTween(startPos, 1000, Easing.Cubic.Out);
0090     // Move to position along the detector axis
0091     const alongAxisPosition = [0, 0, startPos[2]];
0092     const startXAxis = this.getCameraTween(alongAxisPosition, tweenDuration);
0093 
0094     const radius = 500;
0095     const numOfSteps = 24;
0096     const angle = 3 * Math.PI;
0097     const step = angle / numOfSteps;
0098 
0099     const rotationPositions = [];
0100     for (let i = 1; i <= numOfSteps; i++) {
0101       rotationPositions.push([
0102         radius * Math.sin(step * i), // x
0103         0, // y
0104         radius * Math.cos(step * i), // z
0105       ]);
0106     }
0107 
0108     // Go to origin
0109     const rotateStart = this.getCameraTween(
0110       [0, 0, radius],
0111       tweenDuration,
0112       Easing.Cubic.Out,
0113     );
0114 
0115     let rotate = rotateStart;
0116     const rotationTime = tweenDuration * 4;
0117     const singleRotationTime = rotationTime / numOfSteps;
0118     // Rotating around the event
0119     for (const pos of rotationPositions) {
0120       const animation = this.getCameraTween(pos, singleRotationTime);
0121       rotate.chain(animation);
0122       rotate = animation;
0123     }
0124 
0125     // Go to the end position and then back to the starting point
0126     const endPos = [0, 0, -startPos[2]];
0127     const end = this.getCameraTween(endPos, tweenDuration, Easing.Cubic.In);
0128     const startClone = this.getCameraTween(
0129       startPos,
0130       tweenDuration,
0131       Easing.Cubic.Out,
0132     );
0133     startClone.onComplete(() => onAnimationEnd?.());
0134     startClone.delay(500);
0135 
0136     start.chain(startXAxis);
0137     startXAxis.chain(rotateStart);
0138     rotate.chain(end);
0139     end.chain(startClone);
0140 
0141     start.start();
0142   }
0143 
0144   /**
0145    * Animate the propagation and generation of event data.
0146    * @param tweenDuration Duration of the animation tween.
0147    * @param onEnd Callback when all animations have ended.
0148    * @param onAnimationStart Callback when the first animation starts.
0149    */
0150   public animateEvent(
0151     tweenDuration: number,
0152     onEnd?: () => void,
0153     onAnimationStart?: () => void,
0154   ) {
0155     const extraAnimationSphereDuration = tweenDuration * 0.25;
0156     tweenDuration *= 0.75;
0157 
0158     const eventData = this.scene.getObjectByName(SceneManager.EVENT_DATA_ID);
0159     if(!eventData) {
0160       console.error("this.scene.getObjectByName(SceneManager.EVENT_DATA_ID) returned null or undefined");
0161       return;
0162     }
0163 
0164     const animationSphere = new Sphere(new Vector3(), 0);
0165     const objectsToAnimateWithSphere: {
0166       eventObject: Object3D;
0167       position: any;
0168     }[] = [];
0169 
0170     const allTweens = [];
0171     // Traverse over all event data
0172     eventData.traverse((eventObject: any) => {
0173       if (eventObject.geometry) {
0174         // Animation for extrapolating tracks without changing scale
0175         if (eventObject.name === 'Track' || eventObject.name === 'LineHit') {
0176           // Check if geometry drawRange count exists
0177           let geometryPosCount =
0178             eventObject.geometry?.attributes?.position?.count;
0179           if (geometryPosCount) {
0180             // WORKAROUND
0181             // Changing position count for TubeGeometry because
0182             // what we get is not the actual and it has Infinity drawRange count
0183             if (eventObject.geometry instanceof TubeGeometry) {
0184               geometryPosCount *= 6;
0185             }
0186 
0187             if (eventObject.geometry instanceof TracksMesh) {
0188               eventObject.material.progress = 0;
0189               const eventObjectTween = new Tween(eventObject.material).to(
0190                 {
0191                   progress: 1,
0192                 },
0193                 tweenDuration,
0194               );
0195               eventObjectTween.onComplete(() => {
0196                 eventObject.material.progress = 1;
0197               });
0198               allTweens.push(eventObjectTween);
0199             } else if (eventObject.geometry instanceof BufferGeometry) {
0200               const oldDrawRangeCount = eventObject.geometry.drawRange.count;
0201               eventObject.geometry.setDrawRange(0, 0);
0202               const eventObjectTween = new Tween(
0203                 eventObject.geometry.drawRange,
0204               ).to(
0205                 {
0206                   count: geometryPosCount,
0207                 },
0208                 tweenDuration,
0209               );
0210               eventObjectTween.onComplete(() => {
0211                 eventObject.geometry.drawRange.count = oldDrawRangeCount;
0212               });
0213               allTweens.push(eventObjectTween);
0214             }
0215           }
0216         }
0217 
0218       }
0219     });
0220 
0221     // Tween for the animation sphere
0222     const animationSphereTween = new Tween(animationSphere).to(
0223       { radius: 3000 },
0224       tweenDuration,
0225     );
0226 
0227     const onAnimationSphereUpdate = (updateAnimationSphere: Sphere) => {
0228       // objectsToAnimateWithSphere.forEach((obj) => {
0229       //   if (obj.eventObject.name === 'Hit') {
0230       //     const geometry = (obj.eventObject as any).geometry;
0231       //
0232       //     const hitsPositions = this.getHitsPositions(obj.position);
0233       //     const reachedHits = hitsPositions.filter((hitPosition) =>
0234       //       updateAnimationSphere.containsPoint(
0235       //         new Vector3().fromArray(hitPosition),
0236       //       ),
0237       //     );
0238       //
0239       //     if (reachedHits.length > 0) {
0240       //       geometry.setAttribute(
0241       //         'position',
0242       //         new BufferAttribute(
0243       //           new Float32Array([].concat(...reachedHits)),
0244       //           3,
0245       //         ),
0246       //       );
0247       //       geometry.computeBoundingSphere();
0248       //     }
0249       //   } else if (updateAnimationSphere.containsPoint(obj.position)) {
0250       //     obj.eventObject.visible = true;
0251       //   }
0252       // });
0253     };
0254 
0255     animationSphereTween.onUpdate(onAnimationSphereUpdate);
0256 
0257     // Animation sphere tween after covering the tracks
0258     const animationSphereTweenClone = new Tween(animationSphere).to(
0259       { radius: 10000 },
0260       extraAnimationSphereDuration,
0261     );
0262     animationSphereTweenClone.onUpdate(onAnimationSphereUpdate);
0263 
0264     animationSphereTween.chain(animationSphereTweenClone);
0265 
0266     allTweens.push(animationSphereTween);
0267 
0268     // Call onAnimationStart when the first tween starts
0269     allTweens[0].onStart(() => onAnimationStart?.());
0270 
0271     // Start all tweens
0272     for (const tween of allTweens) {
0273       tween.easing(Easing.Quartic.Out).start();
0274     }
0275 
0276     // Call onEnd when the last tween completes
0277     animationSphereTweenClone.onComplete(() => {
0278       // Restore all remaining event data items
0279       onAnimationSphereUpdate(new Sphere(new Vector3(), Infinity));
0280       onEnd?.();
0281     });
0282   }
0283 
0284   /**
0285    * Animate the propagation and generation of event data using clipping planes.
0286    * @param tweenDuration Duration of the animation tween.
0287    * @param onEnd Function to call when all animations have ended.
0288    * @param onAnimationStart Callback when the first animation starts.
0289    * @param clippingConstant Constant for the clipping planes for distance from the origin.
0290    */
0291   public animateEventWithClipping(
0292     tweenDuration: number,
0293     onEnd?: () => void,
0294     onAnimationStart?: () => void,
0295     clippingConstant: number = 11000,
0296   ) {
0297     const allEventData = this.scene.getObjectByName(SceneManager.EVENT_DATA_ID);
0298     if(!allEventData) {
0299       console.error("this.scene.getObjectByName(SceneManager.EVENT_DATA_ID) returned null or undefined");
0300       return;
0301     }
0302 
0303     // Sphere to get spherical set of clipping planes from
0304     const sphere = new SphereGeometry(1, 8, 8);
0305     // Clipping planes for animation
0306     const animationClipPlanes: Plane[] = [];
0307 
0308     // Get clipping planes from the vertices of sphere
0309     const position = sphere.attributes["position"];
0310     const vertex = new Vector3();
0311     for (let i = 0; i < position.count; i++) {
0312       vertex.fromBufferAttribute(position as BufferAttribute, i);
0313       animationClipPlanes.push(new Plane(vertex.clone(), 0));
0314     }
0315 
0316     // Save the previous clipping setting of the renderer
0317     const prevLocalClipping =
0318       this.rendererManager.getMainRenderer().localClippingEnabled;
0319     if (!prevLocalClipping) {
0320       this.rendererManager.setLocalClippingEnabled(true);
0321     }
0322 
0323     // Apply clipping planes to all the event data objects' material
0324     allEventData.traverse((eventObject: any) => {
0325       if (eventObject.geometry && eventObject.material) {
0326         eventObject.material.clippingPlanes = animationClipPlanes;
0327       }
0328     });
0329 
0330     const allTweens = [];
0331     // Create tweens for the animation clipping planes
0332     for (const animationClipPlane of animationClipPlanes) {
0333       animationClipPlane.constant = 0;
0334       const tween = new Tween(animationClipPlane).to(
0335         { constant: clippingConstant },
0336         tweenDuration,
0337       );
0338       allTweens.push(tween);
0339     }
0340 
0341     allTweens[0].onStart(() => onAnimationStart?.());
0342 
0343     // Start all the tweens
0344     for (const tween of allTweens) {
0345       tween.start();
0346     }
0347 
0348     allTweens[allTweens.length - 1].onComplete(() => {
0349       // Revert local clipping of the renderer
0350       if (!prevLocalClipping) {
0351         this.rendererManager.getMainRenderer().localClippingEnabled =
0352           prevLocalClipping /* false */;
0353       }
0354       // Remove the applied clipping planes from the event data objects
0355       allEventData.traverse((eventObject: any) => {
0356         if (eventObject.geometry && eventObject.material) {
0357           eventObject.material.clippingPlanes = null;
0358         }
0359       });
0360       onEnd?.();
0361     });
0362   }
0363 
0364   /**
0365    * Animate the collision of two particles.
0366    * @param tweenDuration Duration of the particle collision animation tween.
0367    * @param particleSize Size of the particles.
0368    * @param distanceFromOrigin Distance of the particles (along z-axes) from the origin.
0369    * @param particleColor Color of the particles.
0370    * @param onEnd Callback to call when the particle collision ends.
0371    */
0372   public collideParticles(
0373     tweenDuration: number,
0374     particleSize: number = 10,
0375     distanceFromOrigin: number = 5000,
0376     particleColor: Color = new Color(0xffffff),
0377     onEnd?: () => void,
0378   ) {
0379     const electronGeometry = new SphereGeometry(0.5*particleSize, 32, 32);
0380     const electronMaterial = new MeshBasicMaterial({ color: 0x0000FF, transparent: true, opacity: 0});
0381     const electron = new Mesh(electronGeometry, electronMaterial);
0382 
0383     const ionMaterial = new MeshBasicMaterial({ color: 0xFF0000, transparent: true, opacity: 0});
0384     const ionGeometry = new SphereGeometry(2*particleSize, 32, 32);
0385     const ion = new Mesh(ionGeometry, ionMaterial);
0386 
0387     electron.position.setZ(distanceFromOrigin);
0388     ion.position.setZ(-distanceFromOrigin);
0389 
0390     const particles = [electron, ion];
0391 
0392     this.scene.add(...particles);
0393 
0394     const particleTweens = [];
0395 
0396     for (const particle of particles) {
0397       new Tween(particle.material)
0398         .to(
0399           {
0400             opacity: 1,
0401           },
0402           300,
0403         )
0404         .start();
0405 
0406       const particleToOrigin = new Tween(particle.position)
0407         .to(
0408           {
0409             z: 0,
0410           },
0411           tweenDuration,
0412         )
0413         .start();
0414 
0415       particleTweens.push(particleToOrigin);
0416     }
0417 
0418     particleTweens[0].onComplete(() => {
0419       this.scene.remove(...particles);
0420       onEnd?.();
0421     });
0422   }
0423 
0424   /**
0425    * Animate the propagation and generation of event data with particle collison.
0426    * @param animationFunction Animation function to call after collision.
0427    * @param tweenDuration Duration of the animation tween.
0428    * @param onEnd Function to call when all animations have ended.
0429    */
0430   public animateWithCollision(
0431     animationFunction: (
0432       tweenDuration: number,
0433       onEnd?: () => void,
0434       onAnimationStart?: () => void,
0435     ) => void,
0436     tweenDuration: number,
0437     onEnd?: () => void,
0438   ) {
0439     const allEventData = this.scene.getObjectByName(SceneManager.EVENT_DATA_ID);
0440     if(!allEventData) {
0441       console.error("this.scene.getObjectByName(SceneManager.EVENT_DATA_ID) returned null or undefined");
0442       return;
0443     }
0444 
0445     // Hide event data to show particles collision
0446     if (allEventData) {
0447       allEventData.visible = false;
0448     }
0449 
0450     this.collideParticles(1500, 30, 5000, new Color(0xAAAAAA), () => {
0451       animationFunction(tweenDuration, onEnd, () => {
0452         if (allEventData) {
0453           allEventData.visible = true;
0454         }
0455       });
0456     });
0457   }
0458 
0459   /**
0460    * Animate the propagation and generation of event data with particle collison.
0461    * @param tweenDuration Duration of the animation tween.
0462    * @param onEnd Function to call when all animations have ended.
0463    */
0464   public animateEventWithCollision(tweenDuration: number, onEnd?: () => void) {
0465     this.animateWithCollision(this.animateEvent, tweenDuration, onEnd);
0466   }
0467 
0468   /**
0469    * Animate the propagation and generation of event data
0470    * using clipping planes after particle collison.
0471    * @param tweenDuration Duration of the animation tween.
0472    * @param onEnd Function to call when all animations have ended.
0473    */
0474   public animateClippingWithCollision(
0475     tweenDuration: number,
0476     onEnd?: () => void,
0477   ) {
0478     this.animateWithCollision(
0479       this.animateEventWithClipping,
0480       tweenDuration,
0481       onEnd,
0482     );
0483   }
0484 
0485   /**
0486    * Get the positions of hits in a multidimensional array
0487    * from a single dimensional array.
0488    * @param positions Positions of hits in a single dimensional array.
0489    * @returns Positions of hits in a multidimensional array.
0490    */
0491   private getHitsPositions(positions: number[]): number[][] {
0492     const hitsPositions: number[][] = [];
0493     for (let i = 0; i < positions.length; i += 3) {
0494       hitsPositions.push(positions.slice(i, i + 3));
0495     }
0496     return hitsPositions;
0497   }
0498 
0499   /**
0500    * Animate scene by animating camera through the scene and animating event collision.
0501    * @param animationPreset Preset for animation including positions to go through and
0502    * event collision animation options.
0503    * @param onEnd Function to call when the animation ends.
0504    */
0505   public animatePreset(animationPreset: AnimationPreset, onEnd?: () => void) {
0506     const { positions, animateEventAfterInterval, collisionDuration } =
0507       animationPreset;
0508 
0509     let allEventData = this.scene.getObjectByName(SceneManager.EVENT_DATA_ID);
0510     if(!allEventData) {
0511       console.error("this.scene.getObjectByName(SceneManager.EVENT_DATA_ID) returned null or undefined");
0512       return;
0513     }
0514 
0515     if (animateEventAfterInterval && collisionDuration) {
0516       // Will be made visible after collision animation ends.
0517       allEventData.visible = false;
0518       setTimeout(() => {
0519         this.animateEventWithCollision(collisionDuration);
0520       }, animateEventAfterInterval);
0521     }
0522 
0523     const firstTween = this.getCameraTween(
0524       positions[0].position,
0525       positions[0].duration ?? 2000,
0526       positions[0].easing,
0527     );
0528 
0529     let previousTween = firstTween;
0530     positions.slice(1).forEach(({ position, duration, easing }) => {
0531       const tween = this.getCameraTween(position, duration ?? 2000, easing);
0532       previousTween.chain(tween);
0533       previousTween = tween;
0534     });
0535     previousTween.onComplete(onEnd);
0536 
0537     firstTween.start();
0538   }
0539 }