Skip to content

Instantly share code, notes, and snippets.

@MagnusThor
Created December 5, 2025 07:43
Show Gist options
  • Select an option

  • Save MagnusThor/d668e857ff429962fac04606bab2be69 to your computer and use it in GitHub Desktop.

Select an option

Save MagnusThor/d668e857ff429962fac04606bab2be69 to your computer and use it in GitHub Desktop.
class SierpinskiTriangle {
static get inputProperties() {
return [
'--sierpinski-iterations',
'--zoom-factor',
'--fractal-opacity',
'--rotation-angle'
];
}
paint(ctx, size, props) {
const maxIterations = parseInt(props.get('--sierpinski-iterations').toString()) || 12;
const zoom = parseFloat(props.get('--zoom-factor').toString()) || 1.0;
const opacity = parseFloat(props.get('--fractal-opacity').toString()) || 0.5;
const rotationDegrees = parseFloat(props.get('--rotation-angle').toString()) || 0;
const rotationRadians = rotationDegrees * Math.PI / 180;
// --- 2. Context Setup ---
ctx.globalAlpha = opacity;
const zoom_center_x = size.width / 2;
const zoom_center_y = size.height / 2;
// --- 3. Apply Canvas Transformation (Rotation) ---
ctx.save();
ctx.translate(zoom_center_x, zoom_center_y);
ctx.rotate(rotationRadians);
ctx.translate(-zoom_center_x, -zoom_center_y);
// --- 4. Infinite Tunneling Logic ---
const log2Zoom = Math.log2(zoom);
// Scale factor cycles from 1.0 up to 2.0 (for seamless repetition)
const scaleFactor = Math.pow(2, log2Zoom % 1);
// --- 5. Define Base Triangle ---
// Use a base size larger than the element to ensure full viewport coverage
const maxDim = Math.max(size.width, size.height);
const sideLength = maxDim * 1.5;
const h = sideLength * Math.sqrt(3) / 2;
const p1_base = { x: zoom_center_x, y: zoom_center_y - h / 2 };
const p2_base = { x: zoom_center_x - sideLength / 2, y: zoom_center_y + h / 2 };
const p3_base = { x: zoom_center_x + sideLength / 2, y: zoom_center_y + h / 2 };
// --- 6. Apply Zoom Scaling ---
const p1_final = this.transformPoint(p1_base, scaleFactor, zoom_center_x, zoom_center_y);
const p2_final = this.transformPoint(p2_base, scaleFactor, zoom_center_x, zoom_center_y);
const p3_final = this.transformPoint(p3_base, scaleFactor, zoom_center_x, zoom_center_y);
// --- 7. Start Recursion (and Color by Depth) ---
this.drawTriangle(ctx, p1_final, p2_final, p3_final, maxIterations, maxIterations);
ctx.restore(); // Restore context to remove rotation
}
// Helper for geometric scaling around a center point (cx, cy)
transformPoint(p, scale, cx, cy) {
return {
x: cx + (p.x - cx) * scale,
y: cy + (p.y - cy) * scale
};
}
// Recursive function with color-by-depth logic
drawTriangle(ctx, pA, pB, pC, level, maxLevel) {
if (level === 0) {
// Base case: Draw the filled triangle
// Calculate HSL color based on depth (maxLevel - level)
// Creates a gradient effect from outer layers (maxLevel) to inner layers (0)
const depth = maxLevel - level;
const hue = 240 + depth * (60 / maxLevel); // Color shift (Blue to Purple)
const lightness = 20 + depth * (30 / maxLevel); // Intensity shift (Darker to Lighter)
ctx.fillStyle = `hsl(${hue}, 100%, ${lightness}%)`;
ctx.beginPath();
ctx.moveTo(pA.x, pA.y);
ctx.lineTo(pB.x, pB.y);
ctx.lineTo(pC.x, pC.y);
ctx.closePath();
ctx.fill();
} else {
// Recursive step: Find the midpoints of the sides
const pAB = { x: (pA.x + pB.x) / 2, y: (pA.y + pB.y) / 2 };
const pBC = { x: (pB.x + pC.x) / 2, y: (pB.y + pC.y) / 2 };
const pCA = { x: (pC.x + pA.x) / 2, y: (pC.y + pA.y) / 2 };
// Recursively call for the three smaller outer triangles
this.drawTriangle(ctx, pA, pAB, pCA, level - 1, maxLevel);
this.drawTriangle(ctx, pAB, pB, pBC, level - 1, maxLevel);
this.drawTriangle(ctx, pCA, pBC, pC, level - 1, maxLevel);
}
}
}
registerPaint('sierpinski-triangle', SierpinskiTriangle);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment