開発環境 Microsoft Visual C# 2010 Express (SP1)
実行環境 Microsoft Windows XP Home Edition (SP3)
プロジェクトの種類 Windows Game (4.0)
プロジェクト名 AstroSim1

Game1.cs
/*
 * 太陽年を春分点の平均回帰年とし、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);
        }
    }
}
 
最終更新:2013年01月02日 17:23