/*
* 太陽年を春分点の平均回帰年とし、365.24219日(365d5h48m45s)を採用する。
* 黄道傾斜角(地球の赤道傾斜角)を23deg26m21.406s(=84381.406s) 2000/01/01 12:00(UT)とする。
*/
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace AstroSim1
{
class Game1 : Game
{
GraphicsDeviceManager graphics;
SpriteBatch sprite;
SpriteFont font;
BasicEffect effect;
VertexBuffer vbLine;
IndexBuffer ibLine;
VertexBuffer vbTriangle;
IndexBuffer ibTriangle;
List<VertexPositionColor> triangleVertices = new List<VertexPositionColor>();
List<short> triangleIndices = new List<short>();
const double au = 149597870700; // 天文単位(m)
const float Sr = 1392000 * 1000 / 2; // 太陽の半径(m)
const double solarYear = 365.24219; // 太陽年(day)
const double obliquity = 84381.406; // 黄道傾斜角(degsec)
const double siderealDay = 86164.091; // 恒星日(s) 23h56m4.091s
Vector3 camPos = new Vector3((float)(au * 2), 0, 0);
float camLat = 0;
float camLon = 180;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
graphics.PreferredBackBufferWidth = 1280;
graphics.PreferredBackBufferHeight = 720;
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void LoadContent()
{
sprite = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>("SpriteFont1");
effect = new BasicEffect(GraphicsDevice);
effect.VertexColorEnabled = true;
effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45),
GraphicsDevice.Viewport.AspectRatio, (float)(au * 0.001), (float)(au * 5));
int solarYearSec = (int)(solarYear * 24 * 60 * 60); // 太陽年(s)
double radObliq = obliquity * Math.PI / (180 * 60 * 60);
double cosObliq = Math.Cos(radObliq);
double sinObliq = Math.Sin(radObliq);
// 頂点
VertexPositionColor[] lineVertices = new VertexPositionColor[(365 + 1) * 4];
int day = 0;
for (int t = 0; t < solarYearSec; t += 86400) // 24 * 60 * 60
{
float rad = MathHelper.TwoPi * t / solarYearSec;
float x = (float)(Math.Cos(rad) * au);
float r = (float)(Math.Sin(rad) * au);
float y = (float)cosObliq * r;
float z = (float)sinObliq * r;
lineVertices[0 + day] = new VertexPositionColor(
new Vector3(x, r, 0), Color.Red); // 黄道傾斜角=0(天の赤道面)
lineVertices[366 + day] = new VertexPositionColor(
new Vector3(x, y, z), Color.Yellow); // 黄道
lineVertices[366 * 2 + day] = new VertexPositionColor(
new Vector3(x, y, 0), Color.Cyan); // 黄道を天の赤道面に投影
lineVertices[366 * 3 + day] = new VertexPositionColor(
new Vector3(0, r, x), Color.Red); // 夏至と冬至を結ぶ大円
day++;
GenerateSun(x, y, 0, Color.Cyan);
}
// 索引
short[] lineIndices = new short[lineVertices.Length * 2];
for (int i = 0; i < 4; i++)
{
int k = 366 * i;
for (int j = 0; j < 366; j++)
{
lineIndices[(k + j) * 2 + 0] = (short)(k + j);
lineIndices[(k + j) * 2 + 1] = (short)(k + (j + 1) % 366);
}
}
vbLine = new VertexBuffer(GraphicsDevice,
typeof(VertexPositionColor), lineVertices.Length, BufferUsage.WriteOnly);
vbLine.SetData(lineVertices);
ibLine = new IndexBuffer(GraphicsDevice,
typeof(short), lineIndices.Length, BufferUsage.WriteOnly);
ibLine.SetData(lineIndices);
// 黄道傾斜角が南中時間に与える影響
day = 0;
for (int t = 0; t < solarYearSec; t++)
{
// 地球を基準とした太陽の公転
double rad = (2 * Math.PI) * t / solarYearSec;
double x = Math.Cos(rad) * au;
double r = Math.Sin(rad) * au;
double y = cosObliq * r;
//double z = sinObliq * r;
double radCelestialEquator = Math.Atan2(y, x); // 天の赤道上の角度
if (radCelestialEquator < 0) radCelestialEquator += (2 * Math.PI);
// 地球の自転
double radEarthRotation = (2 * Math.PI) * ((t / siderealDay) % 1.0);
if (radCelestialEquator <= radEarthRotation)
{
// 60秒進むと太陽半個分上昇
GenerateSun((float)x, (float)y, Sr * (t - 86400 * day) / 60, Color.Blue);
day++;
t += 86164; // 1回転する間は追い越さないので飛ばす
}
else if (0.001 <= radCelestialEquator - radEarthRotation)
{
t += 9; // 角度の差が0.001(およそ14sに相当)以上なら9+1s飛ばす
}
}
vbTriangle = new VertexBuffer(GraphicsDevice,
typeof(VertexPositionColor), triangleVertices.Count, BufferUsage.WriteOnly);
vbTriangle.SetData(triangleVertices.ToArray());
ibTriangle = new IndexBuffer(GraphicsDevice,
typeof(short), triangleIndices.Count, BufferUsage.WriteOnly);
ibTriangle.SetData(triangleIndices.ToArray());
base.LoadContent();
}
void GenerateSun(float x, float y, float z, Color c)
{
int i = triangleVertices.Count;
triangleVertices.Add(new VertexPositionColor(new Vector3(x, y, z + Sr), c));
triangleVertices.Add(new VertexPositionColor(new Vector3(x + Sr, y, z), c));
triangleVertices.Add(new VertexPositionColor(new Vector3(x, y + Sr, z), c));
triangleVertices.Add(new VertexPositionColor(new Vector3(x - Sr, y, z), c));
triangleVertices.Add(new VertexPositionColor(new Vector3(x, y - Sr, z), c));
triangleVertices.Add(new VertexPositionColor(new Vector3(x, y, z - Sr), c));
GenerateTriangle(i, 0, 2, 1);
GenerateTriangle(i, 0, 3, 2);
GenerateTriangle(i, 0, 4, 3);
GenerateTriangle(i, 0, 1, 4);
GenerateTriangle(i, 1, 2, 5);
GenerateTriangle(i, 2, 3, 5);
GenerateTriangle(i, 3, 4, 5);
GenerateTriangle(i, 4, 1, 5);
}
void GenerateTriangle(int vertexNum, int v1, int v2, int v3)
{
triangleIndices.Add((short)(vertexNum + v1));
triangleIndices.Add((short)(vertexNum + v2));
triangleIndices.Add((short)(vertexNum + v3));
}
protected override void Update(GameTime gameTime)
{
KeyboardState kState = Keyboard.GetState();
if (kState.IsKeyDown(Keys.Escape)) Exit();
if (kState.IsKeyDown(Keys.W)) Move(0, 0);
if (kState.IsKeyDown(Keys.S)) Move(180, 0);
if (kState.IsKeyDown(Keys.A)) Move(0, 90);
if (kState.IsKeyDown(Keys.D)) Move(0, -90);
if (kState.IsKeyDown(Keys.Up)) camLat = Math.Min(camLat + 0.5f, 89.9f);
if (kState.IsKeyDown(Keys.Down)) camLat = Math.Max(camLat - 0.5f, -89.9f);
if (kState.IsKeyDown(Keys.Left)) camLon = (camLon + 1) % 360;
if (kState.IsKeyDown(Keys.Right)) camLon = (camLon + 359) % 360;
if (kState.IsKeyDown(Keys.PageUp)) Move(90, 0);
if (kState.IsKeyDown(Keys.PageDown)) Move(-90, 0);
base.Update(gameTime);
}
private void Move(float lat, float lon)
{
float rad = MathHelper.ToRadians(camLat + lat);
float z = (float)(Math.Sin(rad) * au * 0.0025);
float r = (float)(Math.Cos(rad) * au * 0.0025);
if (lon == 0) camPos.Z += z;
rad = MathHelper.ToRadians(camLon + lon);
float x = (float)Math.Cos(rad) * r;
float y = (float)Math.Sin(rad) * r;
camPos.X += x;
camPos.Y += y;
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.BlendState = BlendState.AlphaBlend;
GraphicsDevice.RasterizerState = new RasterizerState { FillMode = FillMode.WireFrame };
// カメラ
float rad = MathHelper.ToRadians(camLat);
float z = (float)(Math.Sin(rad) * au);
float r = (float)(Math.Cos(rad) * au);
rad = MathHelper.ToRadians(camLon);
float x = (float)Math.Cos(rad) * r;
float y = (float)Math.Sin(rad) * r;
effect.View = Matrix.CreateLookAt(camPos, camPos + new Vector3(x, y, z), Vector3.UnitZ);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.SetVertexBuffer(vbLine);
GraphicsDevice.Indices = ibLine;
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.LineList,
0, 0, vbLine.VertexCount, 0, ibLine.IndexCount / 2);
GraphicsDevice.SetVertexBuffer(vbTriangle);
GraphicsDevice.Indices = ibTriangle;
GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0, 0, vbTriangle.VertexCount, 0, ibTriangle.IndexCount / 3);
}
sprite.Begin();
string text = string.Format("x={0:f2} y={1:f2} z={2:f2}",
camPos.X / au, camPos.Y / au, camPos.Z / au);
sprite.DrawString(font, text, new Vector2(0, 0), Color.White);
text = string.Format("lat={0:f0} lon={1:f0}", camLat, camLon);
sprite.DrawString(font, text, new Vector2(0, 20), Color.White);
sprite.End();
base.Draw(gameTime);
}
}
}