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