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