/*
* XnaTexture2D5 正距方位図法+日照
*
* プロジェクトのプロパティ
* [XNA Game Studio]タブ
* Use HiDef to access the complete API
*/
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace XnaTexture2D
{
class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch sprite;
SpriteFont font;
VertexBuffer vertexBuffer;
Effect effect;
BasicEffect basicEffect;
Texture2D texture;
VertexPositionColor[] cross;
KeyboardState kStateOld = new KeyboardState();
EffectParameter fxLat;
EffectParameter fxLon;
EffectParameter fxAxis;
float lat = 35; // 緯度
float lon = 135; // 経度
// fps
int fpsSec = -1;
int fpsDraw = 0;
int fpsCount = 0;
const double solarYear = 365.24219; // 太陽年(day) 365d5h48m45s
const double anomalisticYear = 365.259643; // 近点年(day) 365d6h13m53.1552s
const double earthRotationPerSec = (2 * Math.PI) / 86400; // 地球が1秒間に回転する角度
const double e = 0.01671022; // 離心率(Orbital eccentricity)
readonly double K = Math.Sqrt((1 + e) / (1 - e)); // ケプラー方程式の定数
const double epsilon = 1.0e-14;
DateTime dt = DateTime.UtcNow;
int tz = 1;
readonly string[] tzName = { "UTC", "JST-9" };
int cursor = 0;
readonly int[] cursorPos = { 0, 5, 8, 11, 14, 15, 17 };
readonly int[] cursorLen = { 4, 2, 2, 2, 1, 1, 3 };
int repeatFrame = 0;
double MJD; // 修正ユリウス日
double MJD_date;
double MJD_time;
double T; // 2000/1/1 12:00(UT)からのユリウス世紀(36525日)
double obliquity; // 黄道傾斜角
double eclipticLon; // 黄径
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = 960;
graphics.PreferredBackBufferHeight = 720;
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void LoadContent()
{
sprite = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>("SpriteFont1");
texture = Content.Load<Texture2D>("earthmap1k");
effect = Content.Load<Effect>("Effect1");
effect.Parameters["EarthMap"].SetValue(texture);
effect.Parameters["aspect"].SetValue(GraphicsDevice.Viewport.AspectRatio);
effect.Parameters["revision"].SetValue(
(float)Math.Sin((0.533 / 2 + (35 * 60 + 8) / 3600.0) * Math.PI / 180));
fxLat = effect.Parameters["centerLatRad"];
fxLon = effect.Parameters["centerLonRad"];
fxAxis = effect.Parameters["axis"];
fxLat.SetValue(MathHelper.ToRadians(lat));
fxLon.SetValue(MathHelper.ToRadians(lon));
SetAxis();
basicEffect = new BasicEffect(GraphicsDevice);
// 地図
VertexPositionTexture[] vertices = new VertexPositionTexture[4];
vertices[0] = new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0));
vertices[1] = new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0));
vertices[2] = new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1));
vertices[3] = new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1));
vertexBuffer = new VertexBuffer(GraphicsDevice,
typeof(VertexPositionTexture), 4, BufferUsage.WriteOnly);
vertexBuffer.SetData(vertices);
// 照準
cross = new VertexPositionColor[4];
cross[0] = new VertexPositionColor(new Vector3(-0.05f, 0, 0), Color.White);
cross[1] = new VertexPositionColor(new Vector3(0.05f, 0, 0), Color.White);
cross[2] = new VertexPositionColor(new Vector3(0, -0.1f, 0), Color.White);
cross[3] = new VertexPositionColor(new Vector3(0, 0.1f, 0), Color.White);
CalcParameter();
base.LoadContent();
}
void CalcParameter()
{
// 修正ユリウス日
int y = dt.Year;
int m = dt.Month;
int d = dt.Day;
if (m < 3)
{
y--;
m += 12;
}
MJD_date = (int)(365.25 * y) + (y / 400) - (y / 100) + (int)(30.59 * (m - 2)) + d - 678912;
MJD_time = (dt.Hour * 60 + dt.Minute) / 1440.0;
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;
// 平均近点角(概算)近日点から次の近日点までの角度
double Ma = ModAngle((2 * Math.PI) * ((MJD / anomalisticYear - 0.1242853) % 1.0));
double E;
double Ta; // 真近点角
KeplersEquation(Ma, out E, out Ta);
// 春分点(vernal equinox)の真近点角
double MJDv = ((int)(MJD / solarYear - 0.3399541) + 0.3399541) * solarYear;
double Mv = ModAngle((2 * Math.PI) * ((MJDv / anomalisticYear - 0.1242853) % 1.0));
double Tv;
KeplersEquation(Mv, out E, out Tv);
// 黄径(概算)春分点から次の春分点までの角度
eclipticLon = ModAngle(Ta - Tv);
// 楕円効果と傾斜効果
int ellipseEffect = (int)Math.Round(Ma / earthRotationPerSec - Ta / earthRotationPerSec);
int obliquityEffect = CalcObliquityEffect(obliquity, eclipticLon);
int equationOfTime = ellipseEffect + obliquityEffect; // 均時差
float transit = (43200 - equationOfTime) / 86400.0f; // 南中時
// 天球上の太陽軌道の高さと半径
double solarDecl = Math.Sin(eclipticLon) * obliquity * Math.PI / 180; // 太陽の赤緯
double solarAlt = Math.Sin(solarDecl);
double solarRad = Math.Cos(solarDecl);
effect.Parameters["MJD_time"].SetValue((float)MJD_time);
effect.Parameters["solarDecl"].SetValue((float)solarDecl);
effect.Parameters["solarAlt"].SetValue((float)solarAlt);
effect.Parameters["solarRad"].SetValue((float)solarRad);
effect.Parameters["transit"].SetValue(transit);
}
double ModAngle(double angle)
{
while (angle <= -Math.PI) angle += (2 * Math.PI);
while (Math.PI < angle) angle -= (2 * Math.PI);
return angle;
}
// 傾斜効果の計算
int CalcObliquityEffect(double obliquity, double eclipticLon)
{
// 地球を基準とした太陽の公転
double x = Math.Cos(eclipticLon);
double r = Math.Sin(eclipticLon);
double y = Math.Cos(obliquity * Math.PI / 180) * r;
double celestialEquator = Math.Atan2(y, x); // 天の赤道上の角度
return (int)Math.Round(eclipticLon / earthRotationPerSec - celestialEquator / earthRotationPerSec);
}
// 漸化式によりケプラー方程式を解く
// M 平均近点角(mean anomaly)
// E 離心近点角(Eccentric anomaly)
// T 真近点角(true anomaly)
void KeplersEquation(double M, out double E, out double T)
{
double E0 = M; // 初項
for (int i = 0; ; )
{
i++;
E = M + e * Math.Sin(E0);
if ((E0 - epsilon < E) && (E < E0 + epsilon))
{
break;
}
if (10 <= i)
{
Console.WriteLine(string.Format("計算打ち切り M={0} E={1}", M, E));
break;
}
E0 = E;
}
T = Math.Atan(K * Math.Tan(E / 2)) * 2;
}
protected override void Update(GameTime gameTime)
{
int delta = 0;
KeyboardState kState = Keyboard.GetState();
if (kState.IsKeyDown(Keys.Escape)) Exit();
if (kState.IsKeyDown(Keys.Left) && kStateOld.IsKeyUp(Keys.Left))
{
cursor = (cursor + 6) % 7;
}
if (kState.IsKeyDown(Keys.Right) && kStateOld.IsKeyUp(Keys.Right))
{
cursor = (cursor + 1) % 7;
}
if (kState.IsKeyDown(Keys.Up)) delta = 1;
if (kState.IsKeyDown(Keys.Down)) delta = -1;
kStateOld = kState;
if (delta == 0)
{
repeatFrame = 0;
}
else
{
if (--repeatFrame <= 0)
{
switch (cursor)
{
case 0: dt = dt.AddYears(delta); break;
case 1: dt = dt.AddMonths(delta); break;
case 2: dt = dt.AddDays(delta); break;
case 3: dt = dt.AddHours(delta); break;
case 4: dt = dt.AddMinutes(delta * 10); break;
case 5: dt = dt.AddMinutes(delta); break;
case 6: tz = (tz + 1) % 2; break;
}
if (cursor != 6)
{
CalcParameter();
}
repeatFrame = (repeatFrame == 0) ? 6 : 30;
}
}
base.Update(gameTime);
}
void SetAxis()
{
float rad = MathHelper.ToRadians(lat);
float y = (float)Math.Sin(rad);
float r = (float)Math.Cos(rad);
rad = MathHelper.ToRadians(lon);
float z = (float)Math.Cos(rad) * r;
float x = (float)Math.Sin(rad) * r;
Vector3 v = new Vector3(x, y, z);
v.Normalize();
fxAxis.SetValue(v);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp;
GraphicsDevice.SetVertexBuffer(vertexBuffer);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2);
}
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineList, cross, 0, 2);
}
// fps
fpsDraw++;
if (gameTime.TotalGameTime.Seconds != fpsSec)
{
fpsCount = fpsDraw;
fpsDraw = 0;
fpsSec = gameTime.TotalGameTime.Seconds;
}
sprite.Begin();
string text = new string(' ', cursorPos[cursor]) + new string('_', cursorLen[cursor]);
sprite.DrawString(font, text, new Vector2(0, 0), Color.White);
text = string.Format("{0} {1}", dt.AddHours(tz * 9).ToString("yyyy/MM/dd HH:mm"), tzName[tz]);
sprite.DrawString(font, text, new Vector2(0, 0), Color.White);
text = string.Format("fps={0} lat={1:f1} lon={2:f1}", fpsCount, lat, lon);
sprite.DrawString(font, text, new Vector2(0, 20), Color.White);
sprite.End();
base.Draw(gameTime);
}
}
}