#region Copyright
/*
--------------------------------------------------------------------------------
This source file is part of Xenocide
by Project Xenocide Team
For the latest info on Xenocide, see http://www.projectxenocide.com/
This work is licensed under the Creative Commons
Attribution-NonCommercial-ShareAlike 2.5 License.
To view a copy of this license, visit
http://creativecommons.org/licenses/by-nc-sa/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.
--------------------------------------------------------------------------------
*/
/*
* @file GeoscapeScene.cs
* @date Created: 2007/01/25
* @author File creator: dteviot
* @author Credits: none
*/
#endregion
#region Using Statements
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Drawing;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using ProjectXenocide.Utils;
using ProjectXenocide.Model;
using ProjectXenocide.Model.Geoscape;
using ProjectXenocide.Model.Geoscape.Outposts;
using ProjectXenocide.Model.Geoscape.Vehicles;
using ProjectXenocide.Model.Geoscape.AI;
using ProjectXenocide.UI.Scenes.Common;
#endregion
namespace ProjectXenocide.UI.Scenes.Geoscape
{
///
/// This is the 3D scene that appears on the Geoscape screen
///
public class GeoscapeScene : PolarScene, IDisposable
{
BasicEffect basicEffect;
EarthGlobe earth = new EarthGlobe();
SkyBox skybox = new SkyBox();
GeoHud geoHud = new GeoHud();
Effect effect;
String geoTechnique = String.Empty;
///
/// Constructor
///
/// Initial position of camera in scene
public GeoscapeScene(Vector3 cameraPosition)
:
base(cameraPosition)
{
}
///
/// Implement IDisposable
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Implement IDisposable
///
/// false when called from a finalizer
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (basicEffect != null)
{
basicEffect.Dispose();
basicEffect = null;
}
if (skybox != null)
{
skybox.Dispose();
skybox = null;
}
}
}
///
/// Load the graphic content of the scene
///
/// content manager that fetches the content
/// the display
public override void LoadContent(ContentManager content, GraphicsDevice device)
{
InitializeEffect(device);
earth.LoadContent(device);
skybox.LoadContent(content, device);
geoHud.LoadContent(content, device);
effect = content.Load(@"Content\Shaders\GeoscapeShader");
// figure out which shader we call to render the geoscape
geoTechnique = (Util.GetShaderVersion(device.GraphicsDeviceCapabilities) < 2) ? "RenderGlobeStandard" : "RenderGlobeWithBump";
}
private void InitializeEffect(GraphicsDevice device)
{
basicEffect = new BasicEffect(device, null);
basicEffect.Alpha = 1.0f;
basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
basicEffect.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
basicEffect.SpecularPower = 5.0f;
basicEffect.AmbientLightColor = new Vector3(0.40f, 0.40f, 0.40f);
basicEffect.DirectionalLight0.Enabled = true;
basicEffect.DirectionalLight0.DiffuseColor = Vector3.One;
basicEffect.DirectionalLight0.SpecularColor = Vector3.One;
basicEffect.DirectionalLight1.Enabled = false;
basicEffect.LightingEnabled = true;
}
///
/// Render the scene to the display
///
/// Time since last render
/// device to render to
/// child window to render scene to
public override void Draw(GameTime gameTime, GraphicsDevice device, CeGui.Rect sceneWindow)
{
// only draw in area we've been told to
Viewport oldview = device.Viewport;
device.Viewport = CalcViewportForSceneWindow(sceneWindow, device.Viewport);
// set up camera's position
Vector3 cartesianCamera = GeoPosition.PolarToCartesian(CameraPosition);
Matrix worldMatrix = Matrix.CreateTranslation(cartesianCamera);
Matrix viewMatrix = Matrix.CreateLookAt(
cartesianCamera,
Vector3.Zero,
Vector3.Up
);
// Position skybox in world (it's centered on camera position)
Matrix skyboxMatrix = Matrix.CreateTranslation(cartesianCamera) * viewMatrix;
skyboxMatrix *= GetProjectionMatrix(AspectRatio);
// draw the skybox
skybox.Draw(device, skyboxMatrix, 0.6f);
// Set the state for the globe
device.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
device.RenderState.DepthBufferEnable = true;
device.RenderState.DepthBufferWriteEnable = true;
basicEffect.World = worldMatrix;
basicEffect.TextureEnabled = true;
basicEffect.Projection = GetProjectionMatrix(AspectRatio);
basicEffect.View = viewMatrix;
// configure basic effect for globle (want lighting on)
basicEffect.DirectionalLight0.Direction = ComputeSunAngle();
basicEffect.DirectionalLight0.Enabled = true;
basicEffect.LightingEnabled = true;
// need to rotate earth so that 0 longitute is along Z axis
worldMatrix = Matrix.CreateRotationY((float)(Math.PI) / -2.0f);
// setup our custom shader
effect.CurrentTechnique = effect.Techniques[geoTechnique];
effect.Parameters["World"].SetValue(worldMatrix);
effect.Parameters["View"].SetValue(viewMatrix);
effect.Parameters["Projection"].SetValue(basicEffect.Projection);
effect.Parameters["LightDirection"].SetValue(basicEffect.DirectionalLight0.Direction);
earth.Draw(device, effect);
// Draw the X-Corp outposts
GeoPosition cameraGeoPos = new GeoPosition(CameraPosition.X , CameraPosition.Y );
geoHud.Begin();
foreach (Outpost outpost in Xenocide.GameState.GeoData.Outposts)
{
geoHud.DrawIcon(device, outpost, basicEffect, gameTime, cameraGeoPos, outpost.Name, GeoHud.HudIconTypes.XCorpBase);
}
// Draw the X-Corp craft
foreach (Outpost outpost in Xenocide.GameState.GeoData.Outposts)
{
foreach (Craft craft in outpost.Fleet)
{
if (!craft.InBase)
{
geoHud.DrawIcon(device, craft, basicEffect, gameTime, cameraGeoPos, craft.Name, GeoHud.HudIconTypes.XCorpCraft);
}
}
}
// Draw UFOs
bool showAllUfos = Xenocide.StaticTables.StartSettings.Cheats.ShowUndetectedUfos;
foreach (Ufo ufo in Xenocide.GameState.GeoData.Overmind.Ufos)
{
if (ufo.IsKnownToXCorp || showAllUfos)
{
GeoHud.HudIconTypes iconType = GeoHud.HudIconTypes.UfoFly;
if (ufo.Mission.IsLanded)
{
iconType = ufo.IsCrashed ? GeoHud.HudIconTypes.UfoCrash : GeoHud.HudIconTypes.UfoLand;
}
geoHud.DrawIcon(device, ufo, basicEffect, gameTime, cameraGeoPos, ufo.Name, iconType);
}
}
// Draw Alien Bases & Terror sites
foreach (AlienSite site in Xenocide.GameState.GeoData.Overmind.Sites)
{
if (site.IsKnownToXCorp)
{
GeoHud.HudIconTypes iconType = GeoHud.HudIconTypes.AlienBase;
if (site.GetType() == typeof(TerrorMissionAlienSite))
{
iconType = GeoHud.HudIconTypes.TerrorSite;
}
geoHud.DrawIcon(device, site, basicEffect, gameTime, cameraGeoPos, site.Name, iconType);
}
}
geoHud.End();
// Draw Nav Paths
// Draw Radar
// restore viewport
device.Viewport = oldview;
}
///
/// Compute the angle the sun will strike the earth at, based on the Geoscape's time
///
///
private static Vector3 ComputeSunAngle()
{
DateTime now = Xenocide.GameState.GeoData.GeoTime.Time;
// Fraction of a day (note that 0:0:0 UTC is midnight)
TimeSpan dayFraction = now.TimeOfDay;
float dayAngle = (float)((Math.PI * -2.0 * dayFraction.TotalSeconds / 86400.0));
// Fraction of a year
int dayOfYear = now.DayOfYear - 1;
double yearAngle = (Math.PI * 2.0 * dayOfYear / 365.0);
// assume 1st January is midwinter. (Close enough)
float latitudeAngle = (float)(Math.Cos(yearAngle) * MathHelper.ToRadians(22.5f));
return GeoPosition.PolarToCartesian((float)dayAngle, latitudeAngle);
}
///
/// Convert a position in the viewport to a geoposition on the globe
///
/// The position in the viewport (in relative co-ords)
/// The geoposition or null if point isn't on globe
/// Uses equation from http://wikipedia.org/Ray-sphere_intersection.htm
public GeoPosition WindowToGeoPosition(PointF coords)
{
double lx = Math.Tan(ViewAngle / 2.0) * (coords.X - 0.5) * 2.0 * AspectRatio;
double ly = Math.Tan(ViewAngle / 2.0) * (coords.Y - 0.5) * -2.0;
double sz = CameraPosition.Z;
double term = (sz * sz) - ((lx * lx) + (ly * ly) + 1) * ((sz * sz) - 1);
// if term is negative, then postion isn't on the globe
// we're also going to ignore the result when we're close to edge of globe
// because it's difficult to hit a point accurately around there
if (term < 0.1f)
{
return null;
}
// we're only interested in the near solution
double d = (sz - Math.Sqrt(term)) / ((lx * lx) + (ly * ly) + 1);
// convert back to get cartesian co-ordinates
double x = lx * d;
double y = ly * d;
double z = sz - d;
TestWindowToGeoPositionCalcs(x, y, z);
// convert to polar
GeoPosition origin = new GeoPosition(0.0f, 0.0f);
GeoPosition offset = new GeoPosition((float)Math.Atan(x / z), (float)Math.Asin(y));
// add in camera's displacement
float distance = origin.Distance(offset);
float azimuth = origin.GetAzimuth(offset);
GeoPosition camera = new GeoPosition(CameraPosition.X, CameraPosition.Y);
return camera.GetEndpoint(azimuth, distance);
}
///
/// Verify that the point at co-ords x, y and z lies on sphere of radius 1.0
///
/// x co-ord
/// y co-ord
/// z co-ord
[Conditional("DEBUG")]
private static void TestWindowToGeoPositionCalcs(double x, double y, double z)
{
double hyp = (z * z) + (x * x) + (y * y);
Debug.Assert((0.99 < hyp) && (hyp < 1.01));
}
}
}