Fortuneのアルゴリズムの可視化
ボロノイ図を作図するためのFortuneのアルゴリズムを可視化。
↓ の説明図そのまんま
http://en.wikipedia.org/wiki/Fortune%27s_algorithm
@author
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
/**
* ボロノイ図を作図するためのFortuneのアルゴリズムを可視化。
* ↓ の説明図そのまんま
* http://en.wikipedia.org/wiki/Fortune%27s_algorithm
* @author
*/
[SWF(frameRate="60")]
public class TestParabola extends Sprite
{
private var bmd:BitmapData;
private var bmp:Bitmap;
private const _W:Number = stage.stageWidth;
private const _H:Number = stage.stageHeight;
private var focuses:Vector.<Point>; //放物線の焦点=ボロノイ母点
private var directrix:Number; //放物線の準線=Fortuneアルゴリズムのsweepline
public function TestParabola()
{
bmd = new BitmapData(_W, _H, true, 0x0);
bmp = new Bitmap(bmd);
addChild(bmp);
focuses = new Vector.<Point>(40);
resetFocuses();
directrix = 0.0;
addEventListener(Event.ENTER_FRAME, update);
stage.addEventListener(MouseEvent.CLICK, function(e:Event):void {
hasEventListener(Event.ENTER_FRAME)? removeEventListener(Event.ENTER_FRAME, update): addEventListener(Event.ENTER_FRAME, update);
});
}
// 母点の初期化。ランダムに点を打つ。
private function resetFocuses():void
{
for (var i:int = 0; i < focuses.length; i++)
{
focuses[i] = new Point(_W * Math.random() , _H * Math.random());
}
}
private function update(e:Event):void
{
graphics.clear();
graphics.lineStyle(0);
var paras:Vector.<Parabola> = new Vector.<Parabola>();
var para:Parabola;
for each(var focus:Point in focuses)
{
//各点を描画
graphics.drawCircle(focus.x, focus.y, 2.5);
//sweeplineより下の母点は無視する
if (focus.y > directrix) continue;
//母点を焦点、sweeplineを準線とする放物線を生成
/* 放物線は焦点と準線それぞれからの距離が等しい点の集合なので
* 準線を共有する2つの放物線の交点はそれぞれの焦点からの距離が等しくなる
* よって、このような交点の集合がボロノイ境界となる
* */
para = Parabola.createAsQuadricCurve(focus, directrix);
if (para)
{
paras.push(para);
}
}
bmd.lock();
/* 放物線の交点は複数考えられるが、sweeplineにより上から走査するので
* 最もsweeplineに近い部分とその交点だけ考慮すればボロノイ境界になる
* その様子が波打ち際に見えるからbeach lineと呼ぶんだと思う。
* */
var currPara:Parabola;
var nextPara:Parabola = paras.length > 0?paras[0]:null;
var pts:Vector.<Point>;
var maxVal:Number = paras.length > 0?paras[0].getY(0):null;
var minVal:Number = 0.0;
var tmpVal:Number = 0.0;
//beach line を引くためにx=0から走査する。
//最初の放物線はx=0において最も下にあるそれ
for each (para in paras)
{
tmpVal = para.getY(0); //放物線のx=0におけるyの値を求めている
if (maxVal < tmpVal)
{
maxVal = tmpVal;
nextPara = para;
}
}
currPara = nextPara;
if (currPara) currPara.render(graphics, 0, _W);
for (;; )
{
maxVal = Number.MAX_VALUE;
for each (para in paras)
{
if (currPara == para) continue;
//現在の放物線と他の放物線との交点を求め
//交点のxが最も前回の交点に近い点がボロノイ境界上の点になる
pts = Parabola.getCrossPoints(currPara, para);
for each( var pt:Point in pts)
{
tmpVal = pt.x;
if (tmpVal > minVal && tmpVal < maxVal)
{
maxVal = tmpVal;
nextPara = para; //交点で最も下の放物線が入れ替わる
}
}
}
if (maxVal < _W)
{
bmd.setPixel32(maxVal, currPara.getY(maxVal), 0xff000000);
minVal = maxVal;
currPara = nextPara;
currPara.render(graphics, 0, _W);
}
else
{
break;
}
}
//sweeplineを引く
graphics.lineStyle(0, 0xff0000, 0.75);
graphics.moveTo(0, directrix);
graphics.lineTo(_W, directrix);
directrix += 1.0;
if (directrix > 2 * _H)
{
resetFocuses();
directrix = 0.0;
bmd.fillRect(bmd.rect, 0x0);
}
bmd.unlock();
}
}
}
import flash.display.Graphics;
import flash.geom.Point;
/**
* ...
* @author
*/
class Parabola
{
private var _a:Number;
private var _b:Number;
private var _c:Number;
//private var _d:Number;
/**
* Dy = Ax2 + Bx + C の形式に合わせて、各係数を引数に放物線を生成
* @param coefY 0を除く実数
* @param coefX2 0を除く実数
* @param coefX
* @param coef1
*/
public function Parabola(coefY:Number = 1.0, coefX2:Number = 1.0, coefX:Number = 0.0, coef1:Number = 0.0)
{
var inv:Number;
inv = 1.0 / coefY;
_a = coefX2 * inv;
_b = coefX * inv;
_c = coef1 * inv;
//_d = 1.0;
}
/**
*
* @param focus focus Point
* @param directrix y value of directrix line
* @return yの係数が0になる(x=focux.xの直線になる)場合は容赦なくnullを返す
*/
public static function createAsQuadricCurve(focus:Point, directrix:Number):Parabola
{
var x0:Number = focus.x;
var y0:Number = focus.y;
var c:Number = 0.5 * ( -x0 * x0 + directrix * directrix - y0 * y0);
var d:Number = directrix - y0;
if (d != 0)
{
return new Parabola(d, -0.5, x0, c);
}
else
{
return null;
}
}
/**
* 放物線上のxに対応するyの値を返す。
* @param x
* @return
*/
public function getY(x:Number):Number
{
return _a * x * x + _b * x + _c;
}
/**
* 放物線上のyに対応するxをVectorで返す。Vector.lengthは2以下。
* @param y
* @return
*/
public function getX(y:Number):Vector.<Number>
{
return formula(_a, _b, _c - y);
}
/**
* 解の公式から ax2+bx+c=0 のxを求める。
* 2次方程式の解ならVector.length=2(重解含む)
* 1次方程式の解(a=0)ならVector.length=1
* 解がなければVector.length=0
* @param a
* @param b
* @param c
* @return
*/
private static function formula(a:Number, b:Number, c:Number):Vector.<Number>
{
var xVec:Vector.<Number>;
if (a != 0)
{
var det:Number = b * b - 4 * a * c;
var aInv:Number = 0.5 / a;
var x0:Number = -b * aInv;
if (det > 0)
{
xVec = new Vector.<Number>(2, true);
det = Math.sqrt(det) * aInv;
xVec[0] = x0 - det;
xVec[1] = x0 + det;
}
else if (det < 0)
{
xVec = new Vector.<Number>(0, true);
}
else
{
xVec = new Vector.<Number>(2, true);
xVec[0] = x0;
xVec[1] = x0;
}
}
else
{
if (b != 0)
{
xVec = new Vector.<Number>(1, true);
xVec[0] = -c/b;
}
else
{
xVec = new Vector.<Number>(0, true);
}
}
return xVec;
}
public static function getCrossPoints(p1:Parabola, p2:Parabola):Vector.<Point>
{
var subA:Number = p1._a - p2._a;
var subB:Number = p1._b - p2._b;
var subC:Number = p1._c - p2._c;
var crossXVec:Vector.<Number>;
var pts:Vector.<Point>;
crossXVec = formula(subA, subB, subC);
pts = new Vector.<Point>(crossXVec.length, true);
for (var i:int = 0; i < pts.length; i++)
{
var __x:Number = crossXVec[i];
pts[i] = new Point(__x, p1.getY(__x));
}
return pts;
}
/**
* for debug
* @param graphics
* @param fromX
* @param toX
*/
public function render(graphics:Graphics, fromX:Number, toX:Number):void
{
var i:uint = 100;
var __x:Number = fromX;
var dx:Number = (toX - fromX) / i;
graphics.lineStyle(0, 0xff, 0.5);
graphics.moveTo(__x, getY(__x));
while (i-- > 0)
{
__x += dx;
graphics.lineTo(__x, getY(__x));
}
}
}