AG = new ApollonianGasket();
rect(0, 0, width, height);
if (clear) background(0);
translate(width*.42, height*.35);
AG.build(outer, circs[0], circs[1], circs[2], level, maxLevel);
void build(Circle c1, Circle c2, Circle c3, Circle c4, int current, int max) {
if (current == max) return;
Circle result = secSol(c1, c2, c3, c4);
build(result, c2, c3, c4, 1, max);
Circle result2 = secSol(c2, c1, c3, c4);
Circle result3 = secSol(c3, c1, c2, c4);
Circle result4 = secSol(c4, c1, c2, c3);
build(result2, c1, c3, c4, current+1, max);
build(result3, c1, c2, c4, current+1, max);
build(result4, c1, c2, c3, current+1, max);
Circle(float xin, float yin, float rin) {
pos = new Complex(xin, yin);
stroke(#E48BFF, map(sze, 0, 400, 255, 50));
strokeWeight(map(sze, 0, 200, .5, 15));
ellipse(pos.realFloat(), pos.imagFloat(), sze, sze);
ellipse(pos.realFloat(), pos.imagFloat(), sze, sze);
Complex(double xin, double yin) {
return Math.sqrt(x*x+y*y);
Complex plus(Complex w) {
return new Complex(x+w.real(), y+w.imag());
Complex minus(Complex w) {
return new Complex(x-w.real(), y-w.imag());
Complex timesC(Complex w) {
return new Complex(x*w.real()-y*w.imag(), x*w.imag()+y*w.real());
Complex timesS(double alpha) {
return new Complex(alpha * x, alpha * y);
Complex divC(Complex w) {
double den = Math.pow(w.mod(), 2);
return new Complex((x*w.real()+y*w.imag())/den, (y*w.real()-x*w.imag())/den);
Complex divS(double alpha) {
println("Division by zero attempted in divS");
return new Complex(x, y);
} else return new Complex(x/alpha, y/alpha);
double r = Math.sqrt(this.mod());
double theta = this.arg()/2;
return new Complex(r*Math.cos(theta), r*Math.sin(theta));
circs = calcThreeCircles(80, 80, 80+adj);
outer = calcOuterSoddy(circs[0], circs[1], circs[2]);
Circle[] calcThreeCircles(float r0, float r1, float r2) {
Circle[] circs = new Circle[3];
c0 = new Circle(0, 0, r0);
c1 = new Circle(r0+r1, 0, r1);
pos2x = (r0*r0 + r0*r2 + r0*r1 - r1*r2) / (r0 + r1);
pos2y = sqrt((r0 + r2) * (r0 + r2) - pos2x*pos2x );
c2 = new Circle(pos2x, pos2y, r2);
Circle calcOuterSoddy(Circle c1in, Circle c2in, Circle c3in) {
float c1C, c2C, c3C, c4C, c4Rad;
Complex z1, z2, z3, z4, z5, z6, z7, z8, z9, z10, z11;
c4C = c1C + c2C + c3C - 2*(sqrt((c1C*c2C) + (c2C*c3C ) + (c1C*c3C)));
z1 = new Complex(c1in.pos.real(), c1in.pos.imag());
z2 = new Complex(c2in.pos.real(), c2in.pos.imag());
z3 = new Complex(c3in.pos.real(), c3in.pos.imag());
z4 = z1.timesS(c1C).plus(z2.timesS(c2C)).plus(z3.timesS(c3C));
z5 = z1.timesC(z2).timesS(c1C * c2C);
z6 = z2.timesC(z3).timesS(c2C * c3C);
z7 = z1.timesC(z3).timesS(c1C * c3C);
z8 = z5.plus(z6).plus(z7);
z9 = z8.sqrtC().timesS(-2);
return new Circle(z11.realFloat(), z11.imagFloat(), c4Rad);
Circle secSol(Circle firstSol, Circle c1, Circle c2, Circle c3) {
float c1C, c2C, c3C, cFirst, cNew;
Complex z1, z2, z3, z4, z5, z6, z7, zFirst, zNew;
cFirst = firstSol.curvature();
z1 = new Complex(c1.pos.real(), c1.pos.imag());
z2 = new Complex(c2.pos.real(), c2.pos.imag());
z3 = new Complex(c3.pos.real(), c3.pos.imag());
zFirst = new Complex(firstSol.pos.real(), firstSol.pos.imag());
cNew = (2.0 * (c1C + c2C + c3C)) - cFirst;
z4 = z1.timesS(c1C).plus(z2.timesS(c2C)).plus(z3.timesS(c3C));
z6 = zFirst.timesS(cFirst);
return new Circle(zNew.realFloat(), zNew.imagFloat(), 1/cNew);