chaos theory

LORENZ ATTRACTOR

Real-time 3D visualization of deterministic chaos.

Parameters

10.00
28.00
2.67
8000
1.0x
Lorenz Equations
dx/dt = σ(y − x) dy/dt = x(ρ − z) − y dz/dt = xy − βz
Live Stats
x: 0.0000
y: 0.0000
z: 0.0000
Status: chaotic
Iterations: 0
Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

// ===== STATE =====
    const state = {
      sigma: 10,
      rho: 28,
      beta: 8/3,
      trailLength: 8000,
      speed: 1,
      paused: false,
      iteration: 0,
      x: 0.1,
      y: 0,
      z: 0,
    };

    // ===== PARAMETERS & CONTROLS =====
    const sigmaSlider = document.getElementById('sigma-slider');
    const rhoSlider = document.getElementById('rho-slider');
    const betaSlider = document.getElementById('beta-slider');
    const trailSlider = document.getElementById('trail-slider');
    const speedSlider = document.getElementById('speed-slider');

    sigmaSlider.addEventListener('input', (e) => {
      state.sigma = parseFloat(e.target.value);
      document.getElementById('sigma-value').textContent = state.sigma.toFixed(2);
    });

    rhoSlider.addEventListener('input', (e) => {
      state.rho = parseFloat(e.target.value);
      document.getElementById('rho-value').textContent = state.rho.toFixed(2);
      updateStatusIndicator();
    });

    betaSlider.addEventListener('input', (e) => {
      state.beta = parseFloat(e.target.value);
      document.getElementById('beta-value').textContent = state.beta.toFixed(2);
    });

    trailSlider.addEventListener('input', (e) => {
      state.trailLength = parseInt(e.target.value);
      document.getElementById('trail-value').textContent = state.trailLength;
    });

    speedSlider.addEventListener('input', (e) => {
      state.speed = parseFloat(e.target.value);
      document.getElementById('speed-value').textContent = state.speed.toFixed(1) + 'x';
    });

    // ===== BUTTONS =====
    document.getElementById('reset-btn').addEventListener('click', () => {
      state.x = 0.1;
      state.y = 0;
      state.z = 0;
      state.iteration = 0;
      trailBuffer = [];
      updateStats();
    });

    document.getElementById('pause-btn').addEventListener('click', (e) => {
      state.paused = !state.paused;
      e.target.textContent = state.paused ? 'Resume' : 'Pause';
    });

    document.getElementById('random-btn').addEventListener('click', () => {
      state.x = (Math.random() - 0.5) * 10;
      state.y = (Math.random() - 0.5) * 10;
      state.z = Math.random() * 40;
      state.iteration = 0;
      trailBuffer = [];
      updateStats();
    });

    function updateStatusIndicator() {
      const indicator = document.getElementById('status-indicator');
      const text = document.getElementById('status-text');
      if (state.rho > 24.74) {
        indicator.className = 'status-indicator status-chaotic';
        text.textContent = 'chaotic';
      } else {
        indicator.className = 'status-indicator status-stable';
        text.textContent = 'stable';
      }
    }

    function updateStats() {
      document.getElementById('stat-x').textContent = state.x.toFixed(4);
      document.getElementById('stat-y').textContent = state.y.toFixed(4);
      document.getElementById('stat-z').textContent = state.z.toFixed(4);
      document.getElementById('stat-iter').textContent = state.iteration.toLocaleString();
    }

    // ===== THREE.JS SCENE =====
    const canvas = document.getElementById('canvas');
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);

    const camera = new THREE.PerspectiveCamera(
      75,
      canvas.clientWidth / canvas.clientHeight,
      0.1,
      1000
    );
    camera.position.set(30, 30, 40);
    camera.lookAt(0, 0, 20);

    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
    renderer.setSize(canvas.clientWidth, canvas.clientHeight);
    renderer.setPixelRatio(window.devicePixelRatio);

    // Add subtle lighting
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
    scene.add(ambientLight);

    // Trail buffer (circular)
    let trailBuffer = [];
    let trailGeometry = null;
    let trailLine = null;
    let glowLine = null;

    function createTrailGeometry() {
      if (trailLine) scene.remove(trailLine);
      if (glowLine) scene.remove(glowLine);

      const positions = new Float32Array(state.trailLength * 3);
      trailGeometry = new THREE.BufferGeometry();
      trailGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      trailGeometry.setDrawRange(0, 0);

      // Main trail with gradient
      const trailMaterial = new THREE.LineBasicMaterial({
        color: 0xff2200,
        linewidth: 1,
        vertexColors: true,
      });

      trailLine = new THREE.Line(trailGeometry, trailMaterial);
      scene.add(trailLine);

      // Glow effect (thicker, semi-transparent)
      const glowGeometry = new THREE.BufferGeometry();
      glowGeometry.setAttribute('position', new THREE.BufferAttribute(positions.slice(), 3));
      const glowMaterial = new THREE.LineBasicMaterial({
        color: 0xff2200,
        linewidth: 3,
        transparent: true,
        opacity: 0.15,
        vertexColors: false,
      });
      glowLine = new THREE.Line(glowGeometry, glowMaterial);
      scene.add(glowLine);
    }

    createTrailGeometry();

    // ===== RUNGE-KUTTA 4 INTEGRATION =====
    function lorenzDerivative(x, y, z) {
      const dx = state.sigma * (y - x);
      const dy = x * (state.rho - z) - y;
      const dz = x * y - state.beta * z;
      return { dx, dy, dz };
    }

    function rk4Step(x, y, z, dt) {
      // k1
      let d1 = lorenzDerivative(x, y, z);
      // k2
      let d2 = lorenzDerivative(x + 0.5*dt*d1.dx, y + 0.5*dt*d1.dy, z + 0.5*dt*d1.dz);
      // k3
      let d3 = lorenzDerivative(x + 0.5*dt*d2.dx, y + 0.5*dt*d2.dy, z + 0.5*dt*d2.dz);
      // k4
      let d4 = lorenzDerivative(x + dt*d3.dx, y + dt*d3.dy, z + dt*d3.dz);

      const newX = x + (dt/6) * (d1.dx + 2*d2.dx + 2*d3.dx + d4.dx);
      const newY = y + (dt/6) * (d1.dy + 2*d2.dy + 2*d3.dy + d4.dy);
      const newZ = z + (dt/6) * (d1.dz + 2*d2.dz + 2*d3.dz + d4.dz);

      return { x: newX, y: newY, z: newZ };
    }

    // ===== ANIMATION LOOP =====
    let mouseDown = false;
    let mouseX = 0, mouseY = 0;
    let cameraAzimuth = 0.927; // radians
    let cameraElevation = 0.785; // radians
    const cameraDistance = 50;

    canvas.addEventListener('mousedown', (e) => {
      mouseDown = true;
      mouseX = e.clientX;
      mouseY = e.clientY;
    });

    canvas.addEventListener('mousemove', (e) => {
      if (mouseDown) {
        const dx = e.clientX - mouseX;
        const dy = e.clientY - mouseY;
        cameraAzimuth += dx * 0.005;
        cameraElevation += dy * 0.005;
        cameraElevation = Math.max(-Math.PI/2 + 0.1, Math.min(Math.PI/2 - 0.1, cameraElevation));
        mouseX = e.clientX;
        mouseY = e.clientY;
      }
    });

    canvas.addEventListener('mouseup', () => {
      mouseDown = false;
    });

    canvas.addEventListener('mouseleave', () => {
      mouseDown = false;
    });

    function animate() {
      requestAnimationFrame(animate);

      // Update simulation
      if (!state.paused) {
        const dt = 0.005;
        for (let i = 0; i < state.speed; i++) {
          const result = rk4Step(state.x, state.y, state.z, dt);
          state.x = result.x;
          state.y = result.y;
          state.z = result.z;
          state.iteration++;

          // Add to trail
          trailBuffer.push([state.x, state.y, state.z]);
          if (trailBuffer.length > state.trailLength) {
            trailBuffer.shift();
          }
        }
      }

      // Update trail geometry
      const positions = trailGeometry.attributes.position.array;
      for (let i = 0; i < trailBuffer.length; i++) {
        positions[i*3] = trailBuffer[i][0];
        positions[i*3 + 1] = trailBuffer[i][1];
        positions[i*3 + 2] = trailBuffer[i][2];
      }
      trailGeometry.attributes.position.needsUpdate = true;
      trailGeometry.setDrawRange(0, trailBuffer.length);

      // Update colors with gradient (oldest = dark, newest = bright)
      if (!trailGeometry.attributes.color) {
        const colors = new Float32Array(state.trailLength * 3);
        trailGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
      }

      const colors = trailGeometry.attributes.color.array;
      for (let i = 0; i < trailBuffer.length; i++) {
        const t = i / Math.max(1, trailBuffer.length - 1); // 0 to 1
        // Gradient: red (#ff2200) -> orange -> yellow
        let r, g, b;
        if (t < 0.5) {
          // Red to orange
          const lerp = t * 2;
          r = 1.0;
          g = lerp * 0.65; // 0 -> 165/255
          b = 0;
        } else {
          // Orange to yellow
          const lerp = (t - 0.5) * 2;
          r = 1.0;
          g = 0.65 + lerp * 0.35; // 0.65 -> 1.0
          b = 0;
        }
        colors[i*3] = r;
        colors[i*3 + 1] = g;
        colors[i*3 + 2] = b;
      }
      trailGeometry.attributes.color.needsUpdate = true;

      // Update camera position (auto-rotate + mouse orbit)
      if (!mouseDown) {
        cameraAzimuth += 0.0003; // Slow auto-rotate
      }
      const x = cameraDistance * Math.cos(cameraElevation) * Math.cos(cameraAzimuth);
      const y = cameraDistance * Math.sin(cameraElevation);
      const z = cameraDistance * Math.cos(cameraElevation) * Math.sin(cameraAzimuth);
      camera.position.set(x, y, z);
      camera.lookAt(0, 10, 0);

      // Update glow geometry
      if (glowLine) {
        const glowPositions = glowLine.geometry.attributes.position.array;
        for (let i = 0; i < trailBuffer.length; i++) {
          glowPositions[i*3] = trailBuffer[i][0];
          glowPositions[i*3 + 1] = trailBuffer[i][1];
          glowPositions[i*3 + 2] = trailBuffer[i][2];
        }
        glowLine.geometry.attributes.position.needsUpdate = true;
        glowLine.geometry.setDrawRange(0, trailBuffer.length);
      }

      // Update stats
      updateStats();

      renderer.render(scene, camera);
    }

    animate();

    // ===== RESPONSIVE =====
    window.addEventListener('resize', () => {
      const width = canvas.clientWidth;
      const height = canvas.clientHeight;
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      renderer.setSize(width, height);
    });

    updateStatusIndicator();