Skip to main content

Rössler Attractor

Open the Rössler attractor. Drag with the pointer to examine the phase portrait from any angle. Adjust the bifurcation parameter cc from 3 to 12 via the slider, or toggle pause/play to freeze or resume the moving trajectory markers.

The Dynamical System​

Otto Rössler's 1976 paper introduced a simplified continuous system designed to model chaotic behavior with minimal non-linear coupling:

x˙=−y−z,y˙=x+ay,z˙=b+z(x−c).\dot{x} = -y - z,\qquad \dot{y} = x + ay,\qquad \dot{z} = b + z(x - c).

The standard parameters are a=0.2a = 0.2, b=0.2b = 0.2, and c=5.7c = 5.7. In the (x,y)(x, y) plane, the system exhibits rotational oscillations with growing amplitude. When xx exceeds threshold cc, the non-linear term z(x−c)z(x - c) in the z˙\dot{z} equation rapidly ejects the trajectory into the third dimension before folding it back into the spiral plane—producing a classic single folded band in phase space.

Adjusting cc transitions the system across period-doubling bifurcations, revealing distinct chaotic bands and periodic orbits.

Numerical Integration via Runge–Kutta 4 (RK4)​

Simple Euler integration has first-order truncation error and rapidly diverges in chaotic systems, blurring fine phase-space structure. Instead, the implementation integrates the system using a classical fourth-order Runge–Kutta algorithm with fixed step size Δt=0.01\Delta t = 0.01:

function derivative(x, y, z, {a, b, c}) {
return [-y - z, x + a * y, b + z * (x - c)];
}

function rk4Step([x, y, z], parameters) {
const {dt} = parameters;
const k1 = derivative(x, y, z, parameters);
const k2 = derivative(
x + (dt * k1[0]) / 2,
y + (dt * k1[1]) / 2,
z + (dt * k1[2]) / 2,
parameters,
);
const k3 = derivative(
x + (dt * k2[0]) / 2,
y + (dt * k2[1]) / 2,
z + (dt * k2[2]) / 2,
parameters,
);
const k4 = derivative(
x + dt * k3[0],
y + dt * k3[1],
z + dt * k3[2],
parameters,
);

return [
x + (dt * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0])) / 6,
y + (dt * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1])) / 6,
z + (dt * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2])) / 6,
];
}

For RK4’s error order, stability region and step-halving checks, see Solving ODEs numerically.

To eliminate initial transient effects, the integration performs 3,000 warm-up steps starting from state (0.1,0,0)(0.1, 0, 0) before storing subsequent points. It generates 28,000 samples to reconstruct the manifold's spiral and folding structure.

Three.js Geometry and Spectral Tube​

Rendering 28,000 discrete points with single-pixel line segments lacks depth cues and spatial thickness. Instead, the points are subsampled, centered, interpolated into a smooth curve, and extruded as a 3D tube:

const positions = generateRosslerTrajectory({c: parameter, sampleCount: 28000});
const vertices = [];

// Subsample every 6th point (step size 18 in flat array) to balance fidelity and vertex count
for (let i = 0; i < positions.length; i += 18) {
vertices.push(new THREE.Vector3().fromArray(positions, i));
}

// Center the trajectory geometry so rotation pivots around its bounding-box center
const box = new THREE.Box3().setFromPoints(vertices);
const center = box.getCenter(new THREE.Vector3());
vertices.forEach((v) => v.sub(center));

const curve = new THREE.CatmullRomCurve3(vertices);
const segments = 4800;
const geometry = new THREE.TubeGeometry(curve, segments, 0.12, 5, false);

// Apply multi-stop spectral vertex colors along arc length
const SPECTRUM = ['#337dcc', '#28a5a0', '#b1b748', '#ecaa38', '#e26743', '#b766ac', '#337dcc'];
const stops = SPECTRUM.map((c) => new THREE.Color(c));
const colors = new Float32Array(geometry.attributes.position.count * 3);
const color = new THREE.Color();

for (let i = 0; i < colors.length / 3; i++) {
const t = Math.floor(i / 6) / segments * (stops.length - 1);
const k = Math.min(Math.floor(t), stops.length - 2);
color.copy(stops[k]).lerp(stops[k + 1], t - k).toArray(colors, i * 3);
}
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({vertexColors: true});

The color spectrum indicates temporal order along the sampled path, helping readers trace the direction of flow along the trajectory ribbon.

Instanced Markers with High-Contrast Outlines​

Twelve moving markers circulate at constant arc-length speed along the interpolated trajectory curve to visualize motion. Instead of allocating twelve independent meshes, they are rendered using two THREE.InstancedMesh allocations:

  1. Inner glowing spheres (color: '#fff2bc').
  2. Outer silhouette shells (color: '#191a16', side: THREE.BackSide, scaled by 1.4).
const beadCount = 12;
const beadGeometry = new THREE.SphereGeometry(0.36, 12, 8);
const beadMaterial = new THREE.MeshBasicMaterial({color: '#fff2bc'});
const outlineMaterial = new THREE.MeshBasicMaterial({color: '#191a16', side: THREE.BackSide});

const beads = new THREE.InstancedMesh(beadGeometry, beadMaterial, beadCount);
const outlines = new THREE.InstancedMesh(beadGeometry, outlineMaterial, beadCount);

// Frustum culling is disabled because instances continuously traverse the full curve
beads.frustumCulled = false;
outlines.frustumCulled = false;

The inverted-normal dark silhouette guarantees that markers remain legible against both dark and light UI themes, even when passing in front of brightly colored sections of the tube.

Lifecycle and Bounds​

  • Frame pacing: Timestamps cap rendering at 30 FPS to avoid wasting GPU cycles on high-refresh-rate displays.
  • Background pausing: Animation-frame requests stop completely when the container is offscreen or the tab is hidden.
  • Disposal: On unmount or parameter changes, the component disposes geometries, materials, both InstancedMesh objects, and controls; disconnects observers; and disposes the WebGL renderer and releases its context.
Explore connectionsOpen network