Steiner Chain
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/a8p0
*/
package {
//import aquioux.display.colorUtil.RGBWheel;
import com.bit101.components.Label;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.MouseEvent;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#ffffff")]
/**
* シュタイナー・チェーン(反転前:Fill、反転後:line)
*/
public class Main extends Sprite {
// 拡大率
static private const SCALE:int = 50;
// キャンバス
private var normalCanvas_:Shape;
private var invertedCanvas_:Shape;
private var g_:Graphics; // invertedCanvas の Graphics
// 連鎖を構成する円の数
static private var NUM_OF_CHAIN:int = 7;
// 各円の半径を求めるために必要な値
// 2個の接する連鎖円の中心座標間が占める角度(弧度法)
static private var RADIAN:Number = Math.PI * 2 / NUM_OF_CHAIN;
// RADIAN の半分の値のサイン値
static private var SIN:Number = Math.sin(RADIAN / 2);
// 各円の情報を格納するリスト
private var circles_:Vector.<Number>;
// circles_ に格納される一つの円の情報がいくつの要素から構成されているか
static private var NUM_OF_ELEMENTS:int = 5; // 中心X座標、中心Y座標、半径、反転前の色、反転後の色
// 円描画時のループ回数
private var limit_:int; // circle_.length / NUM_OF_ELEMENTS
// 単位円を内接円にするか外接円にするかの切り替えスイッチ
private var sw_:Boolean;
// 説明
private var label_:Label;
/**
* コンストラクタ
*/
public function Main():void {
setup();
stage.addEventListener(MouseEvent.CLICK, init);
}
/**
* セットアップ
*/
private function setup():void {
// 各円の情報格納リスト生成
circles_ = new Vector.<Number>();
// 反転前の状態を描画するキャンバス
normalCanvas_ = new Shape();
normalCanvas_.x = stage.stageWidth / 2;
normalCanvas_.y = stage.stageHeight / 2;
addChild(normalCanvas_);
// 反転後の状態を描画するキャンバス
invertedCanvas_ = new Shape();
invertedCanvas_.x = normalCanvas_.x;
invertedCanvas_.y = normalCanvas_.y;
addChild(invertedCanvas_);
g_ = invertedCanvas_.graphics;
// 説明
label_ = new Label(this, 0, 0, "");
// 最初の表示
init(null);
// マウスムーブ・イベントリスナー登録
stage.addEventListener(MouseEvent.MOUSE_MOVE, update);
}
/**
* 初期化
*/
private function init(event:MouseEvent):void {
if (sw_) {
// 各円の半径(外接円の半径から他の円の半径を計算)
var outRadius:Number = 1.0; // 外接円(単位円)
var inRadius:Number = outRadius * (1 - SIN) / (1 + SIN); // 内接円
var chainRadius:Number = (outRadius - inRadius) / 2; // 連鎖円
label_.text = "Among the circle of inversion before, unit circle is the outer circle(black circle).";
} else {
// 各円の半径(内接円の半径から他の円の半径を計算)
inRadius = 1.0; // 内接円(単位円)
chainRadius = inRadius * SIN / (1 - SIN); // 連鎖円
outRadius = inRadius + chainRadius * 2; // 外接円
label_.text = "Among the circle of inversion before, unit circle is the inner circle(gray circle).";
}
sw_ = !sw_;
// 各円の情報格納リスト初期化
circles_.fixed = false;
circles_.length = 0;
// 各円の情報を格納
circles_.push(0, 0, outRadius, 0x333333, 0x000000); // 外接円
circles_.push(0, 0, inRadius, 0xcccccc, 0x999999); // 内接円
var length:Number = inRadius + chainRadius; // 原点(0,0)から連鎖円の中心までの距離
for (var i:int = 0; i < NUM_OF_CHAIN; i++) { // 連鎖円
var myRadian:Number = RADIAN * i;
RGBWheel.s = 0.5;
RGBWheel.v = 1.0;
var color1:uint = RGBWheel.getRadianColor(myRadian);
RGBWheel.s = 1.0;
RGBWheel.v = 0.75;
var color2:uint = RGBWheel.getRadianColor(myRadian);
circles_.push(Math.cos(myRadian) * length, Math.sin(myRadian) * length, chainRadius, color1, color2);
}
circles_.fixed = true;
// 反転前の状態を描画
var g:Graphics = normalCanvas_.graphics;
g.clear();
limit_ = circles_.length / NUM_OF_ELEMENTS;
for (i = 0; i < limit_; ++i) {
var idx:int = i * NUM_OF_ELEMENTS;
g.beginFill(circles_[idx + 3]);
g.drawCircle(circles_[idx] * SCALE, circles_[idx + 1] * SCALE, circles_[idx + 2] * SCALE);
g.endFill();
}
}
/**
* アップデート
*/
private function update(event:MouseEvent):void {
// 反転の原点座標(マウスカーソル位置に対応)
var originX:Number = invertedCanvas_.mouseX / SCALE;
var originY:Number = invertedCanvas_.mouseY / SCALE;
// 反転後の状態を描画
g_.clear();
for (var i:int = 0; i < limit_; ++i) {
var idx:int = i * NUM_OF_ELEMENTS;
// 反転前の各円の中心座標(の原点からの距離)と半径
var a:Number = circles_[idx] + originX;
var b:Number = circles_[idx + 1] + originY;
var r:Number = circles_[idx + 2];
// 反転計算のための係数
var s:Number = 1 / (a * a + b * b - r * r);
// 反転後の円の中心座標(の原点からの距離)と半径
var invertedX:Number = a * s;
var invertedY:Number = -b * s;
var invertedR:Number = r * s;
// 描画
g_.lineStyle(4, circles_[idx + 4]);
g_.drawCircle((invertedX + originX) * SCALE, (invertedY + originY) * SCALE, invertedR * SCALE);
}
}
}
}
//package aquioux.display.colorUtil {
/**
* コサインカーブで色相環的に RGB を計算
* @author YOSHIDA, Akio
*/
/*public*/ class RGBWheel {
/**
* 彩度(HSV の彩度 S と同じ扱い)
*/
static public function get s():Number { return _s; }
static public function set s(value:Number):void {
_s = adjust1(value);
}
static private var _s:Number = 1.0;
/**
* 明度(HSV の彩度 V と同じ扱い)
*/
static public function get v():Number { return _v; }
static public function set v(value:Number):void {
_v = adjust1(value);
}
static private var _v:Number = 1.0;
/**
* 角度に応じた RGB を得る(度数法指定)
* @param angle 角度(度数法)
* @return 色(0xRRGGBB)
*/
static private const TO_RADIAN:Number = Math.PI / 180; // 度数を弧度に
static public function getDegreeColor(angle:Number):uint {
var r:uint = (Math.cos( angle * TO_RADIAN) + 1) * 0xff >> 1;
var g:uint = (Math.cos((angle + 120) * TO_RADIAN) + 1) * 0xff >> 1;
var b:uint = (Math.cos((angle - 120) * TO_RADIAN) + 1) * 0xff >> 1;
if (_s != 1.0) {
r += calcShiftS(r);
g += calcShiftS(g);
b += calcShiftS(b);
}
if (_v != 1.0) {
r -= calcShiftV(r);
g -= calcShiftV(g);
b -= calcShiftV(b);
}
return r << 16 | g << 8 | b;
}
/**
* 角度に応じた RGB を得る(弧度法指定)
* @param radian 角度(弧度法)
* @return 色(0xRRGGBB)
*/
static private const RADIAN120:Number = Math.PI * 2 / 3; // 120度を弧度で
static public function getRadianColor(radian:Number):uint {
var r:uint = (Math.cos(radian) + 1) * 0xff >> 1;
var g:uint = (Math.cos(radian + RADIAN120) + 1) * 0xff >> 1;
var b:uint = (Math.cos(radian - RADIAN120) + 1) * 0xff >> 1;
if (_s != 1.0) {
r += calcShiftS(r);
g += calcShiftS(g);
b += calcShiftS(b);
}
if (_v != 1.0) {
r -= calcShiftV(r);
g -= calcShiftV(g);
b -= calcShiftV(b);
}
return r << 16 | g << 8 | b;
}
/**
* 彩度の反映
* @param gray 諧調(0~255)
* @return 諧調のシフト値
* @private
*/
static private function calcShiftS(gray:uint):uint {
return (0xff - gray) * (1 - _s) >> 0;
}
/**
* 明度の反映
* @param gray 諧調(0~255)
* @return 諧調のシフト値
* @private
*/
static private function calcShiftV(gray:uint):uint {
return gray * (1 - _v) >> 0;
}
/**
* 数値を 0.0 <= value <= 1.0 の範囲に収める
* @param value
* @return チェック後の値
* @private
*/
static public function adjust1(value:Number):Number {
if (value < 0.0) value = 0.0;
if (value > 1.0) value = 1.0;
return value;
}
}
//}