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

参考

Game1.cs
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
 
namespace GreatCircleRoute
{
    class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        BasicEffect effect;
        SpriteBatch sprite;
        SpriteFont font;
        Texture2D texture;
 
        // fps
        int sec;
        int draw = 0;
        int fps = 0;
 
        MouseState mStateOld = new MouseState();
        float degLat;
        float degLong;
        float startLat;
        float startLong;
        bool drawSlerp;
        const int div = 16;
        VertexPositionColor[] vertices = new VertexPositionColor[div + 1];
        VertexPositionColor[][] equidistantCurve = new VertexPositionColor[5][]; // 等距離線
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1000;
            graphics.PreferredBackBufferHeight = 500;
            Content.RootDirectory = "Content";
            IsMouseVisible = true;
        }
 
        protected override void LoadContent()
        {
            effect = new BasicEffect(GraphicsDevice);
            sprite = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("SpriteFont1");
            texture = Content.Load<Texture2D>("earthmap1k");
 
            effect.VertexColorEnabled = true;
 
            base.LoadContent();
        }
 
        protected override void Update(GameTime gameTime)
        {
            KeyboardState kState = Keyboard.GetState();
            if (kState.IsKeyDown(Keys.Escape)) Exit();
 
            MouseState mState = Mouse.GetState();
            int x = Clamp(mState.X, 0, 1000 - 1);
            int y = Clamp(mState.Y, 0, 500 - 1);
            degLong = (x - 500) * 180.0f / 500;
            degLat = (250 - y) * 90.0f / 250;
            if (mState.LeftButton == ButtonState.Pressed && 
                mStateOld.LeftButton == ButtonState.Released)
            {
                startLat = degLat;
                startLong = degLong;
                CalcEquidistantCurve();
            }
            drawSlerp = (mState.LeftButton == ButtonState.Pressed);
            mStateOld = mState;
 
            base.Update(gameTime);
        }
 
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
 
            // fps
            draw++;
            if (gameTime.TotalGameTime.Seconds != sec)
            {
                fps = draw;
                draw = 0;
                sec = gameTime.TotalGameTime.Seconds;
            }
 
            sprite.Begin();
            sprite.Draw(texture, new Vector2(0, 0), Color.White);
            string text = string.Format("fps={0} lat={1:f1} long={2:f1}", fps, degLat, degLong);
            sprite.DrawString(font, text, new Vector2(0, 0), Color.Red);
            if (drawSlerp)
            {
                CalcGcr();
                text = string.Format("start lat={0:f1} long={1:f1}", startLat, startLong);
                sprite.DrawString(font, text, new Vector2(0, 20), Color.Red);
                for (int n = 0; n <= div; n++)
                {
                    text = string.Format("{0} lat={1:f1} long={2:f1}",
                        n, vertices[n].Position.Y * 90, vertices[n].Position.X * 180);
                    sprite.DrawString(font, text, new Vector2(0, 20 * (n + 2)), Color.Yellow);
                }
            }
            sprite.End();
 
            if (drawSlerp)
            {
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                    GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                        PrimitiveType.LineStrip, vertices, 0, vertices.Length - 1);
 
                    foreach (var curve in equidistantCurve)
                    {
                        GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                            PrimitiveType.LineStrip, curve, 0, curve.Length - 1);
                    }
                }
            }
 
            base.Draw(gameTime);
        }
 
        int Clamp(int value, int min, int max)
        {
            return Math.Min(Math.Max(value, min), max);
        }
 
        bool CalcGcr()
        {
            float x, y, z, r, rad;
 
            // 開始座標
            rad = MathHelper.ToRadians(startLat);
            y = (float)Math.Sin(rad);
            r = (float)Math.Cos(rad);
            rad = MathHelper.ToRadians(startLong);
            z = (float)Math.Cos(rad) * r;
            x = (float)Math.Sin(rad) * r;
            Quaternion q1 = new Quaternion(x, y, z, 0);
 
            // 終了座標
            rad = MathHelper.ToRadians(degLat);
            y = (float)Math.Sin(rad);
            r = (float)Math.Cos(rad);
            rad = MathHelper.ToRadians(degLong);
            z = (float)Math.Cos(rad) * r;
            x = (float)Math.Sin(rad) * r;
            Quaternion q2 = new Quaternion(x, y, z, 0);
 
            for (int t = 0; t <= div; t++)
            {
                Quaternion q = Slerp(q1, q2, t / (float)div);
                float latitude = MathHelper.ToDegrees((float)Math.Asin(q.Y));
                float longitude = MathHelper.ToDegrees((float)Math.Atan2(q.X, q.Z));
                x = longitude / 180;
                y = latitude / 90;
                vertices[t] = new VertexPositionColor(new Vector3(x, y, 0), Color.Yellow);
            }
            return true;
        }
 
        Quaternion Slerp(Quaternion value1, Quaternion value2, float amount)
        {
            value1.Normalize();
            value2.Normalize();
            float dot = Quaternion.Dot(value1, value2); // cosθ
            float angle = (float)Math.Acos(dot); // 2ベクトル間の角度
            float sinTheta = (float)Math.Sin(angle);
            if (sinTheta == 0 || float.IsNaN(sinTheta)) return value1;
            float Ps = (float)Math.Sin(angle * (1 - amount)) / sinTheta;
            float Pe = (float)Math.Sin(angle * amount) / sinTheta;
            Quaternion q = value1 * Ps + value2 * Pe;
            q.Normalize();
            return q;
        }
 
        void CalcEquidistantCurve()
        {
            float x, y, z, r, rad;
 
            // 開始座標
            rad = MathHelper.ToRadians(startLat);
            y = (float)Math.Sin(rad);
            r = (float)Math.Cos(rad);
            rad = MathHelper.ToRadians(startLong);
            z = (float)Math.Cos(rad) * r;
            x = (float)Math.Sin(rad) * r;
            Vector3 v = new Vector3(x, y, z); // 開始座標が回転軸
            v.Normalize();
 
            // 等距離線
            for (int dist = 0; dist < 5; dist++)
            {
                rad = MathHelper.ToRadians(startLat + 30 * (dist + 1)); // 30度毎
                y = (float)Math.Sin(rad);
                r = (float)Math.Cos(rad);
                rad = MathHelper.ToRadians(startLong);
                z = (float)Math.Cos(rad) * r;
                x = (float)Math.Sin(rad) * r;
                Quaternion p = new Quaternion(x, y, z, 0); // 回転させたい点
 
                equidistantCurve[dist] = new VertexPositionColor[div + 1];
                for (int t = 0; t <= div; t++) // 1周360度をdiv分割
                {
                    Quaternion quat = Quaternion.CreateFromAxisAngle(v, MathHelper.TwoPi * t / div);
                    Quaternion q = Quaternion.Conjugate(quat) * p * quat;
                    float latitude = MathHelper.ToDegrees((float)Math.Asin(q.Y));
                    float longitude = MathHelper.ToDegrees((float)Math.Atan2(q.X, q.Z));
                    x = longitude / 180;
                    y = latitude / 90;
                    equidistantCurve[dist][t] = new VertexPositionColor(
                        new Vector3(x, y, 0), Color.Cyan);
                }
            }
        }
    }
}
 
最終更新:2012年12月16日 17:34