package { import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.*; import flash.display.StageAlign; import flash.display.StageScaleMode; /** * based on SoddyCircles solution by Ryan Phelan * http://www.bit-101.com/blog/?p=1251 */ [SWF(backgroundColor="0xffffff", width="800", height="600", frameRate="30")] public class ApollonianGasket extends Sprite { private var circles:Vector. = new Vector.(); private var oldcircles:Vector. = new Vector.(); public function ApollonianGasket() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; init(); } private function init():void { //add initial 3 circles addCircle(0,0,.5); addCircle(stage.stageWidth,0,.5); addCircle(stage.stageWidth/2,stage.stageHeight,.5); //make 1st 3 circles draggable circles.forEach(function(c:Sprite, i:int, a:Vector.):void { c.addEventListener(MouseEvent.MOUSE_DOWN, onDrag); }); //render all - create=draw will populate circles vector drawSoddyCircles(circles[0],circles[1],circles[2],true); } private function drawSoddyCircles(a:Sprite,b:Sprite,c:Sprite, create:Boolean=false):void { //the 4th circle is Soddy of the first 3 var d:Sprite = drawSoddyCircle(a,b,c,create); if(d.width > 1){ drawSoddyCircles(a,b,d,create); drawSoddyCircles(a,c,d,create); drawSoddyCircles(b,c,d,create); } } //redraw private function draw():void { //recycle circle sprites oldcircles = circles.slice(0); circles = new Vector.(); //copy first three interactive circles into new circles vector circles.push(oldcircles.shift()); circles.push(oldcircles.shift()); circles.push(oldcircles.shift()); //call recursive function to draw all circles drawSoddyCircles(circles[0],circles[1],circles[2],false); //clean up any stray circles for(var c:int = 0; c < numChildren; c++){ if(circles.indexOf(getChildAt(c)) == -1) removeChild(getChildAt(c)); } } //create an empty sprite and push it into circles vector private function addCircle(xPos:Number=0, yPos:Number=0, o:Number=1):Sprite { var c:Sprite = new Sprite(); c.alpha= o; c.x = xPos; c.y = yPos; addChild(c); circles.push(c); return c; } //just draw a circle private function drawCircle(circle:Sprite, r:Number):void { circle.graphics.clear(); circle.graphics.beginFill(0x000000); circle.graphics.drawCircle(0, 0, r); circle.graphics.endFill(); } private function drawSoddyCircle(A:Sprite, B:Sprite, C:Sprite,create:Boolean=true):Sprite { //calculate the necessary radii of the circles to be tangent var a:Number = Math.sqrt((B.x - C.x)*(B.x - C.x)+(B.y - C.y)*(B.y - C.y));//dist(circleA, circleC); var b:Number = Math.sqrt((A.x - C.x)*(A.x - C.x)+(A.y - C.y)*(A.y - C.y));//dist(circleA, circleC); var c:Number = Math.sqrt((A.x - B.x)*(A.x - B.x)+(A.y - B.y)*(A.y - B.y));//dist(circleA, circleB); //these are the radii: var a1:Number = (-a + b + c) / 2; var b1:Number = (a - b + c) / 2; var c1:Number = (a + b - c) / 2; var soddy:Sprite; //re-render the circles drawCircle(A, a1); drawCircle(B, b1); drawCircle(C, c1); //now go and figure out the soddy circle and draw it too... if(create || oldcircles.length == 0){ soddy = addCircle(); } else {//recycle circles :) soddy = oldcircles.shift(); circles.push(soddy); } drawSoddy(A,B,C, a1, b1, c1, soddy); return soddy; } private function drawSoddy(circleA:Sprite, circleB:Sprite, circleC:Sprite, R1:Number, R2:Number, R3:Number, soddy:Sprite):void { var x1:Number = circleA.x; var x2:Number = circleB.x; var x3:Number = circleC.x; var y1:Number = circleA.y; var y2:Number = circleB.y; var y3:Number = circleC.y; var ax:Number = 2 * (x2 - x1); var Ay:Number = 2 * (y2 - y1); var Ar:Number = 2 * (R2 - R1); var bx:Number = 2 * (x3 - x2); var By:Number = 2 * (y3 - y2); var Br:Number = 2 * (R3 - R2); var cx:Number = 2 * (x1 - x3); var Cy:Number = 2 * (y1 - y3); var Cr:Number = 2 * (R1 - R3); var Aa:Number = (Math.pow(x2, 2) - Math.pow(x1, 2)) + (Math.pow(y2, 2) - Math.pow(y1, 2)) + (Math.pow(R1, 2) - Math.pow(R2, 2)); var Bb:Number = (Math.pow(x3, 2) - Math.pow(x2, 2)) + (Math.pow(y3, 2) - Math.pow(y2, 2)) + (Math.pow(R2, 2) - Math.pow(R3, 2)); var Cc:Number = (Math.pow(x1, 2) - Math.pow(x3, 2)) + (Math.pow(y1, 2) - Math.pow(y3, 2)) + (Math.pow(R3, 2) - Math.pow(R1, 2)); var DAB:Number = ax * By - Ay * bx; var DAC:Number = ax * Cy - Ay * cx; var DBC:Number = bx * Cy - By * cx; var DET:Number; var radius:Number; var xPos:Number; var yPos:Number; if( Ar == 0 && Br == 0 ) { DET = ax *By - bx * Ay; xPos = (Aa * By - Bb * Ay ) / DET; yPos = (ax * Bb - Ay * Aa ) / DET; var p:Number = 1; var q:Number = 2 * R1; var r:Number = Math.pow(R1, 2) - Math.pow((x - x1), 2) - Math.pow((y - y1), 2); var d:Number = Math.pow(q, 2) - 4 * p * r; radius = (-q + Math.sqrt(d)) / 2; } if( DAB != 0 ) { var xr:Number = (Br * Ay - Ar * By) / DAB; var yr:Number = (Ar * bx - Br * ax) / DAB; var xc:Number = (Aa * By - Bb * Ay) / DAB; var yc:Number = (Bb * ax - Aa * bx) / DAB; var Zp:Number = Math.pow(xr, 2) + Math.pow(yr, 2) - 1; var zq:Number = 2 * xc * xr - 2 * xr * x3 + 2 * yc * yr - 2 * yr * y3 - 2 * R3; var zr:Number = Math.pow(xc, 2) - 2 * xc * x3 + Math.pow(x3, 2) + Math.pow(yc, 2) - 2 * yc * y3 + Math.pow(y3, 2) - Math.pow(R3, 2); DET = Math.pow(zq, 2) - 4 * Zp * zr; radius = (-zq - Math.sqrt(DET)) / 2 / Zp; xPos = xr * radius + xc; yPos = yr * radius + yc; } //position and draw the Soddy circle soddy.x = xPos; soddy.y = yPos; drawCircle(soddy, radius); } private function onDrag(event:MouseEvent):void { var dx:Number = event.target.mouseX; var dy:Number = event.target.mouseY; event.target.startDrag(); stage.addEventListener(MouseEvent.MOUSE_MOVE, onMove); stage.addEventListener(MouseEvent.MOUSE_UP, onDrop); } private function onMove(event:MouseEvent):void { draw(); event.updateAfterEvent(); } private function onDrop(event:MouseEvent):void { stopDrag(); stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMove); stage.removeEventListener(MouseEvent.MOUSE_UP, onDrop); } } }