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);
}
}
}
}
}