/**
* Copyright bradsedito ( http://wonderfl.net/user/bradsedito )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/qINA
*/
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 = 30;
private const INTERVAL_X:Number = 1.5;
private const INTERVAL_Y:Number = 1.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("BradSedito");
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;
}
}
//}