Worley Noise Generation

Watch the continuously generating Worley (Cellular) noise animation:



← Back to All Cases

Explanation

How It Works

This demo shows how to create cellular patterns using Worley noise (also called Voronoi or Cellular noise). This algorithm creates organic, cell-like patterns by calculating distances to randomly placed feature points. The animation automatically cycles through 4 different modes every 5 seconds:

Perfect for creating textures like stone, water caustics, biological cells, or crystalline structures.

View Worley Noise Class
class WorleyNoise {
    constructor(numPoints = 50) {
        this.numPoints = numPoints;
        this.points = [];
        this.generatePoints();
    }

    // Generate random feature points with velocity
    generatePoints() {
        this.points = [];
        for (let i = 0; i < this.numPoints; i++) {
            this.points.push({
                x: Math.random(),
                y: Math.random(),
                vx: (Math.random() - 0.5) * 0.001, // velocity x
                vy: (Math.random() - 0.5) * 0.001  // velocity y
            });
        }
    }

    // Calculate Euclidean distance
    distance(x1, y1, x2, y2) {
        const dx = x2 - x1;
        const dy = y2 - y1;
        return Math.sqrt(dx * dx + dy * dy);
    }

    // Get noise value (distance to closest point)
    noise(x, y) {
        let minDist = Infinity;
        for (let i = 0; i < this.points.length; i++) {
            const dist = this.distance(x, y,
                this.points[i].x, this.points[i].y);
            if (dist < minDist) minDist = dist;
        }
        return Math.min(minDist * 3, 1);
    }

    // Get edge detection (difference between 2 closest)
    noise2(x, y) {
        let minDist1 = Infinity;
        let minDist2 = Infinity;

        for (let i = 0; i < this.points.length; i++) {
            const dist = this.distance(x, y,
                this.points[i].x, this.points[i].y);

            if (dist < minDist1) {
                minDist2 = minDist1;
                minDist1 = dist;
            } else if (dist < minDist2) {
                minDist2 = dist;
            }
        }
        // Highlights cell boundaries
        return Math.min((minDist2 - minDist1) * 10, 1);
    }

    // Update positions for drift animation
    updatePoints() {
        for (let i = 0; i < this.points.length; i++) {
            this.points[i].x += this.points[i].vx;
            this.points[i].y += this.points[i].vy;

            // Wrap around edges
            if (this.points[i].x < 0) this.points[i].x += 1;
            if (this.points[i].x > 1) this.points[i].x -= 1;
            if (this.points[i].y < 0) this.points[i].y += 1;
            if (this.points[i].y > 1) this.points[i].y -= 1;
        }
    }
}
View Animation Code with Multiple Modes
const canvas = document.getElementById("noiseCanvas");
const ctx = canvas.getContext("2d");
const worley = new WorleyNoise(50);

let animationMode = 0; // 0: drift, 1: circular, 2: pulsing, 3: edge
let time = 0;

// Auto-switch animation modes every 5 seconds
setInterval(() => {
    animationMode = (animationMode + 1) % 4;
}, 5000);

function animate() {
    const imgData = ctx.createImageData(canvas.width, canvas.height);
    const data = imgData.data;
    time += 0.01;

    // Different animation modes
    switch(animationMode) {
        case 0: // Drift - smooth random movement
            worley.updatePoints();
            break;
        case 1: // Circular motion
            for (let i = 0; i < worley.points.length; i++) {
                const angle = time * 0.1 + i * 2;
                worley.points[i].x = baseX + Math.cos(angle) * 0.1;
                worley.points[i].y = baseY + Math.sin(angle) * 0.1;
            }
            break;
        case 2: // Pulsing
            for (let i = 0; i < worley.points.length; i++) {
                const pulse = Math.sin(time + i) * 0.15;
                worley.points[i].x = baseX + Math.cos(i * 2) * pulse;
                worley.points[i].y = baseY + Math.sin(i * 2) * pulse;
            }
            break;
        case 3: // Edge detection mode
            worley.updatePoints();
            break;
    }

    // Render with mode-specific effects
    for (let y = 0; y < canvas.height; y++) {
        for (let x = 0; x < canvas.width; x++) {
            const nx = x / canvas.width;
            const ny = y / canvas.height;

            // Use noise2() for edge detection mode
            const value = animationMode === 3 ?
                worley.noise2(nx, ny) : worley.noise(nx, ny);

            const color = Math.floor(value * 255);
            const index = (y * canvas.width + x) * 4;
            data[index] = color;
            data[index + 1] = color;
            data[index + 2] = color;
            data[index + 3] = 255;
        }
    }

    ctx.putImageData(imgData, 0, 0);
    requestAnimationFrame(animate);
}

animate();
View Distance Calculation
// Different distance metrics create different patterns:

// 1. Euclidean (standard circular cells)
distance(x1, y1, x2, y2) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    return Math.sqrt(dx * dx + dy * dy);
}

// 2. Manhattan (diamond-shaped cells)
distance(x1, y1, x2, y2) {
    return Math.abs(x2 - x1) + Math.abs(y2 - y1);
}

// 3. Chebyshev (square cells)
distance(x1, y1, x2, y2) {
    return Math.max(Math.abs(x2 - x1), Math.abs(y2 - y1));
}