JSFiddle - React, Tailwind, and code Playground

by jcubed111

HTML

<canvas id="view"></canvas>
<div id="bottomBar">
	<div>
		Dispersion:
		<input id="dispersion" type="range" min=0 max=1 step=0.01 value=0.5 />
	</div>
	<div>
		Velocity:
		<input id="velocity" type="range" min=0.5 max=3 step=0.01 value=1.5 />
	</div>
	<div>
		Spawn Rate:
		<input id="spawnRate" type="range" min=1 max=10 step=1 value=5 />
	</div>
	<div>
		View Angle:
		<label>Top <input type="radio" name="viewAngle" value="0" checked=true></label>
		<label>Side <input type="radio" name="viewAngle" value="1"></label>
	</div>
	<div>Particle Count: <span id="particleCount"></span></div>
</div>

CSS

canvas{
	position: absolute;
	background: #fff; /*#110022;/**/
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
}

#bottomBar{
	position: absolute;
	bottom: 0;
	left: 0;
	right: 0;
	display: flex;
	background: rgba(17, 0, 34, 0.5);
	color: #fa8;
	font-family: sans-serif;
	padding: 5px;
}

#bottomBar *{
	vertical-align: middle;
}

#bottomBar>div{
	padding-right: 30px;
}

label{
	padding-left: 5px;
}

JavaScript

var scale = 100;
var sideView = false;

function clampInt(min, max, n) {
	return ~~Math.max(min, Math.min(max, n));
}

function dispFun(n) {
	return Math.pow(n, 5)*0.8 + n*0.2;
}

function velMul(xF, zF) {
	return (1/Math.pow(Math.abs(xF)+1, 2) + 0.75) * (1/Math.pow(Math.abs(zF)+1, 2) + 0.75);
}

class Particle{
	constructor() {
		this.x = Math.random() * 0.05;
		this.y = Math.random() * 0.05;
		this.z = 0;
		
		const xF = dispFun(Math.random());
		const xAngle = xF * Math.PI*0.5 * document.getElementById("dispersion").value;
		const zF = Math.random()*2;
		const zAngle = zF * Math.PI*0.5;
		
		let vel = Math.random() + 1;
		vel *= (2-xF);
		
		const speedFactor = document.getElementById("velocity").value;
		
		this.vx = speedFactor * vel * -Math.cos(xAngle);
		this.vy = speedFactor * vel * Math.sin(xAngle) * Math.cos(zAngle);
		this.vz = speedFactor * vel * Math.sin(xAngle) * Math.sin(zAngle) + 0.2;
		
		this.heat =  1 + Math.random()*0.25 + vel*0.1;
	}
	
	step(t=1/60) {
		this.x += this.vx * t;
		this.y += this.vy * t;
		this.z += this.vz * t;
		this.vz -= 20 * t * t;
		this.heat -= 0.004 * 60 * t;
		
		if(this.vz < 0 && this.z <= 0) this.vz *= -1;
	}
	
	transform([x, y, z]) {
		if(sideView) {
			return [x, z, y];
		}else{
			return [x, y, z];
		}
	}
	
	transformed() {
		return this.transform([this.x, this.y, this.z]);
	}
	
	transformedVel() {
		return this.transform([this.vx, this.vy, this.vz]);
	}
	
	render(ctx, t=1/60) {
		const [x, y, z] = this.transformed();
		const [vx, vy, vz] = this.transformedVel();
		
		let radius = Math.max(1, Math.sqrt(Math.abs(z)) * 10 - 3);
		let colorFactor = 1.5 / (radius*radius);
		colorFactor = 0.5 + 0.5*colorFactor;
		colorFactor *= this.heat;
		
		let r = clampInt(0, 255, 255 * colorFactor);
		let g = clampInt(0, 255, 180 * colorFactor);
		let b = clampInt(0, 255, 128 * colorFactor);
		
		ctx.beginPath();
		if(-this.vx * t > 6/scale) {
			t*=2;
			// draw as line
			ctx.moveTo(x*scale, y*scale);
			ctx.lineTo((x +...