var circle_1,circle_2,circle_3,initial_decartes,touchPoint,r1,r2,z1,z2;
side = windowWidth < windowHeight ? windowWidth:windowHeight;
center = new complex(side/2,side/2);
touchPoint = center.minus(new complex(r1,0));
z2 = touchPoint.add(new complex(r2,0));
mouse = new complex(mouseX,mouseY);
if(abs(mouseX-mouse.x) > 1.0 || abs(mouseX-mouse.x)>1.0 || firstFrame){
mouse = new complex(mouseX,mouseY);
var relativeMouse = mouse.minus(touchPoint);
if(relativeMouse.modulus()>275 && mouse.minus(z1).modulus()<r1){
r2 = 0.5*relativeMouse.modulus()/cos(relativeMouse.arg());
z2 = touchPoint.add(new complex(r2,0));
theta = mouse.minus(z2).arg();
circle_1 = new Circle(z1.scale(k1),k1);
circle_2 = new Circle(z2.scale(k2),k2);
circle_3 = thirdCircle(circle_1,circle_2,theta);
circle_1.tangentCircles = [circle_2,circle_3];
circle_2.tangentCircles = [circle_1,circle_3];
circle_3.tangentCircles = [circle_2,circle_1];
circles = [circle_1,circle_2,circle_3];
while(circles.length<1000 && n<5){
var incompleteCircles = circles.filter((x) => x.tangentCircles.length>0 && x.tangentCircles.length<5);
var completion = incompleteCircles.reduce( function(acc,obj) { return concat(acc,apollonian(obj,r_min));},[]);
circles = concat(circles,completion);
circles.map((x) => x.draw());
else if(circles.length<10000) {
var incompleteCircles = circles.filter((x) => x.tangentCircles.length>0 && x.tangentCircles.length<5);
var completion = incompleteCircles.reduce( function(acc,obj) { return concat(acc,apollonian(obj,r_min));},[]);
completion.map((x) => x.draw());
circles = concat(circles,completion);
function thirdCircle(c1,c2,angle){
var r_a = 0.5*(c1.r + c2.r);
var z_a = c1.center.add(c2.center).scale(0.5);
var dz3 = new complex(r_a*cos(angle),r_a*sin(angle));
r3 = find_r3(z3,c1.center,c1.r);
z3 = find_z3(c2.center,c2.r,r3,theta);
return new Circle(z3.scale(k3),k3);
function find_z3(z2,r2,r3,theta){
var n = new complex(cos(theta),sin(theta));
return z2.add(n.scale(r2+r3));
function find_r3(z3,z1,r1){
return r1 - dz.modulus();
function apollonian(c,r_min){
if(c.tangentCircles.length<2)return [];
if(c.tangentCircles.length==2) return decartes(c,c.tangentCircles[0],c.tangentCircles[1]);
c1 = c.tangentCircles[0];
c2 = c.tangentCircles[1];
c3 = c.tangentCircles[2];
var c23 = decartes(c,c2,c3).filter((x)=> !c1.isEqual(x) && x.r > r_min);
var c13 = decartes(c,c1,c3).filter((x)=> !c2.isEqual(x) && x.r > r_min);
var c12 = decartes(c,c1,c2).filter((x)=> !c3.isEqual(x) && x.r > r_min);
return concat(c23,concat(c12,c13));
function decartes(c1,c2,c3){
var k_plus = c1.k + c2.k + c3.k + 2*sqrt(c1.k*c2.k + c3.k*c2.k + c1.k*c3.k);
var k_minus = c1.k + c2.k + c3.k - 2*sqrt(c1.k*c2.k + c3.k*c2.k + c1.k*c3.k);
var c12 = c1.z.mult(c2.z);
var c23 = c2.z.mult(c3.z);
var c31 = c3.z.mult(c1.z);
var t1 = c1.z.add(c2.z.add(c3.z));
var t2 = c12.add(c23.add(c31));
var t3 = t2.sqrt().scale(2.0);
var z_minus = t1.minus(t3);
var c_plus = new Circle(z_plus,k_plus);
var c_minus = new Circle(z_minus,k_minus);
c_plus.tangentCircles = [c1,c2,c3];
c_minus.tangentCircles = [c1,c2,c3];
this.center = z.scale(1.0 /k);
this.description = function(){
println( "Circle center: (" + this.center.x + "," + this.center.y + ") radius:" + this.r + "curvature:" +this.k);
this.isEqual = function(c) {
var equalR = abs(this.r - c.r) < tolerance;
var equalX = abs(this.center.x - c.center.x) < tolerance;
var equalY = abs(this.center.y - c.center.y) < tolerance;
return equalR && equalX && equalY;
ellipse(this.center.x,this.center.y,2*this.r,2*this.r);
return new complex(this.x + z.x,this.y + z.y);
this.minus = function(z){
return new complex(this.x - z.x,this.y - z.y);
return new complex(this.x * z.x - this.y * z.y ,this.x * z.y + this.y * z.x);
this.scale = function(s){
return new complex(this.x * s ,this.y * s);
var r = sqrt(this.modulus());
var arg = this.arg()/2.0;
return new complex(r*cos(arg),r*sin(arg));
this.modulus = function() {
return sqrt(this.x * this.x + this.y * this.y);
return atan2(this.y,this.x);