Documentation Index
Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/llms.txt
Use this file to discover all available pages before exploring further.
This example demonstrates how to create procedurally generated animations that are fully deterministic and reproducible using Helios utility functions.
Overview
The procedural generation example shows:
- Deterministic random number generation
- Frame-based procedural animations
- Color interpolation over time
- Grid-based generative patterns
Complete implementation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Procedural Generation Example</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: black;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas></canvas>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
import { Helios, random, interpolateColors } from '@helios-project/core';
// Init Helios
const helios = new Helios({ fps: 30, duration: 10 });
helios.bindToDocumentTimeline();
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
// Grid Config
const COLS = 10;
const ROWS = 10;
// Resize handler
function resize() {
if (!canvas) return;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
helios.setSize(canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();
helios.subscribe((state) => {
const frame = state.currentFrame;
const { width, height } = canvas;
// Background Color Cycle
const bg = interpolateColors(
frame,
[0, 300],
['#1a1a2e', '#16213e'],
{ extrapolateRight: 'clamp' }
);
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
const cellW = width / COLS;
const cellH = height / ROWS;
// Draw Grid
for (let i = 0; i < COLS * ROWS; i++) {
const col = i % COLS;
const row = Math.floor(i / COLS);
// Deterministic properties based on index
const seed = i;
const baseSize = random(seed) * (Math.min(cellW, cellH) * 0.5);
const speed = random(seed + 1000) * 0.2;
const offset = random(seed + 2000) * Math.PI * 2;
const colorSeed = random(seed + 3000);
// Animate scale using sine wave based on frame
const currentScale = 0.5 + 0.5 * Math.sin(frame * speed + offset);
const x = col * cellW + cellW/2;
const y = row * cellH + cellH/2;
// Color based on position or random
ctx.fillStyle = colorSeed > 0.5 ? 'tomato' : 'teal';
// Draw
ctx.beginPath();
ctx.arc(x, y, baseSize * currentScale, 0, Math.PI * 2);
ctx.fill();
}
});
// Expose helios for verification
(window as any).helios = helios;
Key patterns
Deterministic random generation
The random() function from Helios core provides deterministic pseudo-random numbers based on a seed:
import { random } from '@helios-project/core';
// Same seed always returns same value
const value1 = random(42); // Always returns same number
const value2 = random(42); // Identical to value1
// Different seeds return different values
const value3 = random(43); // Different from value1
This ensures that procedural animations are reproducible and render identically every time.
Seed-based property generation
Generate unique but deterministic properties for each element:
for (let i = 0; i < COLS * ROWS; i++) {
const seed = i;
const baseSize = random(seed) * (Math.min(cellW, cellH) * 0.5);
const speed = random(seed + 1000) * 0.2;
const offset = random(seed + 2000) * Math.PI * 2;
const colorSeed = random(seed + 3000);
}
By using different seed offsets (1000, 2000, 3000), each property gets independent randomness while remaining deterministic.
Color interpolation
Use interpolateColors() for smooth color transitions:
import { interpolateColors } from '@helios-project/core';
const bg = interpolateColors(
frame,
[0, 300],
['#1a1a2e', '#16213e'],
{ extrapolateRight: 'clamp' }
);
This interpolates between colors based on frame number with clamping at the end.
Frame-based animation
Combine random seeds with frame-based timing for dynamic animations:
const speed = random(seed + 1000) * 0.2;
const offset = random(seed + 2000) * Math.PI * 2;
const currentScale = 0.5 + 0.5 * Math.sin(frame * speed + offset);
Each element animates at its own deterministic speed and phase offset.
Cache random values
For large grids, cache random values instead of recalculating:
// Pre-generate random properties
const elements = Array.from({ length: COLS * ROWS }, (_, i) => ({
seed: i,
baseSize: random(i) * maxSize,
speed: random(i + 1000) * 0.2,
offset: random(i + 2000) * Math.PI * 2,
color: random(i + 3000) > 0.5 ? 'tomato' : 'teal'
}));
// Use cached values in draw loop
helios.subscribe((state) => {
elements.forEach(elem => {
const scale = 0.5 + 0.5 * Math.sin(state.currentFrame * elem.speed + elem.offset);
// Draw with elem.baseSize * scale
});
});
Optimize grid rendering
Skip rendering elements outside the viewport:
for (let i = 0; i < COLS * ROWS; i++) {
const x = col * cellW + cellW/2;
const y = row * cellH + cellH/2;
// Skip if outside viewport
if (x < -margin || x > width + margin ||
y < -margin || y > height + margin) {
continue;
}
// Render element
}
Use integer calculations
Where possible, use integer math for better performance:
// Good - integer operations
const col = i % COLS;
const row = Math.floor(i / COLS);
// Avoid - floating point when unnecessary
const col = Math.floor(i / COLS) * COLS;
Batch canvas operations
Group similar drawing operations to reduce state changes:
// Draw all circles of one color
ctx.fillStyle = 'tomato';
elementsA.forEach(elem => {
ctx.beginPath();
ctx.arc(elem.x, elem.y, elem.radius, 0, Math.PI * 2);
ctx.fill();
});
// Then draw all circles of another color
ctx.fillStyle = 'teal';
elementsB.forEach(elem => {
ctx.beginPath();
ctx.arc(elem.x, elem.y, elem.radius, 0, Math.PI * 2);
ctx.fill();
});
Advanced techniques
Noise-based generation
Implement Perlin or simplex noise for organic patterns:
import { createNoise2D } from 'simplex-noise';
import { random } from '@helios-project/core';
// Create seeded noise function
const noise2D = createNoise2D(() => random(12345));
helios.subscribe((state) => {
for (let x = 0; x < width; x += 10) {
for (let y = 0; y < height; y += 10) {
const noiseValue = noise2D(x * 0.01, y * 0.01 + state.currentTime * 0.1);
const brightness = (noiseValue + 1) * 127.5;
ctx.fillStyle = `rgb(${brightness}, ${brightness}, ${brightness})`;
ctx.fillRect(x, y, 10, 10);
}
}
});
Particle systems
Create deterministic particle systems:
const PARTICLE_COUNT = 1000;
const particles = Array.from({ length: PARTICLE_COUNT }, (_, i) => ({
x: random(i) * width,
y: random(i + 10000) * height,
vx: (random(i + 20000) - 0.5) * 2,
vy: (random(i + 30000) - 0.5) * 2,
radius: random(i + 40000) * 5 + 2,
color: `hsl(${random(i + 50000) * 360}, 70%, 60%)`
}));
helios.subscribe((state) => {
const dt = 1 / state.fps;
particles.forEach(p => {
// Update position based on velocity and time
p.x += p.vx * dt * state.currentFrame;
p.y += p.vy * dt * state.currentFrame;
// Wrap around screen
p.x = ((p.x % width) + width) % width;
p.y = ((p.y % height) + height) % height;
// Draw
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fill();
});
});
L-System generation
Generate fractal patterns using L-Systems:
function generateLSystem(axiom: string, rules: Record<string, string>, iterations: number): string {
let result = axiom;
for (let i = 0; i < iterations; i++) {
result = result.split('').map(char => rules[char] || char).join('');
}
return result;
}
const rules = { 'F': 'F+F-F-F+F' };
const pattern = generateLSystem('F', rules, 3);
// Render L-System with turtle graphics
function drawLSystem(instructions: string, frame: number) {
let x = width / 2, y = height / 2;
let angle = 0;
const step = 10;
const angleStep = Math.PI / 2;
// Animate drawing based on frame
const progress = Math.min(frame / 300, 1);
const visibleLength = Math.floor(instructions.length * progress);
for (let i = 0; i < visibleLength; i++) {
const cmd = instructions[i];
if (cmd === 'F') {
const newX = x + Math.cos(angle) * step;
const newY = y + Math.sin(angle) * step;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(newX, newY);
ctx.stroke();
x = newX;
y = newY;
} else if (cmd === '+') {
angle += angleStep;
} else if (cmd === '-') {
angle -= angleStep;
}
}
}
Multi-layer composition
Combine multiple procedural layers:
helios.subscribe((state) => {
const frame = state.currentFrame;
// Layer 1: Background gradient
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, interpolateColors(frame, [0, 300], ['#000428', '#004e92']));
gradient.addColorStop(1, interpolateColors(frame, [0, 300], ['#004e92', '#000428']));
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Layer 2: Animated grid
drawProceduralGrid(frame);
// Layer 3: Particle overlay
drawParticles(frame);
// Layer 4: Noise texture
drawNoiseOverlay(frame, 0.1);
});