/** * Voronoi Letter * Usase : key down "Alphabet" or "Number" or "Number of tenkey". * if no reaction, click stage and key down. * 使い方 : アルファベットキー、数字キー、テンキーの数字キー、いずれかを押してください。 * 反応しないときは、一度ステージをクリックしてからキーを押してください。 * * Fortune's algorithm - Wikipedia, the free encyclopedia * @see http://en.wikipedia.org/wiki/Fortune's_algorithm * * Controul > Speedy Voronoi diagrams in as3/flash * @see http://blog.controul.com/2009/05/speedy-voronoi-diagrams-in-as3flash/ * * @see http://wonderfl.net/c/3TKq * @see http://wonderfl.net/c/iNy0 * * 解説 : http://aquioux.blog48.fc2.com/blog-entry-735.html * * @author Aquioux(Yoshida, Akio) */ package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Graphics; import flash.display.GraphicsEndFill; import flash.display.GraphicsPath; import flash.display.GraphicsPathCommand; import flash.display.GraphicsSolidFill; import flash.display.GraphicsStroke; import flash.display.IGraphicsData; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")] public class Main extends Sprite { // 母点生成用定数 private const FONT_SIZE:uint = 48; private const INTERVAL_X:Number = 9.5; private const INTERVAL_Y:Number = 9.5; private const W:Number = stage.stageWidth / INTERVAL_X; private const H:Number = stage.stageHeight / INTERVAL_Y; private const CX:Number = stage.stageWidth / 2; private const CY:Number = stage.stageHeight / 2; // 母点動作用定数 private const SPRING:Number = 0.05; // バネ係数 private const FRICTION:Number = 0.79; // 摩擦係数 private const PI2:Number = Math.PI * 2; private const FREQ:uint = 8; private const FREQ2:uint = FREQ * 2 + 1; private const OFFSET:uint = 3; private const OFFSET2:uint = OFFSET * 2 + 1; // 文字を転写する BitmapData private var bmd_:BitmapData; // 母点格納 private var generators_:Vector.<Number2>; // ボロノイ図作図クラス private var fortune_:Fortune; // キャンバス private var canvas_:Sprite; // GraphicsData private var graphicsData:Vector.<IGraphicsData>; private var path:GraphicsPath; // コンストラクタ public function Main() { // ボロノイ辺描画用 GraphicsData 生成 createGraphicsData(); // キャンバス生成 canvas_ = new Sprite(); addChild(canvas_); // ボロノイ辺作図クラス生成 fortune_ = new Fortune(); // 母点生成 createGenerators("A"); addEventListener(Event.ENTER_FRAME, enterFrameHandler); stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler); } // 母点生成 private function createGenerators(letter:String):void { createGenerator(letter); fortune_.points = generators_; } private function createGenerator(letter:String):void { const TRANSPARENT:uint = 0x00000000; const COLOR:uint = 0xFF000000; // TextField の生成 var tFormat:TextFormat = new TextFormat("_sans", FONT_SIZE, COLOR); var tField:TextField = new TextField(); tField.defaultTextFormat = tFormat; tField.text = letter; tField.autoSize = TextFieldAutoSize.LEFT; // TextField を BitmapData へ転写 var bmd1:BitmapData = new BitmapData(tField.textWidth, tField.textHeight, true, TRANSPARENT); bmd1.draw(tField, new Matrix(1, 0, 0, 1, -2, -2)); // 上記 BitmapData から 文字部分のみを抜き出す var rect:Rectangle = bmd1.getColorBoundsRect(0xFF000000, TRANSPARENT, false); // 文字部分のサイズの BitmapData へ転写 var bmd2:BitmapData = new BitmapData(rect.width, rect.height, true, TRANSPARENT); bmd2.draw(bmd1, new Matrix(1, 0, 0, 1, -rect.x, -rect.y)); // 上記 BitmapData をスキャン対象サイズの BitmapData へ転写 bmd_ = new BitmapData(W, H, true, TRANSPARENT); bmd_.draw(bmd2, new Matrix(1, 0, 0, 1, (W - bmd2.width) / 2, (H - bmd2.height) / 2)); // 使用済みの BitmapData を解放 bmd1.dispose(); bmd2.dispose(); // BitmapData をスキャン var flg:Boolean = false; generators_ = new Vector.<Number2>(); for (var y:int = 0; y < H; y++) { for (var x:int = 0; x < W; x++) { var color:uint = bmd_.getPixel32(x, y); var alpha:uint = (color >> 24) & 0xFF; if (alpha > 0x79) { flg = true; } else { if (Math.random() < 0.01) flg = true; } if (flg) { var generator:Number2 = new Number2(); generator.localX = x * INTERVAL_X + int(Math.random() * OFFSET2) - OFFSET; generator.localY = y * INTERVAL_Y + int(Math.random() * OFFSET2) - OFFSET; var radian:Number = Math.random() * PI2; generator.x = Math.cos(radian) * 300 + CX; generator.y = Math.sin(radian) * 300 + CY; generators_.push(generator); flg = false; } } } // 使用済みの BitmapData を解放 bmd_.dispose(); } // ボロノイ辺描画用 GraphicsData 生成 private function createGraphicsData():void { // 線の定義 var stroke:GraphicsStroke = new GraphicsStroke(); stroke.thickness = 0; stroke.fill = new GraphicsSolidFill(0x000000); // パスの定義 path = new GraphicsPath(null, null); // 描画データをパッケージング graphicsData = new Vector.<IGraphicsData>(); graphicsData.push(stroke); graphicsData.push(path); graphicsData.push(new GraphicsEndFill()); } private function enterFrameHandler(event:Event):void { // 母点位置更新 var n:uint = generators_.length; for (var i:int = 0; i < n; i++) { generatorUpdate(generators_[i]); } // ボロノイ点更新 var points:Vector.<Number2> = fortune_.compute(); // ボロノイ辺の描画 var start:Number2; // ボロノイ辺の起点 var end:Number2; // ボロノイ辺の終点 var commands:Vector.<int> = new Vector.<int>(); var data:Vector.<Number> = new Vector.<Number>(); n = points.length; for (i = 0; i < n; i += 2) { start = points[i]; end = points[i + 1]; commands.push(GraphicsPathCommand.MOVE_TO, GraphicsPathCommand.LINE_TO); data.push(start.x, start.y, end.x, end.y); } path.commands = commands; path.data = data; var g:Graphics = canvas_.graphics; g.clear(); g.drawGraphicsData(graphicsData); } // 各母点の位置更新 private function generatorUpdate(generator:Number2):void { var localX:Number = generator.localX; var localY:Number = generator.localY; var vx:Number = generator.vx; var vy:Number = generator.vy; var x:Number = generator.x; var y:Number = generator.y; var diff:Number = Math.floor(Math.random() * FREQ2) - FREQ; var radian:Number = Math.random() * PI2; var dx:Number = localX + Math.cos(radian) * diff; var dy:Number = localY + Math.sin(radian) * diff; vx += (dx - x) * SPRING; vy += (dy - y) * SPRING; vx *= FRICTION; vy *= FRICTION; x += vx; y += vy; generator.x = x; generator.y = y; generator.vx = vx; generator.vy = vy; } private function keyDownHandler(e:KeyboardEvent):void { var flg:Boolean = false; var keyCode:int = e.keyCode; if ((keyCode >= 48) && (keyCode <= 57)) flg = true; // 数字キー(キーボード) if ((keyCode >= 96) && (keyCode <= 105)) flg = true; // 数字キー(テンキー) if ((keyCode >= 65) && (keyCode <= 91)) flg = true; // アルファベットキー(キーボード) if (flg) createGenerators(String.fromCharCode(e.charCode)); } } } //package { /*public*/ class Number2 { // 既定座標 public function get localX():Number { return _localX; } public function set localX(value:Number):void { _localX = value; } private var _localX:Number; public function get localY():Number { return _localY; } public function set localY(value:Number):void { _localY = value; } private var _localY:Number; // 現在座標 public function get x():Number { return _x; } public function set x(value:Number):void { _x = value; } private var _x:Number; public function get y():Number { return _y; } public function set y(value:Number):void { _y = value; } private var _y:Number; // 速度 public function get vx():Number { return _vx; } public function set vx(value:Number):void { _vx = value; } private var _vx:Number = 0; public function get vy():Number { return _vy; } public function set vy(value:Number):void { _vy = value; } private var _vy:Number = 0; public function Number2() { } } //} //package { /*public*/ class Arc { public var p : Number2; public var next : Arc; public var prev : Arc; // public var s0 : Seg; // public var s1 : Seg; public var v0 : Number2; public var v1 : Number2; // Circle event data : public var left : Arc; public var right : Arc; public var endX : Number; public var endP : Number2; } //} //package { /* * An implementation of Steve Fortune's algorithm for computing voronoi diagrams. * @author Matt Brubeck * * Modifications and optimisations: * ** Data structures are adapted to what's available to as3. * ** The beachline intersection lookup is optimised, * intersecting begins only near the probable intersection. * ** Functions are massively inlined. * * Todo list: * ** Provide for bounding box intersection, * ** Inline the 'intersection' method. * ** Design a good datastructure for the solution, which would * ideally contain enough neighbourhood info for region rendering, * extrusion and intrusion of edges and pathfinding. * * Original c++ code * http://www.cs.hmc.edu/~mbrubeck/voronoi.html */ /*public*/ class Fortune { ///////// ///////// ///////// Datastructures. // Points are provided as a vector, which is sorted by x (increasing) before the sweep. public var points : Vector.<Number2>; // Bounding box. private var x0 : Number; // private var x1 : Number; // private var y0 : Number; // private var y1 : Number; // Root of the frontline and next arc to be removed. private var root : Arc; private var next : Arc; // Reusable objects and pools. private var o : Number2 = new Number2; private static var arcPoolD : Arc; //////// //////// //////// API. /** * Computes the Voronoi decomposition of the plane. * @return A vector or vertices in pairs, describing segments ready for drawing. */ public function compute () : Vector.<Number2> { // // // Clear the output. var out : Vector.<Number2> = new Vector.<Number2>, len : int = 0; // // // Clear the state. root = null; next = null; // // // Read the pools. var key : * , arcPool : Arc = arcPoolD; // // // Vars: var i : int, j : int, w : Number, x : Number, a : Arc, b : Arc, // s : Seg, z : Number2, p : Number2 = points [ 0 ], points : Vector.<Number2> = points, n : int = points.length, // Circle events check inlined. circle : Boolean, eventX : Number, c : Arc, d : Arc, aa : Number2, bb : Number2, cc : Number2, A : Number, B : Number, C : Number, D : Number, E : Number, F : Number, G : Number; // // y0 = p.y; // y1 = p.y; // // // Sort points by x coord, compute the bounding box. ///// Currently insertion sort. Quicksort? w = points [ 0 ].x; for ( i = 1; i < n; i ++ ) { p = points [ i ]; // Keep track of the bounding box. // if ( p.y < y0 ) // y0 = p.y; // else if ( p.y > y1 ) // y1 = p.y; // Insertion sort. x = p.x; if ( x < w ) { j = i; while ( ( j > 0 ) && ( points [ int ( j - 1 ) ].x > x ) ) { points [ j ] = points [ int ( j - 1 ) ]; j--; } points [ j ] = p; } else w = x; } // Get x bounds. x0 = points [ 0 ].x; // x1 = points [ n - 1 ].x; // Add margins to the bounding box. /* var dx : Number = (x1 - x0 + 1) / 5.0, dy : Number = dy = (y1 - y0 + 1) / 5.0; x0 -= dx; x1 += dx; y0 -= dy; y1 += dy; // trace ( x0, x1, y0, y1 ); //*/ // // // Process. i = 0; p = points [ 0 ]; x = p.x; for ( ;; ) { // // Check circle events. ///////////////////////// if ( a ) { // Check for arc a. circle = false; if ( a.prev && a.next ) { aa = a.prev.p, bb = a.p, cc = a.next.p; // Algorithm from O'Rourke 2ed p. 189. A = bb.x - aa.x, B = bb.y - aa.y, C = cc.x - aa.x, D = cc.y - aa.y; // Check that bc is a "right turn" from ab. if ( A * D - C * B <= 0 ) { E = A * ( aa.x + bb.x ) + B * ( aa.y + bb.y ), F = C * ( aa.x + cc.x ) + D * ( aa.y + cc.y ), G = 2 * ( A * ( cc.y - bb.y ) - B * ( cc.x - bb.x ) ); // Check for colinearity. // if ( G > 0.000000001 || G < -0.000000001 ) if ( G ) { // Point o is the center of the circle. o.x = ( D * E - B * F ) / G; o.y = ( A * F - C * E ) / G; // o.x plus radius equals max x coordinate. A = aa.x - o.x; B = aa.y - o.y; eventX = o.x + Math.sqrt ( A * A + B * B ); if ( eventX >= w ) circle = true; } } } // Remove from queue. if ( a.right ) a.right.left = a.left; if ( a.left ) a.left.right = a.right; if ( a == next ) next = a.right; // Record event. if ( circle ) { a.endX = eventX; if ( a.endP ) { a.endP.x = o.x; a.endP.y = o.y; } else { a.endP = o; o = new Number2; } d = next; if ( !d ) { next = a; } else for ( ;; ) { if ( d.endX >= eventX ) { a.left = d.left; if ( d.left ) d.left.right = a; if ( next == d ) next = a; a.right = d; d.left = a; break; } if ( !d.right ) { d.right = a; a.left = d; a.right = null; break; } d = d.right; } } else { a.endX = NaN; a.endP = null; a.left = null; a.right = null; } // Push next arc to check. if ( b ) { a = b; b = null; continue; } if ( c ) { a = c; c = null; continue; } a = null; } ////////////////////////////////////////////////// // if ( next && next.endX <= x ) { // // Handle next circle event. // Get the next event from the queue. /////////// a = next; next = a.right; if ( next ) next.left = null; a.right = null; //DEBUG*/ Debug.frontRemove ( a, root ); // Start a new edge. // s = new Seg; // s.start = a.endP; // Remove the associated arc from the front. if ( a.prev ) { a.prev.next = a.next; // a.prev.s1 = s; a.prev.v1 = a.endP; } if ( a.next ) { a.next.prev = a.prev; // a.next.s0 = s; a.next.v0 = a.endP; } // Finish the edges before and after a. /* if ( a.s0 && !a.s0.done ) { a.s0.done = true; // a.s0.end = a.endP; out [ len ++ ] = a.s0.start; out [ len ++ ] = a.endP; } if ( a.s1 && !a.s1.done ) { a.s1.done = true; // a.s1.end = a.endP; out [ len ++ ] = a.s1.start; out [ len ++ ] = a.endP; } */ if ( a.v0 ) { out [ len ++ ] = a.v0; a.v0 = null; out [ len ++ ] = a.endP; } if ( a.v1 ) { out [ len ++ ] = a.v1; a.v1 = null; out [ len ++ ] = a.endP; } // Keep a ref for collection. d = a; // Recheck circle events on either side of p: w = a.endX; if ( a.prev ) { b = a.prev; a = a.next; } else { a = a.next; b = null; } c = null; // Collect. d.v0 = null; d.v1 = null; d.p = null; d.prev = null; d.endX = NaN; d.endP = null; d.next = arcPool; arcPool = d; ////////////////////////////////////////////////// // } else { if ( !p ) break; // // Handle next site event. ////////////////////// if ( !root ) { // root = new Arc; if ( arcPool ) { root = arcPool; arcPool = arcPool.next; root.next = null; } else root = new Arc; root.p = p; //DEBUG*/ Debug.frontInsert ( root, root ); } else { z = new Number2; // Find the first arc with a point below p, // and start searching for the intersection around it. a = root.next; if ( a ) { while ( a.next ) { a = a.next; if ( a.p.y >= p.y ) break; } // Find the intersecting curve. intersection ( a.prev.p, a.p, p.x, z ); if ( z.y <= p.y ) { // Search for the intersection to the south of i. while ( a.next ) { a = a.next; intersection ( a.prev.p, a.p, p.x, z ); if ( z.y >= p.y ) { a = a.prev; break; } } } else { // Search for the intersection above i. a = a.prev; while ( a.prev ) { a = a.prev; intersection ( a.p, a.next.p, p.x, z ); if ( z.y <= p.y ) { a = a.next; break; } } } } else a = root; // New parabola will intersect arc a. Duplicate a. if ( a.next ) { // b = new Arc; if ( arcPool ) { b = arcPool; arcPool = arcPool.next; b.next = null; } else b = new Arc; b.p = a.p; b.prev = a; b.next = a.next; a.next.prev = b; a.next = b; } else { // b = new Arc; if ( arcPool ) { b = arcPool; arcPool = arcPool.next; b.next = null; } else b = new Arc; b.p = a.p; b.prev = a; a.next = b; } // a.next.s1 = a.s1; a.next.v1 = a.v1; // Find the point of intersection. z.y = p.y; z.x = ( a.p.x * a.p.x + ( a.p.y - p.y ) * ( a.p.y - p.y ) - p.x * p.x ) / ( 2 * a.p.x - 2 * p.x ); // Add p between i and i->next. // b = new Arc; if ( arcPool ) { b = arcPool; arcPool = arcPool.next; b.next = null; } else b = new Arc; b.p = p; b.prev = a; b.next = a.next; a.next.prev = b; a.next = b; a = a.next; // Now a points to the new arc. // Add new half-edges connected to i's endpoints. /* s = new Seg; s.start = z; a.prev.s1 = a.s0 = s; s = new Seg; s.start = z; a.next.s0 = a.s1 = s; */ a.prev.v1 = z; a.next.v0 = z; a.v0 = z; a.v1 = z; // Check for new circle events around the new arc: b = a.next; a = a.prev; c = null; w = p.x; //DEBUG*/ Debug.frontInsert ( a, root ); } ////////////////////////////////////////////////// // i ++; if ( i >= n ) { p = null; x = Number.MAX_VALUE; } else { p = points [ i ]; x = p.x; } } } //*/ /* // Clean up dangling edges. // Advance the sweep line so no parabolas can cross the bounding box. x = x1; x = x1 + ( x1 - x0 ) + ( y1 - y0 ); x *= 2; // Extend each remaining segment to the new parabola intersections. var arc : Arc = root; for ( ;; ) { if ( arc.s1 ) arc.s1.finish ( intersection ( arc.p, arc.next.p, x, new Number2 ) ); arc = arc.next; if ( !arc.next ) break; } //*/ // // // Store the pools. arcPoolD = arcPool; // // // Return the result ready for drawing. return out; } /** * Where do two parabolas intersect? * @param p0 A Number2 object describing the site for the first parabola. * @param p1 A Number2 object describing the site for the second parabola. * @param l The location of the sweep line. * @param res A Number2 object in which to store the intersection. * @return The point of intersection. */ public function intersection ( p0 : Number2, p1 : Number2, l : Number, res : Number2 ) : Number2 { var p : Number2 = p0, ll : Number = l * l; if ( p0.x == p1.x ) res.y = ( p0.y + p1.y ) / 2; else if ( p1.x == l ) res.y = p1.y; else if ( p0.x == l ) { res.y = p0.y; p = p1; } else { // Use the quadratic formula. var z0 : Number = 0.5 / ( p0.x - l ); // 1 / ( 2*(p0.x - l) ) var z1 : Number = 0.5 / ( p1.x - l ); // 1 / ( 2*(p1.x - l) ) var a : Number = z0 - z1; var b : Number = -2 * ( p0.y * z0 - p1.y * z1 ); var c : Number = ( p0.y * p0.y + p0.x * p0.x - ll ) * z0 - ( p1.y * p1.y + p1.x * p1.x - ll ) * z1; res.y = ( - b - Math.sqrt ( b * b - 4 * a * c ) ) / ( 2 * a ); } // Plug back into one of the parabola equations. res.x = ( p.x * p.x + ( p.y - res.y ) * ( p.y - res.y ) - ll ) / ( 2 * p.x - 2 * l ); return res; } } //} Voronoi Letter