Warning, /firebird/firebird-ng/src/app/animation/default-animations.ts is written in an unsupported language. File is not indexed.
0001 import { Easing, Tween } from '@tweenjs/tween.js';
0002 import {AnimationTask} from "./animation-manager";
0003 import {
0004 BufferAttribute,
0005 BufferGeometry,
0006 Camera,
0007 Color,
0008 Mesh,
0009 MeshBasicMaterial, Plane,
0010 Scene,
0011 Sphere,
0012 SphereGeometry,
0013 TubeGeometry,
0014 Vector3, WebGLRenderer
0015 } from "three";
0016
0017 export class CameraMoveTask implements AnimationTask {
0018 name = 'CameraMove';
0019 duration: number;
0020 private currentTween: Tween<Vector3> | null = null;
0021
0022 constructor(
0023 private camera: Camera,
0024 private targetPosition: Vector3,
0025 duration: number,
0026 private easing?: (amount: number) => number
0027 ) {
0028 this.duration = duration;
0029 }
0030
0031 async start(): Promise<void> {
0032 return new Promise((resolve) => {
0033 this.currentTween = new Tween(this.camera.position)
0034 .to(this.targetPosition, this.duration)
0035 .onComplete(() => {
0036 this.currentTween = null;
0037 resolve();
0038 })
0039 .start();
0040
0041 if (this.easing) {
0042 this.currentTween.easing(this.easing);
0043 }
0044 });
0045 }
0046
0047 stop?(): void {
0048 if (this.currentTween) {
0049 this.currentTween.stop();
0050 this.currentTween = null;
0051 }
0052 }
0053
0054 pause?(): void {
0055 if (this.currentTween) {
0056 this.currentTween.pause();
0057 }
0058 }
0059
0060 resume?(): void {
0061 if (this.currentTween) {
0062 this.currentTween.resume();
0063 }
0064 }
0065 }
0066
0067 // Concrete Animation Task: ParticleCollisionTask
0068 export class ParticleCollisionTask implements AnimationTask {
0069 name = 'ParticleCollision';
0070 duration: number;
0071
0072 constructor(
0073 private scene: Scene,
0074 duration: number,
0075 private particleSize: number = 10,
0076 private distanceFromOrigin: number = 5000,
0077 private particleColor: Color = new Color(0xffffff)
0078 ) {
0079 this.duration = duration;
0080 }
0081
0082 async start(): Promise<void> {
0083 return new Promise((resolve) => {
0084 const electronGeometry = new SphereGeometry(
0085 0.5 * this.particleSize,
0086 32,
0087 32
0088 );
0089 const electronMaterial = new MeshBasicMaterial({
0090 color: 0x0000ff,
0091 transparent: true,
0092 opacity: 0,
0093 });
0094 const electron = new Mesh(electronGeometry, electronMaterial);
0095
0096 const ionMaterial = new MeshBasicMaterial({
0097 color: 0xff0000,
0098 transparent: true,
0099 opacity: 0,
0100 });
0101 const ionGeometry = new SphereGeometry(2 * this.particleSize, 32, 32);
0102 const ion = new Mesh(ionGeometry, ionMaterial);
0103
0104 electron.position.setZ(this.distanceFromOrigin);
0105 ion.position.setZ(-this.distanceFromOrigin);
0106
0107 const particles = [electron, ion];
0108
0109 this.scene.add(...particles);
0110
0111 const particleTweens = [];
0112
0113 for (const particle of particles) {
0114 new Tween(particle.material)
0115 .to(
0116 {
0117 opacity: 1,
0118 },
0119 300
0120 )
0121 .start();
0122
0123 const particleToOrigin = new Tween(particle.position)
0124 .to(
0125 {
0126 z: 0,
0127 },
0128 this.duration
0129 )
0130 .start();
0131
0132 particleTweens.push(particleToOrigin);
0133 }
0134
0135 particleTweens[0].onComplete(() => {
0136 this.scene.remove(...particles);
0137 resolve();
0138 });
0139 });
0140 }
0141 }
0142
0143 // Animation Task: AnimateThroughEventTask
0144 export class AnimateThroughEventTask implements AnimationTask {
0145 name = 'AnimateThroughEvent';
0146 duration: number;
0147 private startPos: number[];
0148 private tweens: Tween<any>[] = [];
0149 private onAnimationEnd?: () => void;
0150
0151 constructor(
0152 private camera: Camera,
0153 startPos: number[],
0154 tweenDuration: number,
0155 onAnimationEnd?: () => void
0156 ) {
0157 this.startPos = startPos;
0158 this.duration = tweenDuration;
0159 this.onAnimationEnd = onAnimationEnd;
0160 }
0161
0162 async start(): Promise<void> {
0163 return new Promise<void>((resolve) => {
0164 // Move to start
0165 const start = this.getCameraTween(this.startPos, 1000, Easing.Cubic.Out);
0166 // Move to position along the detector axis
0167 const alongAxisPosition = [0, 0, this.startPos[2]];
0168 const startXAxis = this.getCameraTween(alongAxisPosition, this.duration);
0169
0170 const radius = 500;
0171 const numOfSteps = 24;
0172 const angle = 3 * Math.PI;
0173 const step = angle / numOfSteps;
0174
0175 const rotationPositions = [];
0176 for (let i = 1; i <= numOfSteps; i++) {
0177 rotationPositions.push([
0178 radius * Math.sin(step * i), // x
0179 0, // y
0180 radius * Math.cos(step * i), // z
0181 ]);
0182 }
0183
0184 // Go to origin
0185 const rotateStart = this.getCameraTween(
0186 [0, 0, radius],
0187 this.duration,
0188 Easing.Cubic.Out
0189 );
0190
0191 let rotate = rotateStart;
0192 const rotationTime = this.duration * 4;
0193 const singleRotationTime = rotationTime / numOfSteps;
0194 // Rotating around the event
0195 for (const pos of rotationPositions) {
0196 const animation = this.getCameraTween(pos, singleRotationTime);
0197 rotate.chain(animation);
0198 rotate = animation;
0199 }
0200
0201 // Go to the end position and then back to the starting point
0202 const endPos = [0, 0, -this.startPos[2]];
0203 const end = this.getCameraTween(endPos, this.duration, Easing.Cubic.In);
0204 const startClone = this.getCameraTween(
0205 this.startPos,
0206 this.duration,
0207 Easing.Cubic.Out
0208 );
0209 startClone.onComplete(() => {
0210 this.onAnimationEnd?.();
0211 resolve();
0212 });
0213 startClone.delay(500);
0214
0215 start.chain(startXAxis);
0216 startXAxis.chain(rotateStart);
0217 rotate.chain(end);
0218 end.chain(startClone);
0219
0220 this.tweens = [start, startXAxis, rotateStart, ...rotate['_chainedTweens'], end, startClone];
0221 start.start();
0222 });
0223 }
0224
0225 stop?(): void {
0226 for (const tween of this.tweens) {
0227 tween.stop();
0228 }
0229 }
0230
0231 pause?(): void {
0232 for (const tween of this.tweens) {
0233 tween.pause();
0234 }
0235 }
0236
0237 resume?(): void {
0238 for (const tween of this.tweens) {
0239 tween.resume();
0240 }
0241 }
0242
0243 private getCameraTween(
0244 pos: number[],
0245 duration: number = 1000,
0246 easing?: typeof Easing.Linear.None
0247 ) {
0248 const tween = new Tween(this.camera.position).to(
0249 { x: pos[0], y: pos[1], z: pos[2] },
0250 duration
0251 );
0252
0253 if (easing) {
0254 tween.easing(easing);
0255 }
0256
0257 return tween;
0258 }
0259 }
0260
0261 // Animation Task: AnimateEventTask
0262 export class AnimateEventTask implements AnimationTask {
0263 name = 'AnimateEvent';
0264 duration: number;
0265 private onEnd?: () => void;
0266 private onAnimationStart?: () => void;
0267 private animationSphere: Sphere;
0268 private animationSphereTween: Tween<Sphere>;
0269 private animationSphereTweenClone: Tween<Sphere>;
0270 private eventObjectTweens: Tween<any>[] = [];
0271
0272 constructor(
0273 private scene: Scene,
0274 private EVENT_DATA_ID: string,
0275 tweenDuration: number,
0276 onEnd?: () => void,
0277 onAnimationStart?: () => void
0278 ) {
0279 this.duration = tweenDuration;
0280 this.onEnd = onEnd;
0281 this.onAnimationStart = onAnimationStart;
0282 this.animationSphere = new Sphere(new Vector3(), 0);
0283 this.animationSphereTween = new Tween(this.animationSphere)
0284 .to({ radius: 3000 }, this.duration * 0.75)
0285 .onUpdate(() => this.onAnimationSphereUpdate()); // Remove extra parameter
0286 this.animationSphereTweenClone = new Tween(this.animationSphere)
0287 .to({ radius: 10000 }, this.duration * 0.25)
0288 .onUpdate(() => this.onAnimationSphereUpdate()); // Remove extra parameter
0289 this.animationSphereTween.chain(this.animationSphereTweenClone);
0290 }
0291
0292 async start(): Promise<void> {
0293 return new Promise<void>((resolve) => {
0294 const eventData = this.scene.getObjectByName(this.EVENT_DATA_ID);
0295 if (!eventData) {
0296 console.error(
0297 'this.scene.getObjectByName(this.EVENT_DATA_ID) returned null or undefined'
0298 );
0299 resolve();
0300 return;
0301 }
0302
0303 // Traverse over all event data
0304 eventData.traverse((eventObject: any) => {
0305 if (eventObject.geometry) {
0306 // Animation for extrapolating tracks without changing scale
0307 if (
0308 eventObject.name === 'Track' ||
0309 eventObject.name === 'LineHit'
0310 ) {
0311 // Check if geometry drawRange count exists
0312 let geometryPosCount =
0313 eventObject.geometry?.attributes?.position?.count;
0314 if (geometryPosCount) {
0315 // WORKAROUND
0316 // Changing position count for TubeGeometry because
0317 // what we get is not the actual and it has Infinity drawRange count
0318 if (eventObject.geometry instanceof TubeGeometry) {
0319 geometryPosCount *= 6;
0320 }
0321
0322 if (eventObject.geometry instanceof BufferGeometry) {
0323 const oldDrawRangeCount = eventObject.geometry.drawRange.count;
0324 eventObject.geometry.setDrawRange(0, 0);
0325 const eventObjectTween = new Tween(
0326 eventObject.geometry.drawRange
0327 )
0328 .to(
0329 {
0330 count: geometryPosCount,
0331 },
0332 this.duration * 0.75
0333 )
0334 .onComplete(() => {
0335 eventObject.geometry.drawRange.count = oldDrawRangeCount;
0336 });
0337 this.eventObjectTweens.push(eventObjectTween);
0338 }
0339 }
0340 }
0341 }
0342 });
0343
0344 this.eventObjectTweens[0]?.onStart(() => this.onAnimationStart?.());
0345 this.animationSphereTweenClone.onComplete(() => {
0346 // Remove this line
0347 // this.onAnimationSphereUpdate(new Sphere(new Vector3(), Infinity));
0348 this.onEnd?.();
0349 resolve();
0350 });
0351
0352 for (const tween of this.eventObjectTweens) {
0353 tween.easing(Easing.Quartic.Out).start();
0354 }
0355 this.animationSphereTween.start();
0356 });
0357 }
0358
0359 private onAnimationSphereUpdate() {
0360 // Now empty. Remove this function if not required.
0361 }
0362
0363 stop?(): void {
0364 this.animationSphereTween.stop();
0365 this.animationSphereTweenClone.stop();
0366 for (const tween of this.eventObjectTweens) {
0367 tween.stop();
0368 }
0369 }
0370
0371 pause?(): void {
0372 this.animationSphereTween.pause();
0373 this.animationSphereTweenClone.pause();
0374 for (const tween of this.eventObjectTweens) {
0375 tween.pause();
0376 }
0377 }
0378
0379 resume?(): void {
0380 this.animationSphereTween.resume();
0381 this.animationSphereTweenClone.resume();
0382 for (const tween of this.eventObjectTweens) {
0383 tween.resume();
0384 }
0385 }
0386 }
0387
0388 // Animation Task: AnimateEventWithClippingTask
0389 export class AnimateEventWithClippingTask implements AnimationTask {
0390 name = 'AnimateEventWithClipping';
0391 duration: number;
0392 private onEnd?: () => void;
0393 private onAnimationStart?: () => void;
0394 private clippingConstant: number;
0395 private animationClipPlanes: Plane[] = [];
0396 private allTweens: Tween<any>[] = [];
0397
0398 constructor(
0399 private scene: Scene,
0400 private renderer: WebGLRenderer,
0401 private EVENT_DATA_ID: string,
0402 tweenDuration: number,
0403 onEnd?: () => void,
0404 onAnimationStart?: () => void,
0405 clippingConstant: number = 11000
0406 ) {
0407 this.duration = tweenDuration;
0408 this.onEnd = onEnd;
0409 this.onAnimationStart = onAnimationStart;
0410 this.clippingConstant = clippingConstant;
0411 }
0412
0413 async start(): Promise<void> {
0414 return new Promise<void>((resolve) => {
0415 const allEventData = this.scene.getObjectByName(this.EVENT_DATA_ID);
0416 if (!allEventData) {
0417 console.error(
0418 'this.scene.getObjectByName(this.EVENT_DATA_ID) returned null or undefined'
0419 );
0420 resolve();
0421 return;
0422 }
0423
0424 const sphere = new SphereGeometry(1, 8, 8);
0425 const position = sphere.attributes['position'];
0426 const vertex = new Vector3();
0427 for (let i = 0; i < position.count; i++) {
0428 vertex.fromBufferAttribute(position as BufferAttribute, i);
0429 this.animationClipPlanes.push(new Plane(vertex.clone(), 0));
0430 }
0431
0432 const prevLocalClipping = this.renderer.localClippingEnabled;
0433 if (!prevLocalClipping) {
0434 this.renderer.localClippingEnabled = true;
0435 }
0436
0437 allEventData.traverse((eventObject: any) => {
0438 if (eventObject.geometry && eventObject.material) {
0439 eventObject.material.clippingPlanes = this.animationClipPlanes;
0440 }
0441 });
0442
0443 for (const animationClipPlane of this.animationClipPlanes) {
0444 animationClipPlane.constant = 0;
0445 const tween = new Tween(animationClipPlane)
0446 .to({ constant: this.clippingConstant }, this.duration)
0447 .onComplete(() => {
0448 if (animationClipPlane === this.animationClipPlanes[this.animationClipPlanes.length - 1]) {
0449 if (!prevLocalClipping) {
0450 this.renderer.localClippingEnabled = false;
0451 }
0452 allEventData.traverse((eventObject: any) => {
0453 if (eventObject.geometry && eventObject.material) {
0454 eventObject.material.clippingPlanes = null;
0455 }
0456 });
0457 this.onEnd?.();
0458 resolve();
0459 }
0460 });
0461 this.allTweens.push(tween);
0462 }
0463
0464 this.allTweens[0].onStart(() => this.onAnimationStart?.());
0465
0466 for (const tween of this.allTweens) {
0467 tween.start();
0468 }
0469 });
0470 }
0471
0472 stop?(): void {
0473 for (const tween of this.allTweens) {
0474 tween.stop();
0475 }
0476 }
0477
0478 pause?(): void {
0479 for (const tween of this.allTweens) {
0480 tween.pause();
0481 }
0482 }
0483
0484 resume?(): void {
0485 for (const tween of this.allTweens) {
0486 tween.resume();
0487 }
0488 }
0489 }
0490
0491 // Animation Sequence Class
0492 export class AnimationSequence {
0493 private tasks: AnimationTask[] = [];
0494 private currentTaskIndex: number = 0;
0495 private isPlaying: boolean = false;
0496 private isPaused: boolean = false;
0497
0498 constructor(public name: string) {}
0499
0500 addTask(task: AnimationTask): AnimationSequence {
0501 this.tasks.push(task);
0502 return this;
0503 }
0504
0505 async run(): Promise<void> {
0506 if (this.isPlaying) {
0507 return; // Already running
0508 }
0509
0510 this.isPlaying = true;
0511 this.isPaused = false;
0512
0513 if (this.currentTaskIndex >= this.tasks.length) {
0514 this.currentTaskIndex = 0; // Reset if we've reached the end
0515 }
0516
0517 for (
0518 let i = this.currentTaskIndex;
0519 i < this.tasks.length && this.isPlaying;
0520 i++
0521 ) {
0522 const task = this.tasks[i];
0523 this.currentTaskIndex = i;
0524 console.log(`Starting animation task: ${task.name}`);
0525
0526 if (this.isPaused) {
0527 console.log(`Animation sequence paused at task: ${task.name}`);
0528 await this.waitForResume();
0529 }
0530
0531 if (!this.isPlaying) {
0532 console.log(
0533 `Animation sequence stopped during task: ${task.name}`
0534 );
0535 break; // Exit loop if stopped
0536 }
0537
0538 await task.start();
0539 console.log(`Finished animation task: ${task.name}`);
0540 }
0541
0542 this.isPlaying = false;
0543 this.isPaused = false;
0544 this.currentTaskIndex = 0;
0545 }
0546
0547 stop(): void {
0548 if (!this.isPlaying) return;
0549
0550 this.isPlaying = false;
0551 this.isPaused = false;
0552 // Stop the currently running task
0553 const currentTask = this.tasks[this.currentTaskIndex];
0554 if (currentTask && currentTask.stop) {
0555 currentTask.stop();
0556 }
0557 }
0558
0559 pause(): void {
0560 if (!this.isPlaying || this.isPaused) return;
0561
0562 this.isPaused = true;
0563 // Pause the currently running task
0564 const currentTask = this.tasks[this.currentTaskIndex];
0565 if (currentTask && currentTask.pause) {
0566 currentTask.pause();
0567 }
0568 }
0569
0570 resume(): void {
0571 if (!this.isPaused) return;
0572
0573 this.isPaused = false;
0574 // Resume the currently paused task
0575 const currentTask = this.tasks[this.currentTaskIndex];
0576 if (currentTask && currentTask.resume) {
0577 currentTask.resume();
0578 }
0579 }
0580
0581 private waitForResume(): Promise<void> {
0582 return new Promise((resolve) => {
0583 const checkResume = () => {
0584 if (!this.isPaused) {
0585 resolve();
0586 } else {
0587 setTimeout(checkResume, 100);
0588 }
0589 };
0590 checkResume();
0591 });
0592 }
0593 }