In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

ボロノイ図を軽く解説

import net.hires.debug.Stats;

ボロノイ図描画
Quasimondo 先生が 2002 年に書いたコードの写経です。
ステージをクリックしてね。何度も何度もクリックしてね。
オリジナル:http://www.quasimondo.com/archives/000110.php
説明:http://aquioux.blog48.fc2.com/blog-entry-654.html
Get Adobe Flash player
by uwi 01 Dec 2009
/**
 * Copyright uwi ( http://wonderfl.net/user/uwi )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/8oFd
 */

// forked from Aquioux's ボロノイ図
package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	//import net.hires.debug.Stats;
	[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")]
	
	/**
	 * ボロノイ図描画
	 * Quasimondo 先生が 2002 年に書いたコードの写経です。
	 * ステージをクリックしてね。何度も何度もクリックしてね。
	 * オリジナル:http://www.quasimondo.com/archives/000110.php
	 * 説明:http://aquioux.blog48.fc2.com/blog-entry-654.html
	 */
	public class Main extends Sprite {
		private const STAGE_WIDTH:uint  = stage.stageWidth;
		private const STAGE_HEIGHT:uint = stage.stageHeight;

		private var dots:Array;		// Dot の配列
		private var sx:Array;		// 線開始座標(X座標)
		private var sy:Array;		// 線開始座標(Y座標)
		private var ex:Array;		// 線終了座標(X座標)
		private var ey:Array;		// 線終了座標(Y座標)
		
		private var dotLayer:Sprite;		// ドットのコンテナ
		private var lineLayer:Sprite;		// 線描画用レイヤ
		private var textField:TextField;	// "Click STAGE"
		
		
		public function Main():void {
			// 配列初期化
			dots = [];
			sx = [];
			sy = [];
			ex = [];
			ey = [];
			
			// コンテナ等
			addChild(dotLayer  = new Sprite());
			addChild(lineLayer = new Sprite());
			//addChild(new Stats());
			textField = new TextField();
			textField.defaultTextFormat = new TextFormat("_serif", 20, 0x000000);
			textField.text = "Click STAGE";
			textField.autoSize = TextFieldAutoSize.LEFT;
			textField.selectable = false;
			textField.x = (STAGE_WIDTH  - textField.width)  / 2;
			textField.y = (STAGE_HEIGHT - textField.height) / 2;
			addChild(textField);
			
			// 最初に Dot を3つ配置
			for (var i:int = 0; i < 3; i++) {
				addDot(Math.random() * STAGE_WIDTH, Math.random() * STAGE_HEIGHT);
			}
			
			// イベントリスナー
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
			stage.addEventListener(MouseEvent.CLICK, clickHandler);
		}
		

		// ENTER_FRAME
		private function enterFrameHandler(event:Event):void {
			moveDot();		// Dot の移動
			setVoronoi();	// ボロノイ境界線の計算
			drawVolonoi();	// ボロノイ境界線の描画
		}
		
		// Dot の移動
		private function moveDot():void {
			var n:uint = dots.length;
			for (var i:int = 0; i < n; i++) {
				var dot:Dot = dots[i];
				dot.update();
			}
		}
		
		// ボロノイ境界線の計算
        // 長い線分をつくってから、それぞれの交点を計算してトリミングしていきます。
		private function setVoronoi():void {
			var a:Number;
			var b:Number;
			var a0:Number;
			var b0:Number;
			var a1:Number;
			var b1:Number;
			var x:Number;
			var y:Number;
			var x0:Number;
			var y0:Number;
			var x1:Number;
			var y1:Number;
			
			var n:uint = dots.length;
			var m:uint = n + 3;
                // 各dotについて、dot同士と画面境界の線分をつくる
                // sx,syは線分の開始点, ex,eyは終端点とでも
			for (var i:int = 0; i < n; i++) {
				x0 = dots[i].x;
				y0 = dots[i].y;
				var idx1:int = i * m + i + 1; // 以降の線分は[i][i+1]~[i][m-1]の間に格納する
                        // 相手をするdot
                		for (var j:int = i + 1; j < n; j++) {
					x1 = dots[j].x;
					y1 = dots[j].y;
					
                                // 垂直二等分線を求める。
                                // aは傾き、bは切片
					if (x1 == x0) {
						a = 0;
					} else if (y1 == y0) {
						a = 10000;
					} else {
						a = -1 / ((y1 - y0) / (x1 - x0));
					}
					
					b = (y0 + y1) / 2 - a * (x0 + x1) / 2;
					
                                // 45度以下の傾きなら画面境界のx座標でぶったぎる
                                // それ以上の傾きならy座標でぶったぎる
					if (a > -1 && a <= 1) {
						sx[idx1] = 0;
						sy[idx1] = a * sx[idx1] + b;
						ex[idx1] = STAGE_WIDTH - 1;
						ey[idx1] = a * ex[idx1] + b;
					} else {
						sy[idx1] = 0;
						sx[idx1] = (sy[idx1] - b) / a;
						ey[idx1] = STAGE_HEIGHT - 1;
						ex[idx1] = (ey[idx1] - b) / a;
					}
					idx1++;
				}
                        // 画面境界
				sx[idx1] = 0;
				sy[idx1] = 0;
				ex[idx1] = STAGE_WIDTH;
				ey[idx1] = 0;
				idx1++;
				sx[idx1] = 0;
				sy[idx1] = 0;
				ex[idx1] = 0;
				ey[idx1] = STAGE_HEIGHT;
				idx1++;
				sx[idx1] = STAGE_WIDTH;
				sy[idx1] = 0;
				ex[idx1] = STAGE_WIDTH;
				ey[idx1] = STAGE_HEIGHT;
				idx1++;
				sx[idx1] = 0;
				sy[idx1] = STAGE_HEIGHT;
				ex[idx1] = STAGE_WIDTH;
				ey[idx1] = STAGE_HEIGHT;
			}
			
                // それぞれの線分たちを見てトリミングしていく
                // 各dotについて
			for (i = 0; i < n; i++) {
				x0 = dots[i].x;
				y0 = dots[i].y;
                        // 各線分(S1とする)について(jは線分をつくった相手のdotのインデックス)
				for (j = 0; j < m + 1; j++) {
					if (j != i) {
						if (j > i) {
							idx1 = i * m + j;
						} else {
							idx1 = j * m + i;
						}
                                        // -Number.MAX_VALUEだったらトリミング対象にしない(後述)
	            				if (sx[idx1] > -Number.MAX_VALUE) {
                                                // S1の傾き・切片をとる
							a0 = (ey[idx1] - sy[idx1]) / (ex[idx1] - sx[idx1]);
							b0 = sy[idx1] - a0 * sx[idx1];

                                                // dotについてできているもう一個の線分(S2)をとってくる。
                                                // S1-S2間でS2をトリミングする。S1は、iとjが逆になっているときトリミングされる。
							for (var k:int = i + 1; k < m + 1; k++) {
								if (k != j) {
									var idx2:int = i * m + k;
									if (sx[idx2] > -Number.MAX_VALUE) {
                                                                        // S2の傾き・切片をとる
										a1 = (ey[idx2] - sy[idx2]) / (ex[idx2] - sx[idx2]);
										b1 = sy[idx2] - a1 * sx[idx2];
                                                                        // S1とS2の交点を求める
										x = -(b1 - b0) / (a1 - a0);
								                y = a0 * x + b0;

                                                                        // S1からみて、dotとS2の開始点が反対方向にあれば
										if ((a0 * x0 + b0 - y0) * (a0 * sx[idx2] + b0 - sy[idx2]) < 0) {
                                                                        // S2の開始点をS1とS2の交点にする(トリミング) 
											sx[idx2] = x;
											sy[idx2] = y;
										}
                                                                        // S1からみて、dotとS2の終端点が反対方向にあれば 
										if ((a0 * x0 + b0 - y0) * (a0 * ex[idx2] + b0 - ey[idx2]) < 0) {
                                                                                // この上のトリミングが行われている場合、
                                                                                // S1からみて、S2の全体がdotと逆方向にあるため、
                                                                                // S2はもう表示する必要がない。
                                                                                // よってトリミング対象から外す。
                                                                                        if(sx[idx2] == x) {
												sx[idx2] = -Number.MAX_VALUE;
											} else {
                                                                                        // S2の開始点をS1とS2の交点にする(トリミング) 
												ex[idx2] = x;
												ey[idx2] = y;
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		
		// ボロノイ境界線の描画
		private function drawVolonoi():void {
			lineLayer.graphics.clear();
			lineLayer.graphics.lineStyle(0, 0x666666);

			var n:uint = dots.length;
			var m:uint = n + 3;
			for (var i:int = 0; i < n; i++) {
				var idx:uint = i * m + i + 1;
				for (var j:int = i + 1; j < m + 1; j++) {
					if (sx[idx] > -Number.MAX_VALUE) {
						lineLayer.graphics.moveTo(sx[idx], sy[idx]);
						lineLayer.graphics.lineTo(ex[idx], ey[idx]);
					}
					idx++;
				}
			}
		}
		
		
		// マウスイベント
		private function clickHandler(event:MouseEvent):void {
			if (textField) {
				removeChild(textField);
				textField = null;
			}
			addDot(mouseX, mouseY);
		}

		// Dot をコンテナ上に追加する
		private function addDot(x:Number, y:Number):void {
			var dot:Dot = new Dot();
			dot.x = x;
			dot.y = y;
			dots.push(dot);
			dotLayer.addChild(dot);
		}
	}
}


	import flash.display.Shape;
	import flash.events.Event;
	
	class Dot extends Shape {
		private const RADIUS:uint = 3;
		private var vx:Number = 2.0;	// X軸ベロシティ
		private var vy:Number = 2.0;	// Y軸ベロシティ
		private var sw:Number;			// ステージ幅
		private var sh:Number;			// ステージ高
		
		public function Dot() {
			graphics.beginFill(0xCC0033);
			graphics.drawCircle(0, 0, RADIUS);
			graphics.endFill();
			addEventListener(Event.ADDED_TO_STAGE, addHandler);
		}
		
		private function addHandler(event:Event):void {
			removeEventListener(Event.ADDED_TO_STAGE, addHandler);
			sw = stage.stageWidth;
			sh = stage.stageHeight;
			if (Math.random() < 0.5) {
				vx *= -1;
			}
			if (Math.random() < 0.5) {
				vy *= -1;
			}
		}
		
		// 移動
		public function update():void {
			x += vx;
			y += vy;

			if (x < RADIUS) {
				x = RADIUS;
				vx *= -1;
			}
			if (x > sw - RADIUS) {
				x = sw - RADIUS;
				vx *= -1;
			}
			if (y < RADIUS) {
				y = RADIUS;
				vy *= -1;
			}
			if (y > sh - RADIUS) {
				y = sh - RADIUS;
				vy *= -1;
			}
		}
	}