import { Camera, Observer, Vector3 } from "@babylonjs/core";
import { Mesh, Vector3 as Vector3Cabri, SolidParticleSystem, SolidParticle, Observer as ObserverCabri, Camera as CameraCabri } from "babylon4.1";
import { AppUtils } from "../app-utils";
import { BabylonGabaritIntegration } from "./babylon-gabarit-integration";
import { CabriIntegration } from "./cabri-integration";
declare var BABYLON;
export enum ParticleAnimName {
explosion,
basket
}
export class BabylonJsConfetti {
cabri: CabriIntegration | BabylonGabaritIntegration;
mesh: Mesh;
position: Vector3Cabri | Vector3;
amountParticles: number;
// gravity = -0.03;
currentSPS: SolidParticleSystem;
observer: Observer<any> | ObserverCabri<any>;
animationDuration: number;
animationName: ParticleAnimName;
cameraType: Camera | CameraCabri;
/**
* https://doc.babylonjs.com/features/featuresDeepDive/particles/solid_particle_system/sps_intro
*
* @param cabri CabriIntegration
* @param startPosition Vectors of starting position
* @param amountParticles amount of particles into the scene
* @param animationDuration duration in miliseconds
*/
constructor(
cabri: CabriIntegration | BabylonGabaritIntegration,
amountParticles: number,
animationDuration: number,
animationName: ParticleAnimName,
startPosition?: Vector3 | Vector3Cabri
) {
this.cabri = cabri;
if (startPosition) {
this.position = startPosition;
} else {
this.position = new Vector3(0, 0, 0);
}
if (this.cabri.scene.activeCameras) {
const sceneHudCameraExist = this.cabri.scene.activeCameras.some(camera => {
return camera.name?.toLowerCase().startsWith("hud");
});
this.cameraType = sceneHudCameraExist ? this.cabri.cameraHud : this.cabri.camera;
} else {
// security
this.cameraType = this.cabri.cameraHud;
}
this.animationName = animationName;
this.animationDuration = animationDuration;
this.amountParticles = amountParticles;
this.currentSPS = this.init();
this.currentSPS.setParticles();
}
private init() {
const SPS = new BABYLON.SolidParticleSystem("SPS", this.cabri.scene) as SolidParticleSystem;
const tetra = BABYLON.MeshBuilder.CreatePlane("tetra", { size: 0.16 });
SPS.addShape(tetra, this.amountParticles);
tetra.dispose();
this.mesh = SPS.buildMesh();
this.mesh.layerMask = this.cameraType.layerMask;
SPS.initParticles = () => {
for (let p = 0; p < SPS.nbParticles; p++) {
this.recycleParticle(SPS.particles[p]);
}
};
SPS.initParticles();
return SPS;
}
/**
* Start animation
*/
public async runParticles() {
let gravity: number;
this.currentSPS.updateParticle = particle => {
// gravity = ;
gravity = this.animationName === ParticleAnimName.basket ? -0.005 : -0.001;
if (this.cameraType.position.z > 0) {
particle.velocity.z += gravity;
} else {
particle.velocity.y += gravity;
}
particle.position.addInPlace(particle.velocity); // update particle new position
particle.rotation.addInPlace(particle.velocity);
return particle;
};
this.observer = this.cabri.scene.onAfterRenderObservable.add(async () => {
this.currentSPS.setParticles();
});
await AppUtils.timeOut(this.animationDuration);
const ease = new BABYLON.CubicEase();
ease.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
const fade = BABYLON.Animation.CreateAndStartAnimation("fm1", this.mesh, "visibility", 60, 60, this.mesh.visibility, 0, 0, ease);
fade.onAnimationEnd = () => {
if (this.cabri.scene) {
// verify if scene existe maybe we already leave the page at the end of the activity
this.cabri.scene.onAfterRenderObservable.remove(this.observer as any);
}
this.currentSPS.dispose();
};
fade.disposeOnEnd = true;
}
/**
* Particles during the animation
*/
private recycleParticle(particle: SolidParticle) {
if (this.animationName === ParticleAnimName.explosion) {
this.explosionAnim(particle);
} else if (this.animationName === ParticleAnimName.basket) {
this.basketAnim(particle);
}
}
/**
* Confetti explosion anim
*
* @param particle SolidParticle
*/
explosionAnim(particle: SolidParticle) {
const speed = 0.4;
particle.position.x = this.position.x;
particle.position.y = this.position.y;
particle.position.z = this.position.z;
particle.rotation.x = BABYLON.Scalar.RandomRange(-Math.PI, Math.PI);
particle.rotation.y = BABYLON.Scalar.RandomRange(-Math.PI, Math.PI);
particle.rotation.z = BABYLON.Scalar.RandomRange(-Math.PI, Math.PI);
const hexColorsAccepted = ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a", "#02FEFF", "#6D2EF1"];
particle.color = BABYLON.Color3.FromHexString(hexColorsAccepted[Math.floor(Math.random() * hexColorsAccepted.length)]);
particle.velocity.x = BABYLON.Scalar.RandomRange(-0.3 * speed, 0.3 * speed);
if (this.cameraType.position.z > 0) {
particle.velocity.z = BABYLON.Scalar.RandomRange(0.01, 0.12);
} else {
particle.velocity.y = BABYLON.Scalar.RandomRange(0.01, 0.12);
}
}
/**
* Confetti basketball anim
*
* @param particle SolidParticle
*/
basketAnim(particle: SolidParticle) {
const speed = 0.3;
particle.position.x = this.position.x;
particle.position.y = this.position.y;
particle.position.z = this.position.z;
particle.rotation.x = BABYLON.Scalar.RandomRange(-Math.PI, Math.PI);
particle.rotation.y = BABYLON.Scalar.RandomRange(-Math.PI, Math.PI);
particle.rotation.z = BABYLON.Scalar.RandomRange(-Math.PI, Math.PI);
particle.color = new BABYLON.Color3(Math.random(), Math.random(), Math.random());
particle.velocity.x = BABYLON.Scalar.RandomRange(-0.5 * speed, 0.5 * speed);
particle.velocity.y = BABYLON.Scalar.RandomRange(0.25 * speed, speed - 0.07);
particle.velocity.z = BABYLON.Scalar.RandomRange(-0.5 * speed, 0.5 * speed);
}
}