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

Game1.cs
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
 
namespace AstroSim2
{
    class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch sprite;
        SpriteFont font;
        BasicEffect effect;
 
        VertexBuffer vbLine;
        IndexBuffer ibLine;
        List<VertexPositionColor> lineVertices = new List<VertexPositionColor>();
        List<short> lineIndices = new List<short>();
 
        VertexBuffer vbTriangle;
        IndexBuffer ibTriangle;
        List<VertexPositionColor> triangleVertices = new List<VertexPositionColor>();
        List<short> triangleIndices = new List<short>();
 
        Vector3[] ellipticalOrbit = new Vector3[365 + 1]; // 楕円軌道
        int[] ellipseEffect = new int[365 + 1]; // 楕円効果
        int[] obliquityEffect = new int[365 + 1]; // 傾斜効果
 
        const int meanSolarDay = 24 * 60 * 60; // 平均太陽日(s)
        const double siderealYear = 365.25636; // 恒星年(day) 365d6h9m9.504s
        const double solarYear = 365.24219; // 太陽年(day) 365d5h48m45s
        const double anomalisticYear = 365.259643; // 近点年(day) 31558433.1552s 365d6h13m53.1552s
        const int siderealYearSec = (int)(siderealYear * meanSolarDay); // 恒星年(s)
        const int solarYearSec = (int)(solarYear * meanSolarDay); // 太陽年(s)
        const int anomalisticYearSec = (int)(anomalisticYear * meanSolarDay); // 近点年(s)
 
        const double au = 149597870700; // 天文単位(m)
        const double a = au * 1.00000011; // 軌道長半径(Semimajor axis)(m)
        const double e = 0.01671022; // 離心率(Orbital eccentricity)
        readonly double K = Math.Sqrt((1 + e) / (1 - e)); // ケプラー方程式の定数
        const double siderealDay = 86164.0905; // 恒星日(s) 23h56m4.0905s
        const double earthRotationPerSec = (2 * Math.PI) / siderealDay; // 1秒間に地球が回転する角度
        const double obliquity = 84381.406; // 黄道傾斜角(degsec) 23deg26m21.406s
        const float Sr = 1392000 * 1000 / 2; // 太陽の半径(m)
        const double epsilon = 1.0e-14;
 
