fpp: () => sketch.fps * sketch.duration,
createCanvas(size, size);
clk = new PeriodicFrameClock(sketch.fpp());
colorMode(HSB, 360, 100, 100, 1.0);
const xOffset = (width * scale);
const i1 = map(clk.cos(t), -1, 1, 5, 13);
const i2 = i1 * map(clk.sin(t), -1, 1, 2, 2);
const i3 = i2 * map(clk.cos(t), -1, 1, 0.5, 1.5);
const circles = calculateTriple(scale, xOffset, i1, i2, i3);
const tangentCircles = descartesTheorem(...circles);
let boundingCircle, middleCircle;
if (tangentCircles[0].r < 0) {
boundingCircle = tangentCircles[0];
middleCircle = tangentCircles[1];
boundingCircle = tangentCircles[1];
middleCircle = tangentCircles[0];
translate(circles[0].x - boundingCircle.x, circles[0].y - boundingCircle.y);
const drawCircle = (c, n) => {
fill(212 - (total - n) * 30, 50, 60);
strokeWeight((n === 0) ? 6 : 3);
tangentCircles.forEach((c) => drawCircle(c, 0));
circles.forEach((c) => drawCircle(c, 0));
generate(middleCircle, circles, drawCircle, total);
generate(boundingCircle, circles, drawCircle, total);
return `${round(c.x)}-${round(c.y)}-${round(c.r)}`;
function generate(c, circles, drawCircle, n) {
for (let i = 0; i < 3; i++) {
const cs = [c, circles[i % 3], circles[(i + 1) % 3]];
const newCircles = descartesTheorem(...cs);
newCircles.forEach((newC, index) => {
const h = circleHash(newC);
if (drawnCircles[h] === undefined) {
function createComplexNumber(a, b) {
if (typeof n === 'number') {
return createComplexNumber(a + n, b);
return createComplexNumber(a + n.a, b + n.b)
if (typeof n === 'number') {
return createComplexNumber(a * n, b * n);
const real = a * n.a - b * n.b;
const img = a * n.b + n.a * b;
return createComplexNumber(real, img);
if (b === 0) return createComplexNumber(Math.sqrt(a), b);
const r = Math.sqrt(a * a + b * b);
const real = Math.sqrt((r + a) * 0.5);
const img = (b > 0 ? 1.0 : -1.0) * Math.sqrt((r - a) * 0.5);
return createComplexNumber(real, img);
function descartesTheorem(c1, c2, c3) {
const z1 = createComplexNumber(c1.x, c1.y);
const z2 = createComplexNumber(c2.x, c2.y);
const z3 = createComplexNumber(c3.x, c3.y);
const kSum = (k1 + k2 + k3);
const kSqrt = Math.sqrt((k1 * k2) + (k2 * k3) + (k3 * k1));
const k4 = (kSum + 2.0 * kSqrt);
const k5 = (kSum - 2.0 * kSqrt);
const z1k1 = z1.mult(k1);
const z2k2 = z2.mult(k2);
const z3k3 = z3.mult(k3);
const kSumZ = z1k1.add(z2k2).add(z3k3);
let kSqrtZ = (z1k1.mult(z2k2).add(z2k2.mult(z3k3)).add(z1k1.mult(z3k3))).sqrt();
const z4 = kSumZ.add(kSqrtZ.mult(2.0)).mult(1.0 / k4);
const z5 = kSumZ.add(kSqrtZ.mult(-2.0)).mult(1.0 / k5);
function calculateTriple(scale, xOffset, i1, i2, i3) {
const r1 = base * scale / i1;
const r2 = base * scale / i2;
const r3 = base * scale / i3;
const a = map(clk.sin(clk.time()),-1,1,PI/4, TAU);
const C = acos((r1r2 * r1r2 + r3r1 * r3r1 - r2r3 * r2r3) / (2 * r1r2 * r3r1));
const c3V = p5.Vector.fromAngle(a+C);
const c3Center = p5.Vector.add(createVector(c1.x, c1.y), c3V);