Delaunay Letter
Delaunay Letter
* Usase : key down "Alphabet" or "Number" or "Number of tenkey", and move mouse on stage.
* if no reaction, click stage and key down.
* 使い方 : アルファベットキー、数字キー、テンキーの数字キー、いずれかを押してください。そしてマウスを動かしてください。
* 反応しないときは、一度ステージをクリックしてからキーを押してください。
*
* Delaunay triangulation Algorithm by nicoptere
* ドロネー三角形分割のアルゴリズムは nicoptere さんによるクラスで実現されています。
* Delaunay.as, DelaunayPoint.as, DelaunayEdge.as, DelaunayTriangle.as
* @see http://en.nicoptere.net/?p=10
*
* 参考: 「.fla 2」 より "YuruYurer"
* http://www.amazon.co.jp/gp/product/4862670717?ie=UTF8&tag=laxcomplex-22
*
* 解説:http://aquioux.net/blog/2010/07/23/delaunay-letter/
* @author Aquioux(Yoshida, Akio)
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/9aZG
*/
/**
* Delaunay Letter
* Usase : key down "Alphabet" or "Number" or "Number of tenkey", and move mouse on stage.
* if no reaction, click stage and key down.
* 使い方 : アルファベットキー、数字キー、テンキーの数字キー、いずれかを押してください。そしてマウスを動かしてください。
* 反応しないときは、一度ステージをクリックしてからキーを押してください。
*
* Delaunay triangulation Algorithm by nicoptere
* ドロネー三角形分割のアルゴリズムは nicoptere さんによるクラスで実現されています。
* Delaunay.as, DelaunayPoint.as, DelaunayEdge.as, DelaunayTriangle.as
* @see http://en.nicoptere.net/?p=10
*
* 参考: 「.fla 2」 より "YuruYurer"
* http://www.amazon.co.jp/gp/product/4862670717?ie=UTF8&tag=laxcomplex-22
*
* 解説:http://aquioux.net/blog/2010/07/23/delaunay-letter/
* @author Aquioux(Yoshida, Akio)
*/
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.GraphicsEndFill;
import flash.display.GraphicsSolidFill;
import flash.display.GraphicsStroke;
import flash.display.GraphicsTrianglePath;
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 = 14;
private const INTERVAL_Y:Number = 14;
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 DISTANCE_OF_REACTION:uint = 150; // マウスに反応する距離
private const PI2:Number = Math.PI * 2;
private const SCALE:uint = 25;
private const OFFSET:uint = 2;
private const OFFSET2:uint = OFFSET * 2 + 1;
// 文字を転写する BitmapData
private var bmd_:BitmapData;
// 母点格納
private var generators_:Vector.<Point>;
// キャンバス
private var canvas_:Sprite;
// GraphicsData
private var graphicsData:Vector.<IGraphicsData>;
private var path:GraphicsTrianglePath;
// コンストラクタ
public function Main() {
// ドロネー辺描画用 GraphicsData 生成
createGraphicsData();
// キャンバス生成
canvas_ = new Sprite();
addChild(canvas_);
// 母点生成
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.<Point>();
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.005) flg = true;
}
if (flg) {
var generator:ExPoint = new ExPoint();
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 as Point);
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 GraphicsTrianglePath();
// 描画データをパッケージング
graphicsData = new Vector.<IGraphicsData>();
graphicsData.push(stroke);
graphicsData.push(path);
graphicsData.push(new GraphicsEndFill());
}
private function enterFrameHandler(event:Event):void {
// 母点位置更新
var mousePoint:Point = new Point(mouseX, mouseY);
var n:uint = generators_.length;
for (var i:int = 0; i < n; i++) {
generatorUpdate(generators_[i] as ExPoint, mousePoint);
}
var vertices:Vector.<Number> = new Vector.<Number>();
for each (var t:DelaunayTriangle in Delaunay.Triangulate(generators_)) {
vertices.push(t.p1.x, t.p1.y, t.p2.x, t.p2.y, t.p3.x, t.p3.y);
}
path.vertices = vertices;
// 線の描画
var g:Graphics = canvas_.graphics;
g.clear();
g.drawGraphicsData(graphicsData);
}
// 各母点の位置更新
private function generatorUpdate(generator:ExPoint, mousePoint:Point):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 distance:Number = Point.distance(mousePoint, new Point(localX, localY));
var dx:Number; // 到達値X座標
var dy:Number; // 到達値Y座標
var diff:Number;
var radian:Number;
if (distance < DISTANCE_OF_REACTION) {
diff = -distance * (DISTANCE_OF_REACTION - distance) / DISTANCE_OF_REACTION;
var offset:Number = DISTANCE_OF_REACTION / distance * SCALE;
diff += Math.floor(Math.random() * offset) - offset / 2;
radian = Math.atan2(mousePoint.y - localY, mousePoint.x - localX);
} else {
diff = 0;
radian = 0;
}
var diffPoint:Point = Point.polar(diff, radian);
dx = localX + diffPoint.x;
dy = localY + diffPoint.y;
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 {
import flash.geom.Point;
/**
* Expand Point
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ class ExPoint extends Point {
// 既定座標
public function get localX():Number { return _localX; }
public function set localX(value:Number):void { _localX = value; }
private var _localX:Number = 0;
public function get localY():Number { return _localY; }
public function set localY(value:Number):void { _localY = value; }
private var _localY:Number = 0;
// 速度
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 ExPoint() {
}
}
//}
/**
* Delaunay triangulation Algorithm by nicoptere
* Delaunay.as, DelaunayEdge.as, DelaunayPoint.as, DelaunayTriangle.as
* @see http://en.nicoptere.net/?p=10
*/
//package net.nicoptere.delaunay
//{
import flash.geom.Point;
//import net.nicoptere.delaunay.DelaunayTriangle;
//import net.nicoptere.delaunay.DelaunayEdge;
//Credit to Paul Bourke (pbourke@swin.edu.au) for the original Fortran 77 Program :))
//Converted to a standalone C# 2.0 library by Morten Nielsen (www.iter.dk)
//Check out: http://astronomy.swin.edu.au/~pbourke/terrain/triangulate/
//You can use this code however you like providing the above credits remain in tact
/**
* @author nicoptere
* http://en.nicoptere.net/
*/
/*public*/ class Delaunay
{
/**
* performs a Delaunay triangulation on a set of points
* @param points the original Vector of points
* @return a Vector.<DelaunAyTriangle> of delaunay triangles
*
*/
public static function Triangulate( points:Vector.<Point> ):Vector.<DelaunayTriangle>
{
//those will be used quite everywhere so I am storing them here not to declare them x times
var i:int;
var j:int;
var nv:int = points.length;
if (nv < 3) return null;
var trimax:int = 4 * nv;
// Find the maximum and minimum vertex bounds.
// This is to allow calculation of the bounding supertriangle
var xmin:Number = points[0].x;
var ymin:Number = points[0].y;
var xmax:Number = xmin;
var ymax:Number = ymin;
var vertex:Vector.<DelaunayPoint> = new Vector.<DelaunayPoint>( nv );
for ( i = 0; i < nv; i++)
{
vertex[ i ] = new DelaunayPoint( points[i].x, points[i].y, i );
if (vertex[i].x < xmin) xmin = vertex[i].x;
if (vertex[i].x > xmax) xmax = vertex[i].x;
if (vertex[i].y < ymin) ymin = vertex[i].y;
if (vertex[i].y > ymax) ymax = vertex[i].y;
}
var dx:Number = xmax - xmin;
var dy:Number = ymax - ymin;
var dmax:Number = (dx > dy) ? dx : dy;
var xmid:Number = (xmax + xmin) * 0.5;
var ymid:Number = (ymax + ymin) * 0.5;
// Set up the supertriangle
// This is a triangle which encompasses all the sample points.
// The supertriangle coordinates are added to the end of the
// vertex list. The supertriangle is the first triangle in
// the triangle list.
vertex.push(new DelaunayPoint( (xmid - 2 * dmax), (ymid - dmax), nv + 1 ) );
vertex.push(new DelaunayPoint( xmid, (ymid + 2 * dmax), nv + 2 ) );
vertex.push(new DelaunayPoint((xmid + 2 * dmax), (ymid - dmax), nv + 3));
var triangles:Vector.<DelaunayTriangle> = new Vector.<DelaunayTriangle>();
triangles.push( new DelaunayTriangle( vertex[ nv ], vertex[ nv + 1 ], vertex[ nv + 2 ] ) ); //SuperTriangle placed at index 0
// Include each point one at a time into the existing mesh
for ( i = 0; i < nv; i++)
{
var DelaunayEdges:Vector.<DelaunayEdge> = new Vector.<DelaunayEdge>();
// Set up the DelaunayEdge buffer.
// If the point (vertex(i).x,vertex(i).y) lies inside the circumcircle then the
// three DelaunayEdges of that triangle are added to the DelaunayEdge buffer and the triangle is removed from list.
for ( j = 0; j < triangles.length; j++ )
{
if ( InCircle( vertex[ i ], triangles[ j ].p1, triangles[ j ].p2, triangles[ j ].p3 ) )
{
DelaunayEdges.push(new DelaunayEdge(triangles[j].p1, triangles[j].p2) );
DelaunayEdges.push(new DelaunayEdge(triangles[j].p2, triangles[j].p3) );
DelaunayEdges.push(new DelaunayEdge(triangles[j].p3, triangles[j].p1) );
triangles.splice( j,1 );
j--;
}
}
if ( i >= nv) continue; //In case we the last duplicate point we removed was the last in the array
// Remove duplicate DelaunayEdges
// Note: if all triangles are specified anticlockwise then all
// interior DelaunayEdges are opposite pointing in direction.
for ( j = DelaunayEdges.length - 2; j >= 0; j--)
{
for (var k:int = DelaunayEdges.length - 1; k >= j + 1; k--)
{
if ( DelaunayEdges[ j ].equals( DelaunayEdges[ k ] ) )
{
DelaunayEdges.splice( k, 1 );
DelaunayEdges.splice( j, 1 );
k--;
continue;
}
}
}
// Form new triangles for the current point
// Skipping over any tagged DelaunayEdges.
// All DelaunayEdges are arranged in clockwise order.
for ( j = 0; j < DelaunayEdges.length; j++)
{
if (triangles.length >= trimax )
{
// throw new ApplicationException("Exceeded maximum DelaunayEdges");
trace("Exceeded maximum DelaunayEdges");
}
triangles.push( new DelaunayTriangle( DelaunayEdges[ j ].p1, DelaunayEdges[ j ].p2, vertex[ i ] ));
}
DelaunayEdges = null;
}
// Remove triangles with supertriangle vertices
// These are triangles which have a vertex number greater than nv
for ( i = triangles.length - 1; i >= 0; i--)
{
if ( triangles[ i ].p1.id >= nv || triangles[ i ].p2.id >= nv || triangles[ i ].p3.id >= nv)
{
triangles.splice(i, 1);
}
}
//Remove SuperTriangle vertices
/*
vertex.splice(vertex.length - 1, 1);
vertex.splice(vertex.length - 1, 1);
vertex.splice(vertex.length - 1, 1);
*/
return triangles;
}
/// <summary>
/// Returns true if the point (p) lies inside the circumcircle made up by points (p1,p2,p3)
/// </summary>
/// <remarks>
/// NOTE: A point on the DelaunayEdge is inside the circumcircle
/// </remarks>
/// <param name="p">Point to check</param>
/// <param name="p1">First point on circle</param>
/// <param name="p2">Second point on circle</param>
/// <param name="p3">Third point on circle</param>
/// <returns>true if p is inside circle</returns>
static private const Epsilon:Number = Number.MIN_VALUE;
private static function InCircle( p:DelaunayPoint, p1:DelaunayPoint, p2:DelaunayPoint, p3:DelaunayPoint ):Boolean
{
//Return TRUE if the point (xp,yp) lies inside the circumcircle
//made up by points (x1,y1) (x2,y2) (x3,y3)
//NOTE: A point on the DelaunayEdge is inside the circumcircle
if ( Math.abs( p1.y - p2.y ) < Epsilon && Math.abs( p2.y - p3.y) < Epsilon)
{
//INCIRCUM - F - Points are coincident !!
return false;
}
var m1:Number;
var m2:Number;
var mx1:Number;
var mx2:Number;
var my1:Number;
var my2:Number;
var xc:Number;
var yc:Number;
if ( Math.abs(p2.y - p1.y) < Epsilon)
{
m2 = -(p3.x - p2.x) / (p3.y - p2.y);
mx2 = (p2.x + p3.x) * 0.5;
my2 = (p2.y + p3.y) * 0.5;
//Calculate CircumCircle center (xc,yc)
xc = (p2.x + p1.x) * 0.5;
yc = m2 * (xc - mx2) + my2;
}
else if ( Math.abs(p3.y - p2.y) < Epsilon)
{
m1 = -(p2.x - p1.x) / (p2.y - p1.y);
mx1 = (p1.x + p2.x) * 0.5;
my1 = (p1.y + p2.y) * 0.5;
//Calculate CircumCircle center (xc,yc)
xc = (p3.x + p2.x) * 0.5;
yc = m1 * (xc - mx1) + my1;
}
else
{
m1 = -(p2.x - p1.x) / (p2.y - p1.y);
m2 = -(p3.x - p2.x) / (p3.y - p2.y);
mx1 = (p1.x + p2.x) * 0.5;
mx2 = (p2.x + p3.x) * 0.5;
my1 = (p1.y + p2.y) * 0.5;
my2 = (p2.y + p3.y) * 0.5;
//Calculate CircumCircle center (xc,yc)
xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
yc = m1 * (xc - mx1) + my1;
}
var dx:Number = p2.x - xc;
var dy:Number = p2.y - yc;
var rsqr:Number = dx * dx + dy * dy;
dx = p.x - xc;
dy = p.y - yc;
var drsqr:Number = dx * dx + dy * dy;
return ( drsqr <= rsqr );
}
}
//}
//package net.nicoptere.delaunay
//{
/**
* @author nicoptere
*/
/*public*/ class DelaunayPoint
{
public var id:int;
public var x:Number;
public var y:Number;
public function DelaunayPoint( x:Number, y:Number, id:int = -1 )
{
this.x = x;
this.y = y;
if( id != -1 ) this.id = id;
}
public function equals( other:DelaunayPoint ):Boolean
{
return ( x == other.x && y == other.y );
}
}
//}
//package net.nicoptere.delaunay
//{
//Credit to Paul Bourke (pbourke@swin.edu.au) for the original Fortran 77 Program :))
//Converted to a standalone C# 2.0 library by Morten Nielsen (www.iter.dk)
//Check out: http://astronomy.swin.edu.au/~pbourke/terrain/triangulate/
//You can use this code however you like providing the above credits remain in tact
/**
* @author nicoptere
* http://en.nicoptere.net/
*/
/*public*/ class DelaunayEdge
{
public var p1:DelaunayPoint;
public var p2:DelaunayPoint;
/**
* Initializes a new DelaunayEdge instance
* @param point1 Start DelaunayEdge vertex index
* @param point1 Start DelaunayEdge vertex index
*/
public function DelaunayEdge( point1:DelaunayPoint, point2:DelaunayPoint )
{
p1 = point1;
p2 = point2;
}
/**
* Checks whether two DelaunayEdges are equal disregarding the direction of the DelaunayEdges
* @param other the DelaunayEdge to compare
*/
public function equals( other:DelaunayEdge ):Boolean
{
return ((this.p1 == other.p2) && (this.p2 == other.p1)) || ((this.p1 == other.p1) && (this.p2 == other.p2));
}
}
//}
//package net.nicoptere.delaunay
//{
//Credit to Paul Bourke (pbourke@swin.edu.au) for the original Fortran 77 Program :))
//Converted to a standalone C# 2.0 library by Morten Nielsen (www.iter.dk)
//Check out: http://astronomy.swin.edu.au/~pbourke/terrain/triangulate/
//You can use this code however you like providing the above credits remain in tact
/**
* @author nicoptere
* http://en.nicoptere.net/
*/
//import net.nicoptere.delaunay.DelaunayPoint;
import flash.display.Graphics;
/*public*/ class DelaunayTriangle
{
//points of the DelaunayTriangle
public var p1:DelaunayPoint;
public var p2:DelaunayPoint;
public var p3:DelaunayPoint;
//gravity center
public var center:DelaunayPoint;
//middle of the sides
public var mid0:DelaunayPoint;//p0 > p1
public var mid1:DelaunayPoint;//p1 > p2
public var mid2:DelaunayPoint;//p2 > p0
public function DelaunayTriangle( p1:DelaunayPoint, p2:DelaunayPoint, p3:DelaunayPoint )
{
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
/**
* retrieves the gravity center of the DelaunayTriangle
*/
public function getCenter():void
{
if( center == null ) center = new DelaunayPoint( 0,0 );
center.x = ( p1.x + p2.x + p3.x ) / 3;
center.y = ( p1.y + p2.y + p3.y ) / 3;
}
/**
* retrieves the midPoint of the DelaunayTriangle's sides. might be useful in some cases
*/
public function getSidesCenters():void
{
if( mid0 == null || mid1 == null || mid2 == null )
{
mid0 = new DelaunayPoint( 0, 0 );
mid1 = new DelaunayPoint( 0, 0 );
mid2 = new DelaunayPoint( 0, 0 );
}
mid0.x = p1.x + ( p2.x - p1.x )/2;
mid0.y = p1.y + ( p2.y - p1.y )/2;
mid1.x = p2.x + ( p3.x - p2.x )/2;
mid1.y = p2.y + ( p3.y - p2.y )/2;
mid2.x = p3.x + ( p1.x - p3.x )/2;
mid2.y = p3.y + ( p1.y - p3.y )/2;
}
public function draw( g:Graphics ):void
{
g.moveTo( p1.x, p1.y );
g.lineTo( p2.x, p2.y );
g.lineTo( p3.x, p3.y );
g.lineTo( p1.x, p1.y );
}
}
//}