        Vector3 camPos = new Vector3(0, (float)(au * -2), 0);
        float camLat = 0;
        float camLon = 90;
 
        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));
 
            CalcEllipticalOrbit(); // 楕円軌道の計算
            CalcEllipseEffect(); // 楕円効果の計算
            CalcObliquityEffect(); // 傾斜効果の計算
 
            // 各種ラインの生成
            GenerateLines();
 
            vbLine = new VertexBuffer(GraphicsDevice,
                typeof(VertexPositionColor), lineVertices.Count, BufferUsage.WriteOnly);
            vbLine.SetData(lineVertices.ToArray());
 
            ibLine = new IndexBuffer(GraphicsDevice,
                typeof(short), lineIndices.Count, BufferUsage.WriteOnly);
            ibLine.SetData(lineIndices.ToArray());
 
            // 太陽の生成
            GenerateSun((float)(a * e), 0, 0, Color.Yellow);
 
            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();
        }
 
        // 漸化式によりケプラー方程式を解く
        // 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;
            if (T < 0) T += (2 * Math.PI);
        }
 
        // 楕円軌道の計算
        void CalcEllipticalOrbit()
        {
            int day = 0;
            for (int t = 0; t < siderealYearSec; t += meanSolarDay) // 恒星年で近似
            {
                double E, T;
                KeplersEquation((2 * Math.PI) * t / siderealYearSec, out E, out T);
                double r = a * (1 - e * Math.Cos(E));
                float x = (float)(Math.Cos(T) * r + a * e); // 焦点との距離を加算
                float y = (float)(Math.Sin(T) * r);
                ellipticalOrbit[day++] = new Vector3(x, y, 0);
            }
        }
 
        // 楕円効果の計算
        void CalcEllipseEffect()
        {
            int day = 0;
            for (int t = 0; t < anomalisticYearSec; t++) // 近点年
            {
                double E, T;
                KeplersEquation((2 * Math.PI) * t / anomalisticYearSec, out E, out T);
 
                // 地球の自転
                double earthRotation = (2 * Math.PI) * ((t / siderealDay) % 1.0);
                if (T <= earthRotation)
                {
                    ellipseEffect[day] = t - meanSolarDay * day;
                    day++;
                    t += 86164; // 1回転する間は追い越さないので飛ばす
                }
                else
                {
                    // 角度の差だけ秒を進める。小数点以下は切捨て。桁落ちによる誤差増大に注意
                    t += (int)((T / earthRotationPerSec) - (earthRotation / earthRotationPerSec));
                }
            }
        }
 
        // 傾斜効果の計算
        void CalcObliquityEffect()
        {
            double radObliq = obliquity * Math.PI / (180 * 60 * 60);
            double cosObliq = Math.Cos(radObliq);
            int day = 0;
            for (int t = 0; t < solarYearSec; t++) // 太陽年
            {
                // 地球を基準とした太陽の公転
                double rad = (2 * Math.PI) * t / solarYearSec;
                double x = Math.Cos(rad);
                double r = Math.Sin(rad);
                double y = cosObliq * r;
                double celestialEquator = Math.Atan2(y, x); // 天の赤道上の角度
                if (celestialEquator < 0) celestialEquator += (2 * Math.PI);
 
                // 地球の自転
                double earthRotation = (2 * Math.PI) * ((t / siderealDay) % 1.0);
                if (celestialEquator <= earthRotation)
                {
                    obliquityEffect[day] = t - meanSolarDay * day;
                    day++;
                    t += 86164; // 1回転する間は追い越さないので飛ばす
                }
                else
                {
                    t += (int)((celestialEquator / earthRotationPerSec)
                        - (earthRotation / earthRotationPerSec));
                }
            }
        }
 
        // 各種ラインの生成
        void GenerateLines()
        {
            double aspectRatio = Math.Sqrt(1 - e * e); // 楕円軌道の長短比
            double b = a * aspectRatio; // 軌道短半径(m)
 
            // 長径と短径
            lineVertices.Add(new VertexPositionColor(new Vector3((float)-a, 0, 0), Color.White));
            lineVertices.Add(new VertexPositionColor(new Vector3((float)a, 0, 0), Color.White));
            lineVertices.Add(new VertexPositionColor(new Vector3(0, (float)-b, 0), Color.White));
            lineVertices.Add(new VertexPositionColor(new Vector3(0, (float)b, 0), Color.White));
            for (int i = 0; i < 4; i++)
            {
                lineIndices.Add((short)i);
            }
 
            // 楕円軌道
            int vertexNum = lineVertices.Count;
            for (int i = 0; i < 365; i++)
            {
                lineVertices.Add(new VertexPositionColor(ellipticalOrbit[i], Color.White));
                lineIndices.Add((short)(vertexNum + i));
                lineIndices.Add((short)(vertexNum + (i + 1) % 365));
            }
 
            // 楕円効果
            vertexNum = lineVertices.Count;
            for (int i = 0; i < 365; i++)
            {
                Vector3 pos = ellipticalOrbit[i];
                pos.Z = (float)(Sr * ellipseEffect[i] / 30); // 30秒進むと太陽半個分上昇
                lineVertices.Add(new VertexPositionColor(pos, Color.Cyan));
                lineIndices.Add((short)(vertexNum + i));
                lineIndices.Add((short)(vertexNum + (i + 1) % 365));
            }
 
            // 傾斜効果
            vertexNum = lineVertices.Count;
            for (int i = 0; i < 365; i++)
            {
                Vector3 pos = ellipticalOrbit[i];
                int j = (290 + i) % 365; // 春分点3/20 近日点1/4 と仮定
                pos.Z = (float)(Sr * obliquityEffect[j] / 30);
                lineVertices.Add(new VertexPositionColor(pos, Color.Yellow));
                lineIndices.Add((short)(vertexNum + i));
                lineIndices.Add((short)(vertexNum + (i + 1) % 365));
            }
 
            // 均時差
            vertexNum = lineVertices.Count;
            for (int i = 0; i < 365; i++)
            {
                Vector3 pos = ellipticalOrbit[i];
                int j = (290 + i) % 365;
                pos.Z = (float)(Sr * (ellipseEffect[i] + obliquityEffect[j]) / 30);
                lineVertices.Add(new VertexPositionColor(pos, Color.Red));
                lineIndices.Add((short)(vertexNum + i));
                lineIndices.Add((short)(vertexNum + (i + 1) % 365));
            }
        }
 
        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);
        }
 
        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;
 
            // カメラ
            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月04日 11:28