Atmospheric Tornado
Severe mesocyclone tornadic vortex simulation modeling hyperbolic funnel cone boundary expansion, Rankine tangential velocity shear, and particle suction updraft.
60 FPS • Canvas 2D
Click + Drag to interact with field
</>
Full Executable Algorithm Code
129 lines
4748 chars
// 039 - Atmospheric Tornado (fluid)
// 1:1 Original algorithm engine source
function createAtmosphericTornado() {
const PARTICLE_COUNT = 1200;
const px = new Float32Array(PARTICLE_COUNT);
const py = new Float32Array(PARTICLE_COUNT);
const pz = new Float32Array(PARTICLE_COUNT);
const pAngle = new Float32Array(PARTICLE_COUNT);
const pRadius = new Float32Array(PARTICLE_COUNT);
const pSpeed = new Float32Array(PARTICLE_COUNT);
return {
setup() {
for (let i = 0; i < PARTICLE_COUNT; i++) {
pz[i] = Math.random();
pAngle[i] = Math.random() * Math.PI * 2;
pRadius[i] = 0.2 + Math.random() * 0.8;
pSpeed[i] = 1.5 + Math.random() * 2.5;
}
},
render(context, timeState, params) {
const { ctx, width, height } = context;
const swirlSpeed = Number(params.swirlSpeed || 1.4);
const funnelWidth = Number(params.funnelWidth || 1);
const dt = Math.min(timeState.deltaTime, 0.05) * swirlSpeed;
const t = timeState.time * swirlSpeed;
ctx.fillStyle = "rgba(5, 7, 12, 0.22)";
ctx.fillRect(0, 0, width, height);
const groundX = width * 0.5 + Math.sin(t * 0.6) * (width * 0.06);
const groundY = height * 0.92;
const cloudY = height * 0.08;
const totalH = groundY - cloudY;
ctx.beginPath();
const funnelSlices = 40;
for (let s = 0; s <= funnelSlices; s++) {
const normZ = s / funnelSlices;
const curY = groundY - normZ * totalH;
const curR = (18 + Math.pow(normZ, 2.2) * 160) * funnelWidth;
const sway = Math.sin(t * 1.5 + normZ * 3) * (40 * normZ);
const curX = groundX + sway;
const leftX = curX - curR;
if (s === 0) ctx.moveTo(leftX, curY);
else ctx.lineTo(leftX, curY);
}
for (let s = funnelSlices; s >= 0; s--) {
const normZ = s / funnelSlices;
const curY = groundY - normZ * totalH;
const curR = (18 + Math.pow(normZ, 2.2) * 160) * funnelWidth;
const sway = Math.sin(t * 1.5 + normZ * 3) * (40 * normZ);
const curX = groundX + sway;
const rightX = curX + curR;
ctx.lineTo(rightX, curY);
}
ctx.closePath();
ctx.fillStyle = "rgba(14, 25, 45, 0.2)";
ctx.fill();
ctx.strokeStyle = "rgba(56, 189, 248, 0.3)";
ctx.lineWidth = 1.2;
ctx.stroke();
for (let i = 0; i < PARTICLE_COUNT; i++) {
pz[i] += dt * (0.35 + pz[i] * 0.4);
if (pz[i] > 1) {
pz[i] = 0;
pAngle[i] = Math.random() * Math.PI * 2;
pRadius[i] = 0.2 + Math.random() * 0.8;
}
const rNorm = pRadius[i];
const angularVel = pSpeed[i] / (0.3 + rNorm * 0.7) * 4;
pAngle[i] += angularVel * dt;
const funnelR = (20 + Math.pow(pz[i], 2.2) * 170) * funnelWidth;
const radius = funnelR * rNorm;
const sway = Math.sin(t * 1.5 + pz[i] * 3) * (40 * pz[i]);
const centerX = groundX + sway;
const posY = groundY - pz[i] * totalH;
const posX = centerX + Math.cos(pAngle[i]) * radius;
const depth = Math.sin(pAngle[i]);
const pyDepth = posY + depth * (radius * 0.2);
px[i] = posX;
py[i] = pyDepth;
const depthAlpha = 0.2 + (depth + 1) * 0.35;
const hue = (195 + pz[i] * 30 + depth * 15) % 360;
const size = (1.2 + (depth + 1) * 0.8) * (1 + (1 - pz[i]) * 0.6);
ctx.fillStyle = hsla(hue, 85, 65 + depth * 15, depthAlpha);
ctx.fillRect(posX - size * 0.5, pyDepth - size * 0.5, size, size);
}
}
};
}
// Default parameters from content metadata
const defaultParams = [
{
"key": "swirlSpeed",
"label": "Vortex Rotation Rate",
"type": "range",
"min": 0.5,
"max": 3,
"step": 0.1,
"defaultValue": 1.4,
"description": "Angular vortex velocity multiplier"
},
{
"key": "funnelWidth",
"label": "Funnel Cone Aperture",
"type": "range",
"min": 0.5,
"max": 2,
"step": 0.1,
"defaultValue": 1,
"description": "Hyperbolic cone boundary scaling"
}
];
if (!window.__art_instances) window.__art_instances = {};
if (!window.__art_instances['atmospheric-tornado']) {
const inst = typeof createAtmosphericTornado === 'function' ? createAtmosphericTornado() : null;
if (inst && inst.setup) {
inst.setup({ ctx, width, height, dpr: 1, aspectRatio: width / height }, defaultParams);
}
window.__art_instances['atmospheric-tornado'] = inst;
}
const instance = window.__art_instances['atmospheric-tornado'];
if (instance && instance.render) {
instance.render(
{ ctx, width, height, dpr: 1, aspectRatio: width / height },
{ time, deltaTime: dt, frameCount: Math.floor(time * 60), fps: 60 },
defaultParams
);
} Edit in Interactive Playground
Zero Dependencies • Standalone Canvas 2D
ƒ
Mathematical Formulation
high
Analytical Equation
vθ(r)={ωr2πrΓr≤Rcr>Rc,R(z)=R0+αz2.2,vz(z)=v0(1+βz)
Click to expand
∑
Atmospheric Tornado
Full Mathematical System • fluid
100%
Complete System of Equations
[Governing Law][Discrete Progression][Domain & Space][Parameter State]vθ(r)={ωr2πrΓr≤Rcr>Rc,R(z)=R0+αz2.2,vz(z)=v0(1+βz)vtheta=speed/(0.3+r⋅0.7),Rfunnel=20+z2.2⋅170,sway=sin(1.5t+3z)⋅40zx∈R2,t∈R+,ω∈[0,2π]λswirlSpeed=1.4(Vortex Rotation Rate),λfunnelWidth=1(Funnel Cone Aperture)
vθ(r)={ωr2πrΓr≤Rcr>Rc,R(z)=R0+αz2.2,vz(z)=v0(1+βz)
Computational Implementation (JavaScript Engine Equivalent)
v_theta = speed / (0.3 + r*0.7), R_funnel = 20 + z^2.2 * 170, sway = sin(1.5t + 3z)*40z Compact Formula
v_theta = speed / (0.3 + r*0.7), R_funnel = 20 + z^2.2 * 170, sway = sin(1.5t + 3z)*40z Mathematical Tags
#tornado
#vortex
#rankine
#fluid
#meteorology
#atmosphere
#weather
Author: Math Art Core Target: 60 FPS
Press ESC or F to exit