var k1,k2,k3,c1,c2,c3,r1,r2,r3,z1,z2,z3;
side = windowWidth < windowHeight ? windowWidth:windowHeight;
z1 = new complex(side/2,side/2);
var touchPoint = new complex(side/2-r1,side/2);
z2 = touchPoint.add(new complex(r2,0));
z12 = touchPoint.add(new complex(r12,0));
c1 = new Circle(z1.scale(k1),k1);
c2 = new Circle(z2.scale(k2),k2);
let cm = c1.mapToUnitCircle()
let cn = cm.mapFromUnitCircle()
invertedCircles = circles.map((x) => x.invert());
invertedCircles.map((x) => x.print());
function createCircles(theta) {
var dz3 = new complex(r12*cos(theta),r12*sin(theta));
r3 = r1 - z1.minus(z3).modulus();
z3 = find_z3(z2,r2,r3,theta);
c1 = new Circle(z1.scale(k1),k1);
c2 = new Circle(z2.scale(k2),k2);
var c3 = new Circle(z3.scale(k3),k3);
c1.tangentCircles = [c2,c3];
c2.tangentCircles = [c1,c3];
c3.tangentCircles = [c2,c1];
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));},[]);
circles = concat(circles,completion);
circles.map((x) => x.draw());
invertedCircles.map((x) => x.draw());
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();
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.isEqual = function(c) {
var equalR = abs(this.r - c.r) < tolerance;
var equalX = abs(this.x - c.x) < tolerance;
var equalY = abs(this.y - c.y) < tolerance;
return equalR && equalX && equalY;
colorMode(HSB,360,100,100);
var sat = map(s,0,1,100,0);
fill(this.hue,sat,bright);
ellipse(this.x,this.y,2*this.r,2*this.r);
this.mapToUnitCircle = function() {
let z_ = new complex(x_ * k_,y_ * k_)
this.mapFromUnitCircle = function() {
let z_ = new complex(x_ * k_,y_ * k_)
this.invert = function() {
let c = this.mapToUnitCircle()
let center = new complex(c.x,c.y)
let R_0 = center.modulus()
let R_minus_f = 1 / R_minus
let radius = 0.5 * abs(R_plus_f - R_plus_f)
let centerDistance = 0.5 * abs(R_plus_f + R_plus_f)
let z = createComplexPolar(centerDistance,center.arg())
let invertedInUnit = new Circle(w,k)
let inverted = invertedInUnit.mapFromUnitCircle()
this.print = function() {
console.log("r=" + this.r)
function createComplexPolar(r,theta) {
return new complex(r * cos(theta),r * sin(theta))
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);
this.lengthSquared = function() {
return this.x * this.x + this.y * this.y;
return atan2(this.y,this.x);