Steiner Chain Clock

by Aquioux forked from Steiner Chain(複数圏) (diff: 253)
♥2 | Line 319 | Modified 2013-09-09 19:49:08 | MIT License
play

ActionScript3 source code

/**
 * Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/kDrW
 */

// forked from Aquioux's Steiner Chain(複数圏)
// forked from Aquioux's Steiner Chain
package {
    //import aquioux.display.colorUtil.RGBWheel;
    import flash.display.Graphics;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#ffffff")]
    /**
     * シュタイナー・チェーン・クロック
     * @author YOSHIDA, Akio
     */
    public class Main2 extends Sprite {
        // 拡大率
        static private const SCALE:int = 120;

        // キャンバス
        private var canvas_:Shape;
        private var g_:Graphics;

        // 連鎖を構成する円の数
        static private var NUM_OF_CHAIN1:int = 60;    // 秒
        static private var NUM_OF_CHAIN2:int = 60;    // 分
        static private var NUM_OF_CHAIN3:int = 12;    // 時

        // 各円の半径を求めるために必要な値
        static private var RADIAN1:Number = Math.PI * 2 / NUM_OF_CHAIN1;
        static private var SIN1:Number = Math.sin(RADIAN1 / 2);
        static private var RADIAN2:Number = Math.PI * 2 / NUM_OF_CHAIN2;
        static private var SIN2:Number = Math.sin(RADIAN2 / 2);
        static private var RADIAN3:Number = Math.PI * 2 / NUM_OF_CHAIN3;
        static private var SIN3:Number = Math.sin(RADIAN3 / 2);

        // 円の情報を格納するリスト(1次元配列)
        private var circles1_:Vector.<Number>;
        private var circles2_:Vector.<Number>;
        private var circles3_:Vector.<Number>;
        // circles_ に格納される一つの円の情報がいくつの要素から構成されているか
        static private var NUM_OF_ELEMENTS:int = 5;    // 中心X座標、中心Y座標、半径、色、表示有無

        // 円の情報を格納するリストを回す for 文の限界値
        private var limit1_:int;
        private var limit2_:int;
        private var limit3_:int;

        // 反転原点座標用(極座標形式)
        private const ORIGIN_LENGTH:Number = 0.65;    // 座標(0,0)から反転原点までの距離
        private const PI:Number = Math.PI;

        // 時エンジン
        private var zeit_:Zeit;


        /**
         * コンストラクタ
         */
        public function Main2():void {
            setup();
            zeit_.addEventListener(Zeit.SECONDS_CHANGED, update);
        }

        /**
         * セットアップ
         */
        private function setup():void {
            const COLOR1:uint = 0x000000;
            const COLOR2:uint = 0xffffff;

            // 色を真上から時計回りになるようにする(270度)
            const SHIFT_RADIAN:Number = Math.PI * 3 / 2;
            RGBWheel.s = 0.5;

            // 円の情報格納リスト生成
            circles1_ = new Vector.<Number>();
            circles2_ = new Vector.<Number>();
            circles3_ = new Vector.<Number>();

            // 第1圏
            // 各円の半径
            var inRadius:Number    = 1.0;                            // 内接円
            var chainRadius:Number = inRadius * SIN1 / (1 - SIN1);    // 連鎖円
            var outRadius:Number   = inRadius + chainRadius * 2;    // 外接円
            // 円の情報格納
            circles1_.push(0, 0, inRadius,  COLOR1, true);
            circles1_.push(0, 0, outRadius, COLOR2, false);
            var length:Number = inRadius + chainRadius;
            for (var i:int = 0; i < NUM_OF_CHAIN1; i++) {
                var myRadian:Number = RADIAN1 * i;
                circles1_.push(Math.cos(myRadian) * length, Math.sin(myRadian) * length, chainRadius, RGBWheel.getRadianColor(SHIFT_RADIAN - myRadian), true);
            }
            circles1_.fixed = true;

            // 第2圏
            // 各円の半径
            inRadius    = outRadius;                    // 内接円
            chainRadius = inRadius * SIN2 / (1 - SIN2);    // 連鎖円
            outRadius   = inRadius + chainRadius * 2;    // 外接円
            // 円の情報格納
            circles2_.push(0, 0, inRadius,  COLOR2, true);
            circles2_.push(0, 0, outRadius, COLOR1, false);
            length = inRadius + chainRadius;
            for (i = 0; i < NUM_OF_CHAIN2; i++) {
                myRadian = RADIAN2 * i;
                circles2_.push(Math.cos(myRadian) * length, Math.sin(myRadian) * length, chainRadius, RGBWheel.getRadianColor(SHIFT_RADIAN - myRadian), true);
            }
            circles2_.fixed = true;

            // 第3圏
            // 各円の半径
            inRadius    = outRadius;                    // 内接円
            chainRadius = inRadius * SIN3 / (1 - SIN3);    // 連鎖円
            outRadius   = inRadius + chainRadius * 2;    // 外接円
            // 円の情報格納
            circles3_.push(0, 0, inRadius,  COLOR1, true);
            circles3_.push(0, 0, outRadius, COLOR2, true);
            length = inRadius + chainRadius;
            for (i = 0; i < NUM_OF_CHAIN3; i++) {
                myRadian = RADIAN3 * i;
                circles3_.push(Math.cos(myRadian) * length, Math.sin(myRadian) * length, chainRadius, RGBWheel.getRadianColor(SHIFT_RADIAN - myRadian), true);
            }
            circles3_.fixed = true;

            // 円の情報を格納するリストを回す for 文の限界値
            limit1_ = circles1_.length / NUM_OF_ELEMENTS;
            limit2_ = circles2_.length / NUM_OF_ELEMENTS;
            limit3_ = circles3_.length / NUM_OF_ELEMENTS;

            // キャンバス
            canvas_ = new Shape();
            canvas_.x = stage.stageWidth  / 2;
            canvas_.y = stage.stageHeight / 2;
            addChild(canvas_);
            g_ = canvas_.graphics;

            // 時計エンジン
            zeit_ = new Zeit();
        }

        /**
         * アップデート
         */
        private function update(event:Event):void {
            g_.clear();

            // 反転計算と描画
            // 第1圏
            var radian:Number  = CalcHandAngle.getSecondsRadian(zeit_) - PI;
            var originX:Number = Math.cos(radian) * ORIGIN_LENGTH;
            var originY:Number = Math.sin(radian) * ORIGIN_LENGTH;
            for (var i:int = 0; i < limit1_; ++i) {
                var idx:int = i * NUM_OF_ELEMENTS;
                // 反転前の各円の中心座標と半径
                var a:Number = circles1_[idx]     + originX;
                var b:Number = circles1_[idx + 1] + originY;
                var r:Number = circles1_[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;
                // 描画座標と半径
                var drawX:Number = invertedX * SCALE;
                var drawY:Number = invertedY * SCALE;
                var drawR:Number = invertedR * SCALE;
                // この圏の位置シフト
                if (i == 0) {
                    var currentShiftX:Number = -drawX;
                    var currentShiftY:Number = -drawY;
                }
                // 次の圏の位置シフト
                if (i == 1) {
                    var nextShiftX:Number = drawX + currentShiftX;
                    var nextShiftY:Number = drawY + currentShiftY;
                }
                // 描画
                if (circles1_[idx + 4]) {
                    g_.beginFill(circles1_[idx + 3]);
                    g_.drawCircle(drawX + currentShiftX, drawY + currentShiftY, drawR);
                    g_.endFill();
                }
            }

            // 第2圏
            radian  = CalcHandAngle.getMinutesRadian(zeit_) - PI;
            originX = Math.cos(radian) * ORIGIN_LENGTH;
            originY = Math.sin(radian) * ORIGIN_LENGTH;
            // 反転計算と描画
            for (i = 0; i < limit2_; ++i) {
                idx = i * NUM_OF_ELEMENTS;
                // 反転前の各円の中心座標と半径
                a = circles2_[idx]     + originX;
                b = circles2_[idx + 1] + originY;
                r = circles2_[idx + 2];
                // 反転計算のための係数
                s = 1 / (a * a + b * b - r * r);
                // 反転後の円の中心座標と半径
                invertedX = a * s;
                invertedY = b * s;    // 連鎖円が逆時計回りになるのでマイナスを外す
                invertedR = r * s;
                // 描画座標と半径
                drawX = invertedX * SCALE;
                drawY = invertedY * SCALE;
                drawR = invertedR * SCALE;
                // この圏の位置シフト
                if (i == 0) {
                    currentShiftX = nextShiftX - drawX;
                    currentShiftY = nextShiftY - drawY;
                }
                // 次の圏の位置シフト
                if (i == 1) {
                    nextShiftX = drawX + currentShiftX;
                    nextShiftY = drawY + currentShiftY;
                }
                // 描画
                if (circles2_[idx + 4]) {
                    g_.beginFill(circles2_[idx + 3]);
                    g_.drawCircle(drawX + currentShiftX, drawY + currentShiftY, drawR);
                    g_.endFill();
                }
            }

            // 第3圏
            radian  = CalcHandAngle.getHoursRadian(zeit_) - PI;
            originX = Math.cos(radian) * ORIGIN_LENGTH;
            originY = Math.sin(radian) * ORIGIN_LENGTH;
            // 反転計算と描画
            for (i = 0; i < limit3_; ++i) {
                idx = i * NUM_OF_ELEMENTS;
                // 反転前の各円の中心座標と半径
                a = circles3_[idx]     + originX;
                b = circles3_[idx + 1] + originY;
                r = circles3_[idx + 2];
                // 反転計算のための係数
                s = 1 / (a * a + b * b - r * r);
                // 反転後の円の中心座標と半径
                invertedX = a * s;
                invertedY = b * s;    // 連鎖円が逆時計回りになるのでマイナスを外す
                invertedR = r * s;
                // 描画座標と半径
                drawX = invertedX * SCALE;
                drawY = invertedY * SCALE;
                drawR = invertedR * SCALE;
                // この圏の位置シフト
                if (i == 0) {
                    currentShiftX = nextShiftX - drawX;
                    currentShiftY = nextShiftY - drawY;
                }
                // 描画
                if (circles3_[idx + 4]) {
                    g_.beginFill(circles3_[idx + 3]);
                    g_.drawCircle(drawX + currentShiftX, drawY + currentShiftY, drawR);
                    g_.endFill();
                }
            }
        }
    }
}


//package {
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.TimerEvent;
    import flash.utils.Timer;
    /**
     * 時計エンジン
     * @author YOSHIDA, Akio
     */
    /*public*/ class Zeit extends EventDispatcher {
        // 送出イベント
        static public const YEAR_CHANGED:String    = "yearChanged";        // 年
        static public const MONTH_CHANGED:String   = "monthChanged";    // 月
        static public const DATE_CHANGED:String    = "dateChanged";        // 日
        static public const HOURS_CHANGED:String   = "hoursChanged";    // 時
        static public const MINUTES_CHANGED:String = "minutesChanged";    // 分
        static public const SECONDS_CHANGED:String = "secondsChanged";    // 秒

        /**
         * 現在年
         */
        public function get year():uint  { return _year; }
        private var _year:int;
        /**
         * 現在月
         */
        public function get month():uint { return _month; }
        private var _month:int;
        /**
         * 現在日
         */
        public function get date():uint  { return _date; }
        private var _date:int;
        /**
         * 現在曜日
         */
        public function get day():uint   { return _day; }
        private var _day:int;
        /**
         * 現在時
         */
        public function get hours():uint   { return _hours; }
        private var _hours:int;
        /**
         * 現在分
         */
        public function get minutes():uint { return _minutes; }
        private var _minutes:int;
        /**
         * 現在秒
         */
        public function get seconds():uint { return _seconds; }
        private var _seconds:int;

        // イベント発行チェック用
        private var prevSeconds_:Number;

        // 基幹エンジン
        private var timer:Timer;


        /**
         * コンストラクタ
         */
        public function Zeit() {
            init();
            timer = new Timer(20);
            timer.addEventListener(TimerEvent.TIMER, timerHandler);
            timer.start();
        }

        // 時間データの取得
        private function init():void{
            // Date オブジェクト生成
            var d:Date = new Date();
            // 年・月・日・曜日・時・分・秒取得
            _year    = d.getFullYear();
            _month   = d.getMonth() + 1;
            _date    = d.getDate();
            _day     = d.getDay();
            _hours   = d.getHours();
            _minutes = d.getMinutes();
            _seconds = d.getSeconds();
        }

        // Timer ハンドラ
        private function timerHandler(e:TimerEvent):void {
            init();            // 時間データの取得
            sendEvent();    // イベント送出
        }

        // イベント送出
        private function sendEvent():void{
            if (_seconds != prevSeconds_) {
                dispatchEvent(new Event(SECONDS_CHANGED));
                if (_seconds == 0) {
                    dispatchEvent(new Event(MINUTES_CHANGED));
                    if (_minutes == 0) {
                        dispatchEvent(new Event(HOURS_CHANGED));
                        if (_hours == 0) {
                            dispatchEvent(new Event(DATE_CHANGED));
                            if (_date == 1) {
                                dispatchEvent(new Event(MONTH_CHANGED));
                                if (_month == 1) {
                                    dispatchEvent(new Event(YEAR_CHANGED));
                                }
                            }
                        }
                    }
                }
            }
            prevSeconds_ = _seconds;
        }
    }
//}


//package {
    /**
     * 時計エンジンヘルパー
     * アナログ時計の針の角度を計算
     * @author YOSHIDA, Akio
     */
    /*public*/ class CalcHandAngle {
        // 度数法を弧度法に変換する
        static private const DEGREE_TO_RADIAN:Number = Math.PI / 180;

        /* ////////// 度数法 ////////// */
        /**
         * 時針の角度(12時制)
         * @param    zeit    時計エンジン
         * @return
         */
        static public function getHoursDegree(zeit:Zeit):Number {
            return ((zeit.hours % 12) * 30) + (zeit.minutes * 0.5) + (zeit.seconds / 120) - 90;
        }
        /**
         * 分針の角度
         * @param    zeit    時計エンジン
         * @return
         */
        static public function getMinutesDegree(zeit:Zeit):Number {
            return (zeit.minutes * 6) + (zeit.seconds * 0.1) - 90;
        }
        /**
         * 秒針の角度
         * @param    zeit    時計エンジン
         * @return
         */
        static public function getSecondsDegree(zeit:Zeit):Number {
            return zeit.seconds * 6 - 90;
        }

        /* ////////// 弧度法 ////////// */
        /**
         * 時針の角度
         * @param    zeit    時計エンジン
         * @return
         */
        static public function getHoursRadian(zeit:Zeit):Number {
            return getHoursDegree(zeit) * DEGREE_TO_RADIAN
        }
        /**
         * 分針の角度
         * @param    zeit    時計エンジン
         * @return
         */
        static public function getMinutesRadian(zeit:Zeit):Number {
            return getMinutesDegree(zeit) * DEGREE_TO_RADIAN
        }
        /**
         * 秒針の角度
         * @param    zeit    時計エンジン
         * @return
         */
        static public function getSecondsRadian(zeit:Zeit):Number {
            return getSecondsDegree(zeit) * DEGREE_TO_RADIAN
        }
    }
//}


//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;
        }
    }
//}