Something fun I created this afternoon! Here is a cluster growth algorithm where each perimeter cell grows depending on whether a certain probability threshold is met!
Animation below:
Deconstruction tutorial here:
Source here:
kirupa/examples/cluster_growth_algorithm.htm at master · kirupa/kirupa · GitHub
Reposted below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cluster Growth Animation</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
canvas {
border: 1px solid black;
background-color: #fff;
}
</style>
</head>
<body>
<canvas id="canvas" width="420" height="420"></canvas>
<script>
// Get the canvas element and its 2D rendering context
let myCanvas = document.getElementById("canvas");
let ctx = myCanvas.getContext("2d");
// Configuration variables
let activeCells = 0;
let iterations = 0;
let cellSize = 3; // Size of each cell in pixels
let gap = 3; // Size of the gap between cells in pixels
let totalOffset = cellSize + gap;
// Calculate the grid dimensions based on canvas size and cell size
let width = Math.floor(myCanvas.width / totalOffset);
let height = Math.floor(myCanvas.height / totalOffset);
let totalCells = (width - 1) * (height - 1);
// Create an empty grid to start with
let tempGrid = createEmptyGrid();
let growthProbability = customRandom(0.05, 0.2); // Set initial growth probability
// Array of dark colors for the clusters
const dark_colors = [
"#9D0000", // Dark Red
"#C70039", // Deep Pink
"#800080", // Purple
"#4C3378", // Dark Purple (bluish)
"#003080", // Navy Blue (more vivid)
"#007BFF", // Material Blue (darker, vivid)
"#388E3C", // Dark Green (vivid)
"#00695C", // Dark Teal
"#663399", // Dark Amethyst
"#8B0000", // Maroon (more vivid)
"#A020F0", // Deep Purple (more intense)
"#B32830", // Dark Red (brownish)
"#BF360C", // Dark Orange
"#C2C255", // Dark Lime (more muted)
"#FF9933", // Dark Orange (reddish)
];
// Select a random color for the current iteration
let color = getRandomColor(dark_colors);
// Function to create an empty grid filled with zeros
function createEmptyGrid() {
let emptyGrid = [];
for (let i = 0; i < width + 1; i++) {
let row = [];
for (let j = 0; j < height + 1; j++) {
row.push(0); // Initialize each cell to 0
}
emptyGrid.push(row);
}
return emptyGrid;
}
// Function to set the initial cluster of active cells
function setInitialCluster(x, y, size) {
let newCells = [];
for (let i = x - size; i < x + size; i++) {
for (let j = y - size; j < y + size; j++) {
if (i >= 0 && i < width && j >= 0 && j < height) {
tempGrid[i][j] = 1;
newCells.push([i, j]);
}
}
}
// Draw only the new cells
newCells.forEach(([x, y]) => drawCell(x, y));
}
// Function to draw the current state of the grid
function drawCell(x, y) {
ctx.fillStyle = color;
let xPos = Math.round(x * totalOffset - cellSize / 2);
let yPos = Math.round(y * totalOffset - cellSize / 2)
ctx.fillRect(xPos, yPos, cellSize, cellSize);
activeCells++;
}
// Function to update the grid based on growth rules
function updateGrid() {
let newCells = [];
let updatedGrid = createEmptyGrid(); // Create a new empty grid
// Iterate through each cell in the grid
for (let i = 1; i < tempGrid.length - 1; i++) {
for (let j = 1; j < tempGrid[0].length - 1; j++) {
if (tempGrid[i][j] === 0) {
let neighbors = countNeighbors(tempGrid, i, j); // Count the number of neighboring cells
if (neighbors > 0) {
if (growthProbability > Math.random()) {
newCells.push([i, j]);
updatedGrid[i][j] = 1; // Grow a new cell
}
}
} else {
updatedGrid[i][j] = tempGrid[i][j]; // Keep the existing cell
}
}
}
// Draw only the new cells
newCells.forEach(([x, y]) => drawCell(x, y));
tempGrid = updatedGrid; // Update the grid
}
// Function to count the number of active neighboring cells
function countNeighbors(grid, x, y) {
let sum = 0;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
sum += tempGrid[x + i][y + j]; // Sum the values of the neighboring cells
}
}
return sum;
}
// Function to reset the animation
function reset() {
activeCells = 0;
tempGrid = createEmptyGrid();
growthProbability = customRandom(0.05, 0.2); // Set new growth probability
// Alternate between colored and white clusters
if (iterations % 2 !== 0) {
color = getRandomColor(dark_colors);
} else {
color = "white";
}
setInitialCluster(Math.ceil(width / 2), Math.ceil(height / 2), 2);
iterations++;
}
// Set the initial cluster in the center of the grid
setInitialCluster(Math.ceil(width / 2), Math.ceil(height / 2), 2);
// Main animation function
function growClusters() {
updateGrid();
// Reset if the entire grid is filled
if (activeCells >= totalCells) {
reset();
}
requestAnimationFrame(growClusters);
}
// Start the animation
growClusters();
// Utility function to get a random color from the array
function getRandomColor(colors) {
return colors[Math.floor(Math.random() * colors.length)];
}
// Utility function to generate a random number within a range
function customRandom(min, max) {
return Math.random() * (max - min) + min;
}
</script>
</body>
</html>
Hope you all enjoy this. This will likely be part of a future tutorial
Cheers,
Kirupa