開発環境 Apache Flex SDK 4.12.1
FlashDevelop 4.6.1
実行環境 Microsoft Windows 8.1 (64bit)
プロジェクトの種類 ActionScript 3/AS3 Project
プロジェクト名 WorldMap2


参考

Main.as
package 
{
    import com.adobe.utils.*;
    import flash.display.*;
    import flash.display3D.*;
    import flash.display3D.textures.*;
    import flash.events.*;
    import flash.geom.*;
    import flash.text.*;
    import flash.ui.*;
 
    public class Main extends Sprite 
    {
        private var context:Context3D;
        private var program:Program3D;
        private var vertexBuffer:VertexBuffer3D;
        private var indexBuffer:IndexBuffer3D;
 
        [Embed(source="earthmap1k.jpg")]
        private const TextureBitmap:Class;
        private var texture0:Texture;
        private var texture1:Texture;
 
        private var textField:Array = new Array(5);
        private var cursor:int = 4;
        private var datetime:Array;
 
        private const solarYear:Number = 365.24219; // 太陽年(day) 365d5h48m45s
        private const anomalisticYear:Number = 365.259643; // 近点年(day) 365d6h13m53.1552s
        private const earthRotationPerSec:Number = (2 * Math.PI) / 86400; // 地球が1秒間に回転する角度
        private const e:Number = 0.01671022; // 離心率(Orbital eccentricity)
        private const K:Number = Math.sqrt((1 + e) / (1 - e)); // ケプラー方程式の定数
        private const epsilon:Number = 1.0e-14;
 
        private var MJD:Number; // 修正ユリウス日
        private var MJD_date:Number;
        private var MJD_time:Number;
        private var T:Number; // 2000/1/1 12:00(UT)からのユリウス世紀(36525日)
        private var obliquity:Number; // 黄道傾斜角
        private var eclipticLon:Number; // 黄径
 
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
 
            var textFormat:TextFormat = new TextFormat(null, 32);
            for (var i:int = 0; i < 5; i++) 
            {
                var tf:TextField = new TextField;
                tf.defaultTextFormat = textFormat;
                tf.x = (i == 0) ? 0 : (40 + 50 * i);
                this.addChild(tf);
                textField[i] = tf;
            }
            UpdateDatetime(new Date);
 
            stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
            stage.stage3Ds[0].requestContext3D();
 
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        }
 
        private function onContext3DCreate(e:Event):void 
        {
            // context
            context = stage.stage3Ds[0].context3D;
            context.configureBackBuffer(1024, 512, 0, false);
 
            // vertexBuffer
            const vertices:Vector.<Number> = Vector.<Number>([
            //  x,  y,  u, v,
                -1, -1, 0, 1, // LB
                -1, 1, 0, 0, // LT
                1, 1, 1, 0, // RT
                1, -1, 1, 1, // RB
            ]);
            const perVertex:int = 4;
            const numVertices:int = vertices.length / perVertex;
            vertexBuffer = context.createVertexBuffer(numVertices, perVertex);
            vertexBuffer.uploadFromVector(vertices, 0, numVertices);
 
            // indexBuffer
            const indices:Vector.<uint> = Vector.<uint>([0, 1, 2, 2, 3, 0]);
            indexBuffer = context.createIndexBuffer(indices.length);
            indexBuffer.uploadFromVector(indices, 0, indices.length);
 
            // texture0
            var bitmap:Bitmap = new TextureBitmap();
            var bitmapData0:BitmapData = new BitmapData(1024, 512, false);
            bitmapData0.draw(bitmap.bitmapData,
                new Matrix(1024 / 1000, 0, 0, 512 / 500), null, null, null, true);
            texture0 = context.createTexture(
                bitmapData0.width, bitmapData0.height, Context3DTextureFormat.BGRA, false);
            texture0.uploadFromBitmapData(bitmapData0);
 
            // texture1
            var bitmapData1:BitmapData = new BitmapData(256, 1, false);
            for (var x:int = 0; x < bitmapData1.width; x++) 
            {
                var val:Number = (x / (bitmapData1.width - 1)) * 2 - 1;
                var halfDaytime:Number = Math.acos(val) / (2 * Math.PI);
                var color:uint = 0xff * halfDaytime;
                bitmapData1.setPixel(x, 0, color);
            }
            texture1 = context.createTexture(
                bitmapData1.width, bitmapData1.height, Context3DTextureFormat.BGRA, false);
            texture1.uploadFromBitmapData(bitmapData1);
 
            // program
            var vertexAssembler:AGALMiniAssembler = new AGALMiniAssembler;
            vertexAssembler.assemble(Context3DProgramType.VERTEX,
                "mov op va0\n" +
                "mov v0 va1\n"
            );
            // v0.x : u 経度 L:0 - R:1
            // v0.y : v 緯度 T:0 - B:1
 
            var fragmentAssembler:AGALMiniAssembler = new AGALMiniAssembler;
            fragmentAssembler.assemble(Context3DProgramType.FRAGMENT,
                "sub ft0.y fc1.x v0.y\n" +  // y = 0.5 - v
                "mul ft2.y ft0.y fc2.y\n" + // lat = y * PI
 
                "sub ft0.x v0.x fc1.x\n" +  // x = u - 0.5
                "add ft0.x fc0.x ft0.x\n" + // x = MJD_time + x
                "frc ft2.z ft0.x\n" +       // localtime = frac(x)
 
                "sin ft0.y ft2.y\n" +       // y = sin(lat)
                "mul ft0.z ft0.y fc2.x\n" + // ra = y * revision
                "cos ft0.x ft2.y\n" +       // x = cos(lat)
                "mul ft0.w ft0.x fc2.x\n" + // rx = x * revision
                "div ft0.y ft0.y ft0.x\n" + // y = y / x
                "add ft0.x fc0.z ft0.z\n" + // x = solarAlt + ra
                "neg ft0.y ft0.y\n" +       // y = -y
                "mul ft0.x ft0.x ft0.y\n" + // x = x * y
                "sub ft0.x ft0.x ft0.w\n" + // x = x - rx
                "div ft2.x ft0.x fc0.w\n" + // X = x / solarRad
 
                "add ft0.x ft2.x fc1.y\n" + // x = X + 1
                "mul ft0.x ft0.x fc1.x\n" + // x = x * 0.5
                "mov ft0.y fc1.x\n" +       // y = 0.5
                "tex ft1 ft0 fs1 <2d,linear>\n" +
 
                "sub ft0.x fc0.y ft1.z\n" + // rising = transit - halfDaytime
                "slt ft0.y ft0.x ft2.z\n" + // y = (rising < localtime) ? 1 : 0
                "add ft0.x fc0.y ft1.z\n" + // setting = transit + halfDaytime
                "slt ft0.z ft2.z ft0.x\n" + // z = (localtime < setting) ? 1 : 0
                "mul ft0.y ft0.y ft0.z\n" + // y = y * z
                "sge ft0.z fc1.z ft2.x\n" + // z = (-1 >= X) ? 1 : 0
                "add ft0.y ft0.y ft0.z\n" + // y = y + z
                "sat ft0.x ft0.y\n" +       // x = clamp(y)
 
                "mul ft0.x ft0.x fc1.x\n" + // x = x * 0.5
                "add ft0.x ft0.x fc1.x\n" + // x = x + 0.5
                "tex ft1 v0 fs0 <2d>\n" +
                "mul ft1.xyz ft1.xyz ft0.xxx\n" + // rgb = rgb * x
                "mov oc ft1\n"              // output color = rgba
            );
            // ft0 general-purpose
            // ft1 texture
            // ft2.x : X
            // ft2.y : lat 緯度 T:PI/2 - B:-PI/2
            // ft2.z : localtime ローカル時刻 0(0:00) - 1(24:00)
 
            program = context.createProgram();
            program.upload(vertexAssembler.agalcode, fragmentAssembler.agalcode);
 
            // va0 : xy
            context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2);
            // va1 : uv
            context.setVertexBufferAt(1, vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_2);
            // fs0 : texture sampler
            context.setTextureAt(0, texture0);
            // fs1 : texture sampler
            context.setTextureAt(1, texture1);
            context.setProgram(program);
 
            // 太陽の視角(0.533deg)と大気差(35m8s)による昼の長さの補正
            const revision:Number = Math.sin((0.533 / 2 + (35 * 60 + 8) / 3600) * Math.PI / 180);
 
            // fc1 : x=0.5 y=1 z=-1 w=0
            // fc2 : x=revision y=Math.PI
            context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1,
                Vector.<Number>([0.5, 1, -1, 0]));
            context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2,
                Vector.<Number>([revision, Math.PI, 0, 0]));
 
            onRender();
        }
 
        private function onKeyDown(e:KeyboardEvent):void 
        {
            var delta:int = 0;
            switch (e.keyCode) 
            {
                case Keyboard.LEFT:
                    cursor = (cursor + 4) % 5;
                    break;
                case Keyboard.UP:
                    delta = 1;
                    break;
                case Keyboard.RIGHT:
                    cursor = (cursor + 1) % 5;
                    break;
                case Keyboard.DOWN:
                    delta = -1;
                    break;
                default:
                    return;
            }
            datetime[cursor] += delta;
            var date:Date = new Date(datetime[0], datetime[1] - 1, datetime[2], datetime[3], datetime[4]);
            UpdateDatetime(date);
            onRender();
        }
 
        private function UpdateDatetime(date:Date):void 
        {
            datetime = [date.fullYear, date.month + 1, date.date, date.hours, date.minutes];
            for (var i:int = 0; i < 5; i++) 
            {
                var tf:TextField = textField[i];
                tf.text = (i == 0) ? datetime[i] : ("0" + datetime[i]).slice( -2);
                tf.textColor = (i == cursor) ? 0xff0000 : 0x0000ff;
            }
        }
 
        private function onRender():void 
        {
            if (!context) return;
 
            CalcParameter();
 
            context.clear();
            context.drawTriangles(indexBuffer);
            context.present();
        }
 
        private function CalcParameter():void 
        {
            // 修正ユリウス日
            var y:int = datetime[0];
            var m:int = datetime[1];
            var d:int = datetime[2];
            if (m < 3)
            {
                y--;
                m += 12;
            }
            MJD_date = Math.floor(365.25 * y) + Math.floor(y / 400) - Math.floor(y / 100) +
                Math.floor(30.59 * (m - 2)) + d - 678912;
            var h:int = datetime[3] - 9; // JST-9
            if (h < 0)
            {
                h += 24;
                MJD_date--;
            }
            MJD_time = (h * 60 + datetime[4]) / 1440;
            MJD = MJD_date + MJD_time;
 
            // 黄道傾斜角
            T = (MJD - 51544.5) / 36525;
            obliquity = (84381.406 - 46.836769 * T - 0.00059 * T * T + 0.001813 * T * T * T) / 3600;
 
            // 平均近点角(概算)近日点から次の近日点までの角度
            var Ma:Number = ModAngle((2 * Math.PI) * ((MJD / anomalisticYear - 0.1242853) % 1));
            var Ta:Number = KeplersEquation(Ma); // 真近点角
 
            // 春分点(vernal equinox)の真近点角
            var MJDv:Number = (Math.floor(MJD / solarYear - 0.3399541) + 0.3399541) * solarYear;
            var Mv:Number = ModAngle((2 * Math.PI) * ((MJDv / anomalisticYear - 0.1242853) % 1));
            var Tv:Number = KeplersEquation(Mv);
 
            // 黄径(概算)春分点から次の春分点までの角度
            eclipticLon = ModAngle(Ta - Tv);
 
            // 楕円効果と傾斜効果
            var ellipseEffect:int = Math.round(Ma / earthRotationPerSec - Ta / earthRotationPerSec);
            var obliquityEffect:int = CalcObliquityEffect(obliquity, eclipticLon);
            var equationOfTime:int = ellipseEffect + obliquityEffect; // 均時差
            var transit:Number = (43200 - equationOfTime) / 86400; // 南中時
 
            // 天球上の太陽軌道の高さと半径
            var solarDecl:Number = Math.sin(eclipticLon) * obliquity * Math.PI / 180; // 太陽の赤緯
            var solarAlt:Number = Math.sin(solarDecl); // 天球上の太陽軌道の高さ
            var solarRad:Number = Math.cos(solarDecl); // 天球上の太陽軌道の半径
 
            // fc0 : x=MJD_time y=transit z=solarAlt w=solarRad
            context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0,
                Vector.<Number>([MJD_time, transit, solarAlt, solarRad]));
        }
 
        // -Math.PI < angle <= Math.PI に正規化
        private function ModAngle(angle:Number):Number
        {
            while (angle <= -Math.PI) angle += (2 * Math.PI);
            while (Math.PI < angle) angle -= (2 * Math.PI);
            return angle;
        }
 
        // 傾斜効果の計算
        private function CalcObliquityEffect(obliquity:Number, eclipticLon:Number):int 
        {
            var x:Number = Math.cos(eclipticLon);
            var r:Number = Math.sin(eclipticLon);
            var y:Number = Math.cos(obliquity * Math.PI / 180) * r;
            var celestialEquator:Number = Math.atan2(y, x); // 天の赤道上の角度
            return Math.round(eclipticLon / earthRotationPerSec - celestialEquator / earthRotationPerSec);
        }
 
        // 漸化式によりケプラー方程式を解く
        // M 平均近点角(mean anomaly)
        // E 離心近点角(Eccentric anomaly)
        // T 真近点角(true anomaly)
        private function KeplersEquation(M:Number):Number 
        {
            var E:Number;
            var E0:Number = M; // 初項
            for (var i:int = 0; ; ) 
            {
                i++;
                E = M + e * Math.sin(E0);
                if ((E0 - epsilon < E) && (E < E0 + epsilon)) break;
                if (10 <= i)
                {
                    // 計算打ち切り
                    break;
                }
                E0 = E;
            }
            var T:Number = Math.atan(K * Math.tan(E / 2)) * 2;
            return T;
        }
 
    }
 
}
 
最終更新:2014年06月20日 15:07