Skip to content

Instantly share code, notes, and snippets.

@sp90
Created December 5, 2024 11:23
Show Gist options
  • Save sp90/3926e53871b9425f4725444168ec99a3 to your computer and use it in GitHub Desktop.
Save sp90/3926e53871b9425f4725444168ec99a3 to your computer and use it in GitHub Desktop.
Angular component for adding fog and dust particles to your images
import { Component, ElementRef, viewChild } from '@angular/core';
import * as THREE from 'three';
const SMOKE_PARTICLE_COUNT = 150;
const SMOKE_SPREAD = 1600;
const SMOKE_OPACITY = 0.5;
const SMOKE_COLOR = 0xcdc8bb;
const SMOKE_ROTATION_SPEED = 0.5;
const SMOKE_SIZE_FACTOR = 300;
const SMOKE_SIZE_VARIATION = 0.1;
const DUST_PARTICLE_COUNT = 1200;
const DUST_SPREAD = 1600;
const DUST_SIZE = 2;
const DUST_COLOR = 0xcdc8bb;
const DUST_MOVEMENT_SPEED = 200;
const DUST_MOVEMENT_LEVEL = 2;
const DUST_VERTICAL_DIRECTION = 1;
const DUST_OPACITY_LEVEL = 0.5;
@Component({
selector: 'app-smoke',
template: `
<canvas #canvas></canvas>
`,
styles: [
`
canvas {
display: block; /* Prevents scrollbar */
}
`,
],
})
export class EffectsComponent {
canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
#renderer!: THREE.WebGLRenderer;
#scene!: THREE.Scene;
#camera!: THREE.PerspectiveCamera;
#smokeParticles: THREE.Mesh[] = [];
#dustParticles: THREE.Points[] = [];
#clock = new THREE.Clock();
ngAfterViewInit(): void {
this.init();
this.animate();
}
private init(): void {
const canvas = this.canvasRef().nativeElement;
this.#renderer = new THREE.WebGLRenderer({ canvas, alpha: true });
this.#renderer.setSize(window.innerWidth, window.innerHeight);
this.#scene = new THREE.Scene();
this.addCamera();
this.addLights();
this.addParticles();
this.addDustParticles();
window.addEventListener('resize', this.onResize.bind(this));
}
private animate(): void {
requestAnimationFrame(this.animate.bind(this));
this.evolveSmoke(this.#clock.getDelta());
this.evolveDust(this.#clock.getDelta());
this.#renderer.render(this.#scene, this.#camera);
}
private addCamera(): void {
this.#camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
1,
10000
);
this.#camera.position.z = 1000;
this.#scene.add(this.#camera);
}
private addLights(): void {
const light = new THREE.DirectionalLight(0xffffff, 0.95);
light.position.set(-1, 0, 1);
this.#scene.add(light);
}
private addParticles(): void {
const textureLoader = new THREE.TextureLoader();
textureLoader.load('/others/clouds.png', (texture: any) => {
let smokeMaterial = new THREE.MeshLambertMaterial({
color: SMOKE_COLOR,
map: texture,
transparent: true,
opacity: SMOKE_OPACITY,
});
(smokeMaterial.map as any).minFilter = THREE.LinearFilter;
for (let i = 0; i < SMOKE_PARTICLE_COUNT; i++) {
const sizeVariation = 1 + Math.random() * SMOKE_SIZE_VARIATION;
const size = SMOKE_SIZE_FACTOR * sizeVariation;
const smokeGeometry = new THREE.PlaneGeometry(size, size);
const smokeMesh = new THREE.Mesh(smokeGeometry, smokeMaterial);
smokeMesh.position.set(
Math.random() * SMOKE_SPREAD - SMOKE_SPREAD / 2,
Math.random() * SMOKE_SPREAD - SMOKE_SPREAD / 2,
Math.random() * 1000 - 100
);
smokeMesh.rotation.z = Math.random() * 360;
this.#smokeParticles.push(smokeMesh);
this.#scene.add(smokeMesh);
}
});
}
private evolveSmoke(delta: number): void {
for (let i = 0; i < this.#smokeParticles.length; i++) {
this.#smokeParticles[i].rotation.z += delta * SMOKE_ROTATION_SPEED;
}
}
private onResize(): void {
this.#camera.aspect = window.innerWidth / window.innerHeight;
this.#camera.updateProjectionMatrix();
this.#renderer.setSize(window.innerWidth, window.innerHeight);
}
// New function to add dust particles:
private addDustParticles(): void {
const dustGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(DUST_PARTICLE_COUNT * 3);
for (let i = 0; i < DUST_PARTICLE_COUNT; i++) {
const i3 = i * 3;
positions[i3] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2;
positions[i3 + 1] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2;
positions[i3 + 2] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2;
}
dustGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const dustMaterial = new THREE.PointsMaterial({
color: DUST_COLOR,
size: DUST_SIZE,
transparent: true,
opacity: DUST_OPACITY_LEVEL,
});
const dustParticles = new THREE.Points(dustGeometry, dustMaterial);
this.#dustParticles.push(dustParticles);
this.#scene.add(dustParticles);
}
// New function to animate dust particles:
private evolveDust(delta: number): void {
const movementSpeed = DUST_MOVEMENT_SPEED * DUST_MOVEMENT_LEVEL;
for (let i = 0; i < this.#dustParticles.length; i++) {
const positions = this.#dustParticles[i].geometry.attributes['position']
.array as Float32Array;
for (let j = 0; j < positions.length; j += 3) {
// Mostly upwards movement with slight randomness
positions[j] += (Math.random() - 0.5) * delta * movementSpeed * 0.1; // Reduced horizontal movement
positions[j + 1] +=
DUST_VERTICAL_DIRECTION * delta * movementSpeed +
(Math.random() - 0.5) * delta * movementSpeed * 0.1; // Mostly vertical
positions[j + 2] += (Math.random() - 0.5) * delta * movementSpeed * 0.1; // Reduced depth movement
// Fade out and reset position if particle goes too high/low
if (positions[j + 1] > DUST_SPREAD / 2 || positions[j + 1] < -DUST_SPREAD / 2) {
positions[j] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2;
positions[j + 1] = -DUST_SPREAD / 2; // Reset at the bottom
positions[j + 2] = Math.random() * DUST_SPREAD - DUST_SPREAD / 2;
}
}
this.#dustParticles[i].geometry.attributes['position'].needsUpdate = true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment