//-----------------------------------------------------------------------------
// BaseGame.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Threading;
using RacingGame.GameLogic;
using RacingGame.Helpers;
using RacingGame.Properties;
using RacingGame.Shaders;
using RacingGame.Sounds;
#if GAMERSERVICES
using Microsoft.Xna.Framework.GamerServices;
#endif
using RacingGame.GameScreens;
namespace RacingGame.Graphics
{
///
/// Base game class for all the basic game support.
/// Connects all our helper classes together and makes our life easier!
/// Note: This game was designed for 16:9 (e.g. 1920x1200), but it
/// also works just fine for 4:3 (1024x768, 800x600, etc.).
/// This is the reason you might see 1024x640 instead of 1024x768 in
/// some width/height calculations and the UI textures.
/// The game looks best at 1920x1080 (HDTV 1080p resolution).
///
public partial class BaseGame : Microsoft.Xna.Framework.Game
{
///
/// Background color
///
private static readonly Color BackgroundColor = Color.Black;
///
/// Field of view and near and far plane distances for the
/// ProjectionMatrix creation.
///
private const float FieldOfView = (float)Math.PI / 2,
NearPlane = 0.5f,
FarPlane = 1750;
///
/// Viewable field of view for object visibility testing (see Model class)
///
public const float ViewableFieldOfView = FieldOfView / 1.125f;
// UWP COMMENT OUT
// public static PlatformID CurrentPlatform = Environment.OSVersion.Platform;
///
/// Graphics device manager, used for the graphics creation and holds
/// the GraphicsDevice.
///
public static GraphicsDeviceManager graphicsManager = null;
///
/// Content manager
///
protected static ContentManager content = null;
///
/// UI Renderer helper class for all 2d rendering.
///
protected static UIRenderer ui = null;
///
/// Our screen resolution: Width and height of visible render area.
///
protected static int width, height;
///
/// Aspect ratio of our current resolution
///
private static float aspectRatio = 1.0f;
///
/// Remember windows title to check if we are in certain unit tests!
///
private static string remWindowsTitle = "";
///
/// Get windows title to check if we are in certain unit tests!
///
public static string WindowsTitle
{
get
{
return remWindowsTitle;
}
}
///
/// Line manager 2D
///
private static LineManager2D lineManager2D = null;
///
/// Line manager 3D
///
private static LineManager3D lineManager3D = null;
///
/// Mesh render manager to render meshes of models in a highly
/// optimized manner. We don't really have anything stored in here
/// except for a sorted list on how to render everything based on the
/// techniques and the materials and links to the renderable meshes.
///
private static MeshRenderManager meshRenderManager =
new MeshRenderManager();
///
/// Matrices for shaders. Used in a similar way than in Rocket Commander,
/// but since we don't have a fixed function pipeline here we just use
/// these values in the shader. Make sure to set all matrices before
/// calling a shader. Inside a shader you have to update the shader
/// parameter too, just setting the WorldMatrix alone does not work.
///
private static Matrix worldMatrix,
viewMatrix,
projectionMatrix;
///
/// Light direction, please read matrices info above for more details.
/// The same things apply here.
///
private static Vector3 lightDirection = new Vector3(0, 0, 1);
///
/// Light direction
///
/// Vector 3
public static Vector3 LightDirection
{
get
{
return lightDirection;
}
set
{
lightDirection = value;
lightDirection.Normalize();
}
}
///
/// Elapsed time this frame in ms. Always have something valid here
/// in case we devide through this values!
///
private static float elapsedTimeThisFrameInMs = 0.001f, totalTimeMs = 0,
lastFrameTotalTimeMs = 0;
///
/// Helper for calculating frames per second.
///
private static float startTimeThisSecond = 0;
///
/// For more accurate frames per second calculations,
/// just count for one second, then fpsLastSecond is updated.
/// Btw: Start with 1 to help some tests avoid the devide through zero
/// problem.
///
private static int
frameCountThisSecond = 0,
totalFrameCount = 0,
fpsLastSecond = 60;
///
/// Return true every checkMilliseconds.
///
/// Check ms
/// Bool
public static bool EveryMillisecond(int checkMilliseconds)
{
return (int)(lastFrameTotalTimeMs / checkMilliseconds) !=
(int)(totalTimeMs / checkMilliseconds);
}
#if GAMERSERVICES
private static GamerServicesComponent gamerServicesComponent = null;
public static GamerServicesComponent GamerServicesComponent
{
get { return gamerServicesComponent; }
}
#endif
static public GraphicsDevice Device
{
get
{
return graphicsManager.GraphicsDevice;
}
}
///
/// Back buffer depth format
///
static DepthFormat backBufferDepthFormat = DepthFormat.Depth24;
///
/// Back buffer depth format
///
/// Surface format
public static DepthFormat BackBufferDepthFormat
{
get
{
return backBufferDepthFormat;
}
}
private static bool alreadyCheckedGraphicsOptions = false;
///
/// Check options and PS version
///
internal static void CheckOptionsAndPSVersion()
{
GraphicsDevice device = Device;
if (device == null)
throw new InvalidOperationException("Device is not created yet!");
alreadyCheckedGraphicsOptions = true;
usePostScreenShaders = GameSettings.Default.PostScreenEffects;
//TODO Fix Shadow Maps!
allowShadowMapping = GameSettings.Default.ShadowMapping;
highDetail = GameSettings.Default.HighDetail;
}
///
/// Fullscreen
///
/// Bool
public static bool Fullscreen
{
get
{
return graphicsManager.IsFullScreen;
}
}
private static bool highDetail = true;
///
/// High detail
///
/// Bool
public static bool HighDetail
{
get
{
if (alreadyCheckedGraphicsOptions == false)
CheckOptionsAndPSVersion();
return highDetail;
}
}
private static bool allowShadowMapping = true;
///
/// Allow shadow mapping
///
/// Bool
public static bool AllowShadowMapping
{
get
{
if (alreadyCheckedGraphicsOptions == false)
CheckOptionsAndPSVersion();
return allowShadowMapping;
}
}
private static bool usePostScreenShaders = false;
///
/// Use post screen shaders
///
/// Bool
public static bool UsePostScreenShaders
{
get
{
//if (alreadyCheckedGraphicsOptions == false)
// CheckOptionsAndPSVersion();
return usePostScreenShaders;
}
}
private static bool mustApplyDeviceChanges = false;
internal static void ApplyResolutionChange()
{
int resolutionWidth = GameSettings.Default == null ? 0 :
GameSettings.Default.ResolutionWidth;
int resolutionHeight = GameSettings.Default == null ? 0 :
GameSettings.Default.ResolutionHeight;
// Use current desktop resolution if autodetect is selected.
if (resolutionWidth <= 0 ||
resolutionHeight <= 0)
{
resolutionWidth =
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
resolutionHeight =
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
}
#if XBOX360 || XBOXONE
// Xbox 360 graphics settings are fixed
graphicsManager.IsFullScreen = true;
graphicsManager.PreferredBackBufferWidth =
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
graphicsManager.PreferredBackBufferHeight =
GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
#else
graphicsManager.PreferredBackBufferWidth = resolutionWidth;
graphicsManager.PreferredBackBufferHeight = resolutionHeight;
graphicsManager.IsFullScreen = false;//GameSettings.Default.Fullscreen;
graphicsManager.GraphicsDevice.Viewport = new Viewport (0, 0, resolutionWidth, resolutionHeight);
mustApplyDeviceChanges = true;
#endif
}
///
/// Content
///
/// Content manager
public new static ContentManager Content
{
get
{
return content;
}
}
///
/// User interface renderer helper ^^
///
/// UIRenderer
public static UIRenderer UI
{
get
{
return ui;
}
}
///
/// Mesh render manager to render meshes of models in a highly
/// optimized manner.
///
/// Mesh render manager
public static MeshRenderManager MeshRenderManager
{
get
{
return meshRenderManager;
}
}
///
/// Width
///
/// Int
public static int Width
{
get
{
return width;
}
}
///
/// Height
///
/// Int
public static int Height
{
get
{
return height;
}
}
///
/// Aspect ratio
///
/// Float
public static float AspectRatio
{
get
{
return aspectRatio;
}
}
///
/// Resolution rectangle
///
/// Rectangle
public static Rectangle ResolutionRect
{
get
{
return new Rectangle(0, 0, width, height);
}
}
///
/// XToRes helper method to convert 1024x640 to the current
/// screen resolution. Used to position UI elements.
///
/// X in 1024px width resolution
/// Int
public static int XToRes(int xIn1024px)
{
return (int)Math.Round(xIn1024px * BaseGame.Width / 1024.0f);
}
///
/// YToRes helper method to convert 1024x640 to the current
/// screen resolution. Used to position UI elements.
///
/// Y in 640px height
/// Int
public static int YToRes(int yIn640px)
{
return (int)Math.Round(yIn640px * BaseGame.Height / 640.0f);
}
///
/// YTo res 768
///
/// Y in 768px
/// Int
public static int YToRes768(int yIn768px)
{
return (int)Math.Round(yIn768px * BaseGame.Height / 768.0f);
}
///
/// XTo res 1600
///
/// X in 1600px
/// Int
public static int XToRes1600(int xIn1600px)
{
return (int)Math.Round(xIn1600px * BaseGame.Width / 1600.0f);
}
///
/// YTo res 1200
///
/// Y in 1200px
/// Int
public static int YToRes1200(int yIn1200px)
{
return (int)Math.Round(yIn1200px * BaseGame.Height / 1200.0f);
}
///
/// XTo res 1400
///
/// X in 1400px
/// Int
public static int XToRes1400(int xIn1400px)
{
return (int)Math.Round(xIn1400px * BaseGame.Width / 1400.0f);
}
///
/// YTo res 1200
///
/// Y in 1050px
/// Int
public static int YToRes1050(int yIn1050px)
{
return (int)Math.Round(yIn1050px * BaseGame.Height / 1050.0f);
}
///
/// Calc rectangle, helper method to convert from our images (1024)
/// to the current resolution. Everything will stay in the 16/9
/// format of the textures.
///
/// X
/// Y
/// Width
/// Height
/// Rectangle
public static Rectangle CalcRectangle(
int relX, int relY, int relWidth, int relHeight)
{
float widthFactor = width / 1024.0f;
float heightFactor = height / 640.0f;
return new Rectangle(
(int)Math.Round(relX * widthFactor),
(int)Math.Round(relY * heightFactor),
(int)Math.Round(relWidth * widthFactor),
(int)Math.Round(relHeight * heightFactor));
}
///
/// Calc rectangle with bounce effect, same as CalcRectangle, but sizes
/// the resulting rect up and down depending on the bounceEffect value.
///
/// Rel x
/// Rel y
/// Rel width
/// Rel height
/// Bounce effect
/// Rectangle
public static Rectangle CalcRectangleWithBounce(
int relX, int relY, int relWidth, int relHeight, float bounceEffect)
{
float widthFactor = width / 1024.0f;
float heightFactor = height / 640.0f;
float middleX = (relX + relWidth / 2) * widthFactor;
float middleY = (relY + relHeight / 2) * heightFactor;
float retWidth = relWidth * widthFactor * bounceEffect;
float retHeight = relHeight * heightFactor * bounceEffect;
return new Rectangle(
(int)Math.Round(middleX - retWidth / 2),
(int)Math.Round(middleY - retHeight / 2),
(int)Math.Round(retWidth),
(int)Math.Round(retHeight));
}
///
/// Calc rectangle, same method as CalcRectangle, but keep the 4 to 3
/// ratio for the image. The Rect will take same screen space in
/// 16:9 and 4:3 modes. E.g. Buttons should be displayed this way.
/// Should be used for 1024px width graphics.
///
/// Rel x
/// Rel y
/// Rel width
/// Rel height
/// Rectangle
public static Rectangle CalcRectangleKeep4To3(
int relX, int relY, int relWidth, int relHeight)
{
float widthFactor = width / 1024.0f;
float heightFactor = height / 768.0f;
return new Rectangle(
(int)Math.Round(relX * widthFactor),
(int)Math.Round(relY * heightFactor),
(int)Math.Round(relWidth * widthFactor),
(int)Math.Round(relHeight * heightFactor));
}
///
/// Calc rectangle, same method as CalcRectangle, but keep the 4 to 3
/// ratio for the image. The Rect will take same screen space in
/// 16:9 and 4:3 modes. E.g. Buttons should be displayed this way.
/// Should be used for 1024px width graphics.
///
/// Gfx rectangle
/// Rectangle
public static Rectangle CalcRectangleKeep4To3(
Rectangle gfxRect)
{
float widthFactor = width / 1024.0f;
float heightFactor = height / 768.0f;
return new Rectangle(
(int)Math.Round(gfxRect.X * widthFactor),
(int)Math.Round(gfxRect.Y * heightFactor),
(int)Math.Round(gfxRect.Width * widthFactor),
(int)Math.Round(gfxRect.Height * heightFactor));
}
///
/// Calc rectangle for 1600px width graphics.
///
/// Rel x
/// Rel y
/// Rel width
/// Rel height
/// Rectangle
public static Rectangle CalcRectangle1600(
int relX, int relY, int relWidth, int relHeight)
{
float widthFactor = width / 1600.0f;
float heightFactor = (height / 1200.0f);// / (aspectRatio / (16 / 9));
return new Rectangle(
(int)Math.Round(relX * widthFactor),
(int)Math.Round(relY * heightFactor),
(int)Math.Round(relWidth * widthFactor),
(int)Math.Round(relHeight * heightFactor));
}
///
/// Calc rectangle 2000px, just a helper to scale stuff down
///
/// Rel x
/// Rel y
/// Rel width
/// Rel height
/// Rectangle
public static Rectangle CalcRectangle2000(
int relX, int relY, int relWidth, int relHeight)
{
float widthFactor = width / 2000.0f;
float heightFactor = (height / 1500.0f);
return new Rectangle(
(int)Math.Round(relX * widthFactor),
(int)Math.Round(relY * heightFactor),
(int)Math.Round(relWidth * widthFactor),
(int)Math.Round(relHeight * heightFactor));
}
///
/// Calc rectangle keep 4 to 3 align bottom
///
/// Rel x
/// Rel y
/// Rel width
/// Rel height
/// Rectangle
public static Rectangle CalcRectangleKeep4To3AlignBottom(
int relX, int relY, int relWidth, int relHeight)
{
float widthFactor = width / 1024.0f;
float heightFactor16To9 = height / 640.0f;
float heightFactor4To3 = height / 768.0f;
return new Rectangle(
(int)(relX * widthFactor),
(int)(relY * heightFactor16To9) -
(int)Math.Round(relHeight * heightFactor4To3),
(int)Math.Round(relWidth * widthFactor),
(int)Math.Round(relHeight * heightFactor4To3));
}
///
/// Calc rectangle keep 4 to 3 align bottom right
///
/// Rel x
/// Rel y
/// Rel width
/// Rel height
/// Rectangle
public static Rectangle CalcRectangleKeep4To3AlignBottomRight(
int relX, int relY, int relWidth, int relHeight)
{
float widthFactor = width / 1024.0f;
float heightFactor16To9 = height / 640.0f;
float heightFactor4To3 = height / 768.0f;
return new Rectangle(
(int)(relX * widthFactor) -
(int)Math.Round(relWidth * widthFactor),
(int)(relY * heightFactor16To9) -
(int)Math.Round(relHeight * heightFactor4To3),
(int)Math.Round(relWidth * widthFactor),
(int)Math.Round(relHeight * heightFactor4To3));
}
///
/// Calc rectangle centered with given height.
/// This one uses relX and relY points as the center for our rect.
/// The relHeight is then calculated and we align everything
/// with help of gfxRect (determinating the width).
/// Very useful for buttons, logos and other centered UI textures.
///
/// Rel x
/// Rel y
/// Rel height
/// Gfx rectangle
/// Rectangle
public static Rectangle CalcRectangleCenteredWithGivenHeight(
int relX, int relY, int relHeight, Rectangle gfxRect)
{
float widthFactor = width / 1024.0f;
float heightFactor = height / 640.0f;
int rectHeight = (int)Math.Round(relHeight * heightFactor);
// Keep aspect ratio
int rectWidth = (int)Math.Round(
gfxRect.Width * rectHeight / (float)gfxRect.Height);
return new Rectangle(
Math.Max(0, (int)Math.Round(relX * widthFactor) - rectWidth / 2),
Math.Max(0, (int)Math.Round(relY * heightFactor) - rectHeight / 2),
rectWidth, rectHeight);
}
///
/// Fps
///
/// Int
public static int Fps
{
get
{
return fpsLastSecond;
}
}
///
/// Interpolated fps over the last 10 seconds.
/// Obviously goes down if our framerate is low.
///
private static float fpsInterpolated = 100.0f;
///
/// Total frames
///
/// Int
public static int TotalFrames
{
get
{
return totalFrameCount;
}
}
///
/// Elapsed time this frame in ms
///
/// Int
public static float ElapsedTimeThisFrameInMilliseconds
{
get
{
return elapsedTimeThisFrameInMs;
}
}
///
/// Total time in seconds
///
/// Int
public static float TotalTime
{
get
{
return totalTimeMs / 1000.0f;
}
}
///
/// Total time ms
///
/// Float
public static float TotalTimeMilliseconds
{
get
{
return totalTimeMs;
}
}
///
/// Move factor per second, when we got 1 fps, this will be 1.0f,
/// when we got 100 fps, this will be 0.01f.
///
public static float MoveFactorPerSecond
{
get
{
return elapsedTimeThisFrameInMs / 1000.0f;
}
}
///
/// World matrix
///
/// Matrix
public static Matrix WorldMatrix
{
get
{
return worldMatrix;
}
set
{
worldMatrix = value;
// Update worldViewProj here?
}
}
///
/// View matrix
///
/// Matrix
public static Matrix ViewMatrix
{
get
{
return viewMatrix;
}
set
{
// Set view matrix, usually only done in ChaseCamera.Update!
viewMatrix = value;
// Update camera pos and rotation, used all over the game!
invViewMatrix = Matrix.Invert(viewMatrix);
camPos = invViewMatrix.Translation;
cameraRotation = Vector3.TransformNormal(
new Vector3(0, 0, 1), invViewMatrix);
}
}
///
/// Projection matrix
///
/// Matrix
public static Matrix ProjectionMatrix
{
get
{
return projectionMatrix;
}
set
{
projectionMatrix = value;
// Update worldViewProj here?
}
}
///
/// Camera pos, updated each frame in ViewMatrix!
/// Public to allow easy access from everywhere, will be called a lot each
/// frame, for example Model.Render uses this for distance checks.
///
private static Vector3 camPos;
///
/// Get camera position from inverse view matrix. Similar to method
/// used in shader. Works only if ViewMatrix is correctly set.
///
/// Vector 3
public static Vector3 CameraPos
{
get
{
return camPos;
}
}
///
/// Camera rotation, used to compare objects for visibility.
///
private static Vector3 cameraRotation = new Vector3(0, 0, 1);
///
/// Camera rotation
///
/// Vector 3
public static Vector3 CameraRotation
{
get
{
return cameraRotation;
}
}
///
/// Remember inverse view matrix.
///
private static Matrix invViewMatrix;
///
/// Inverse view matrix
///
/// Matrix
public static Matrix InverseViewMatrix
{
get
{
return invViewMatrix;//Matrix.Invert(ViewMatrix);
}
}
///
/// View projection matrix
///
/// Matrix
public static Matrix ViewProjectionMatrix
{
get
{
return ViewMatrix * ProjectionMatrix;
}
}
///
/// World view projection matrix
///
/// Matrix
public static Matrix WorldViewProjectionMatrix
{
get
{
return WorldMatrix * ViewMatrix * ProjectionMatrix;
}
}
///
/// Alpha blending
///
public static void SetAlphaBlendingEnabled(bool value)
{
if (value)
{
Device.BlendState = BlendState.AlphaBlend;
}
else
{
Device.BlendState = BlendState.Opaque;
}
}
///
/// Alpha modes
///
public enum AlphaMode
{
///
/// Disable alpha blending for this (even if the texture has alpha)
///
DisableAlpha,
///
/// Default alpha mode: SourceAlpha and InvSourceAlpha, which does
/// nothing if the texture does not have alpha, else it just displays
/// it as it is (with transparent pixels).
///
Default,
///
/// Use source alpha one mode, this is the default mode for lighting
/// effects.
///
SourceAlphaOne,
///
/// One one alpha mode.
///
OneOne,
}
///
/// Current alpha mode
///
public static void SetCurrentAlphaMode(AlphaMode value)
{
switch (value)
{
case AlphaMode.DisableAlpha:
Device.BlendState = new BlendState()
{
AlphaSourceBlend = Blend.Zero,
AlphaDestinationBlend = Blend.One
};
break;
case AlphaMode.Default:
Device.BlendState = new BlendState()
{
AlphaSourceBlend = Blend.SourceAlpha,
AlphaDestinationBlend = Blend.InverseSourceAlpha
};
break;
case AlphaMode.SourceAlphaOne:
Device.BlendState = new BlendState()
{
AlphaSourceBlend = Blend.SourceAlpha,
AlphaDestinationBlend = Blend.One
};
break;
case AlphaMode.OneOne:
Device.BlendState = new BlendState()
{
AlphaSourceBlend = Blend.One,
AlphaDestinationBlend = Blend.One
};
break;
}
}
///
/// Create base game
///
/// Set windows title
protected BaseGame(string setWindowsTitle)
{
#if GAMERSERVICES
gamerServicesComponent = new GamerServicesComponent(this);
base.Components.Add(gamerServicesComponent);
#endif
// Set graphics
graphicsManager = new GraphicsDeviceManager(this);
graphicsManager.GraphicsProfile = GraphicsProfile.HiDef;
graphicsManager.PreparingDeviceSettings +=
new EventHandler(
graphics_PrepareDevice);
#if DEBUG
// Disable vertical retrace to get highest framerates possible for
// testing performance.
graphicsManager.SynchronizeWithVerticalRetrace = false;
#endif
// Update as fast as possible, do not use fixed time steps.
// The whole game is designed this way, if you remove this line
// the car will not behave normal anymore!
this.IsFixedTimeStep = false;
// Init content manager
BaseGame.content = base.Content;
base.Content.RootDirectory = String.Empty;
// Update windows title (used for unit testing)
this.Window.Title = setWindowsTitle;
this.Window.AllowUserResizing = true;
remWindowsTitle = setWindowsTitle;
}
///
/// Empty constructor for the designer support
///
protected BaseGame()
: this("Game")
{
}
void graphics_PrepareDevice(object sender, PreparingDeviceSettingsEventArgs e)
{
// UWP COMMENT OUT
// if (Environment.OSVersion.Platform != PlatformID.Win32NT)
// {
// PresentationParameters presentParams =
// e.GraphicsDeviceInformation.PresentationParameters;
// presentParams.RenderTargetUsage = RenderTargetUsage.PlatformContents;
// if (graphicsManager.PreferredBackBufferHeight == 720)
// {
// presentParams.MultiSampleCount = 4;
//#if !DEBUG
// presentParams.PresentationInterval = PresentInterval.One;
//#endif
// }
// else
// {
// presentParams.MultiSampleCount = 2;
//#if !DEBUG
// presentParams.PresentationInterval = PresentInterval.Two;
//#endif
// }
// }
}
///
/// Initialize
///
protected override void Initialize()
{
#if !XBOX360
// Add screenshot capturer. Note: Don't do this in constructor,
// we need the correct window name for screenshots!
this.Components.Add(new ScreenshotCapturer(this));
#endif
base.Initialize();
GameSettings.Initialize();
ApplyResolutionChange();
Sound.SetVolumes(GameSettings.Default.SoundVolume,
GameSettings.Default.MusicVolume);
//Init the static screens
Highscores.Initialize();
// Replaces static Constructors with simple inits.
Log.Initialize();
// Set depth format
backBufferDepthFormat = graphicsManager.PreferredDepthStencilFormat;
// Update resolution if it changes
graphicsManager.DeviceReset += graphics_DeviceReset;
graphics_DeviceReset(null, EventArgs.Empty);
// Create matrices for our shaders, this makes it much easier
// to manage all the required matrices since there is no fixed
// function support and theirfore no Device.Transform class.
WorldMatrix = Matrix.Identity;
// ViewMatrix is updated in camera class
ViewMatrix = Matrix.CreateLookAt(
new Vector3(0, 0, 250), Vector3.Zero, Vector3.Up);
// Projection matrix is set by DeviceReset
// Init global manager classes, which will be used all over the place ^^
lineManager2D = new LineManager2D();
lineManager3D = new LineManager3D();
ui = new UIRenderer();
}
///
/// Graphics device reset handler.
///
void graphics_DeviceReset(object sender, EventArgs e)
{
// Update width and height
width = Device.Viewport.Width;//Window.ClientBounds.Width;
height = Device.Viewport.Height;//Window.ClientBounds.Height;
aspectRatio = (float)width / (float)height;
ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
FieldOfView, aspectRatio, NearPlane, FarPlane);
// Re-Set device
// Restore z buffer state
BaseGame.Device.DepthStencilState = DepthStencilState.Default;
// Set u/v addressing back to wrap
BaseGame.Device.SamplerStates[0] = SamplerState.LinearWrap;
// Restore normal alpha blending
BaseGame.SetCurrentAlphaMode(BaseGame.AlphaMode.Default);
//TODO: AlphaTestEffect
// Set 128 and greate alpha compare for Model.Render
//BaseGame.Device.RenderState.ReferenceAlpha = 128;
//BaseGame.Device.RenderState.AlphaFunction = CompareFunction.Greater;
// Recreate all render-targets
foreach (RenderToTexture renderToTexture in remRenderToTextures)
renderToTexture.HandleDeviceReset();
}
///
/// Epsilon (1/1000000) for comparing stuff which is nearly equal.
///
public const float Epsilon = 0.000001f;
///
/// Convert 3D vector to 2D vector, this is kinda the oposite of
/// GetScreenPlaneVector (not shown here). This can be useful for user
/// input/output, because we will often need the actual position on screen
/// of an object in 3D space from the users view to handle it the right
/// way. Used for lens flare and asteroid optimizations.
///
/// 3D world position
/// Resulting 2D screen position
public static Point Convert3DPointTo2D(Vector3 point)
{
Vector4 result4 = Vector4.Transform(point,
ViewProjectionMatrix);
if (result4.W == 0)
result4.W = BaseGame.Epsilon;
Vector3 result = new Vector3(
result4.X / result4.W,
result4.Y / result4.W,
result4.Z / result4.W);
// Output result from 3D to 2D
return new Point(
(int)Math.Round(+result.X * (width / 2)) + (width / 2),
(int)Math.Round(-result.Y * (height / 2)) + (height / 2));
}
///
/// Is point in front of camera?
///
/// Position to check.
/// Bool
public static bool IsInFrontOfCamera(Vector3 point)
{
Vector4 result = Vector4.Transform(
new Vector4(point.X, point.Y, point.Z, 1),
ViewProjectionMatrix);
// Is result in front?
return result.Z > result.W - NearPlane;
}
///
/// Helper to check if a 3d-point is visible on the screen.
/// Will basically do the same as IsInFrontOfCamera and Convert3DPointTo2D,
/// but requires less code and is faster. Also returns just an bool.
/// Will return true if point is visble on screen, false otherwise.
/// Use the offset parameter to include points into the screen that are
/// only a couple of pixel outside of it.
///
/// Point
/// Check offset in percent of total
/// screen
/// Bool
public static bool IsVisible(Vector3 point, float checkOffset)
{
Vector4 result = Vector4.Transform(
new Vector4(point.X, point.Y, point.Z, 1),
ViewProjectionMatrix);
// Point must be in front of camera, else just skip everything.
if (result.Z > result.W - NearPlane)
{
Vector2 screenPoint = new Vector2(
result.X / result.W, result.Y / result.W);
// Change checkOffset depending on how depth we are into the scene
// for very near objects (z < 5) pass all tests!
// for very far objects (z >> 5) only pass if near to +- 1.0f
float zDist = Math.Abs(result.Z);
if (zDist < 5.0f)
return true;
checkOffset = 1.0f + (checkOffset / zDist);
return
screenPoint.X >= -checkOffset && screenPoint.X <= +checkOffset &&
screenPoint.Y >= -checkOffset && screenPoint.Y <= +checkOffset;
}
// Point is not in front of camera, return false.
return false;
}
///
/// Draw line
///
/// Start point
/// End point
/// Color
public static void DrawLine(Point startPoint, Point endPoint, Color color)
{
lineManager2D.AddLine(startPoint, endPoint, color);
}
///
/// Draw line
///
/// Start point
/// End point
public static void DrawLine(Point startPoint, Point endPoint)
{
lineManager2D.AddLine(startPoint, endPoint, Color.White);
}
///
/// Draw line
///
/// Start position
/// End position
/// Color
public static void DrawLine(Vector3 startPos, Vector3 endPos, Color color)
{
lineManager3D.AddLine(startPos, endPos, color);
}
///
/// Draw line
///
/// Start position
/// End position
/// Start color
/// End color
public static void DrawLine(Vector3 startPos, Vector3 endPos,
Color startColor, Color endColor)
{
lineManager3D.AddLine(startPos, startColor, endPos, endColor);
}
///
/// Draw line
///
/// Start position
/// End position
public static void DrawLine(Vector3 startPos, Vector3 endPos)
{
lineManager3D.AddLine(startPos, endPos, Color.White);
}
///
/// Flush line manager 2D. Renders all lines and allows more lines
/// to be rendered. Used to render lines into textures and stuff.
///
public static void FlushLineManager2D()
{
lineManager2D.Render();
}
///
/// Flush line manager 3D. Renders all lines and allows more lines
/// to be rendered.
///
public static void FlushLineManager3D()
{
lineManager3D.Render();
}
///
/// Update
///
protected override void Update(GameTime gameTime)
{
base.Update(gameTime);
// Update all input states
Input.Update();
lastFrameTotalTimeMs = totalTimeMs;
elapsedTimeThisFrameInMs =
(float)gameTime.ElapsedGameTime.TotalMilliseconds;
totalTimeMs += elapsedTimeThisFrameInMs;
// Make sure elapsedTimeThisFrameInMs is never 0
if (elapsedTimeThisFrameInMs <= 0)
elapsedTimeThisFrameInMs = 0.001f;
// Increase frame counter for FramesPerSecond
frameCountThisSecond++;
totalFrameCount++;
// One second elapsed?
if (totalTimeMs - startTimeThisSecond > 1000.0f)
{
// Calc fps
fpsLastSecond = (int)(frameCountThisSecond * 1000.0f /
(totalTimeMs - startTimeThisSecond));
// Reset startSecondTick and repaintCountSecond
startTimeThisSecond = totalTimeMs;
frameCountThisSecond = 0;
fpsInterpolated =
MathHelper.Lerp(fpsInterpolated, fpsLastSecond, 0.1f);
// Check out if our framerate is running very low. Then we can improve
// rendering by reducing the number of objects we draw.
if (fpsInterpolated < 5)
Model.MaxViewDistance = 50;
else if (fpsInterpolated < 12)
Model.MaxViewDistance = 70;
else if (fpsInterpolated < 16)
Model.MaxViewDistance = 90;
else if (fpsInterpolated < 20)
Model.MaxViewDistance = 120;
else if (fpsInterpolated < 25)
Model.MaxViewDistance = 150;
else if (fpsInterpolated < 30 ||
HighDetail == false)
Model.MaxViewDistance = 175;
}
// Update sound and music
Sound.Update();
}
// Check if app is currently active
static bool isAppActive = true;
///
/// Is app active
///
/// Bool
public static bool IsAppActive
{
get
{
return isAppActive;
}
}
///
/// On activated
///
/// Sender
/// Arguments
protected override void OnActivated(object sender, EventArgs args)
{
base.OnActivated(sender, args);
isAppActive = true;
}
///
/// On deactivated
///
/// Sender
/// Arguments
protected override void OnDeactivated(object sender, EventArgs args)
{
base.OnDeactivated(sender, args);
isAppActive = false;
}
#if !DEBUG
int renderLoopErrorCount = 0;
#endif
///
/// Draw
///
/// Game time
protected override void Draw(GameTime gameTime)
{
try
{
// Clear anyway, makes unit tests easier and fixes problems if
// we don't have the z buffer cleared (some issues with line
// rendering might happen else). Performance drop is not significant!
ClearBackground();
// Get our sprites ready to draw...
Texture.additiveSprite.Begin(SpriteSortMode.Deferred, BlendState.Additive);
Texture.alphaSprite.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
// Handle custom user render code
Render();
// Render all models we remembered this frame.
meshRenderManager.Render();
// Render all 3d lines
lineManager3D.Render();
// Render UI and font texts, this also handles all collected
// screen sprites (on top of 3d game code)
UIRenderer.Render(lineManager2D);
PostUIRender();
//Handle drawing the Trophy
if (RacingGameManager.InGame && RacingGameManager.Player.Victory)
{
Texture.alphaSprite.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
int rank = GameScreens.Highscores.GetRankFromCurrentTime(
RacingGameManager.Player.LevelNum,
(int)RacingGameManager.Player.BestTimeMilliseconds);
// Show one of the trophies
BaseGame.UI.GetTrophyTexture(
// Select right one
rank == 0 ? UIRenderer.TrophyType.Gold :
rank == 1 ? UIRenderer.TrophyType.Silver :
UIRenderer.TrophyType.Bronze).
RenderOnScreen(new Rectangle(
BaseGame.Width / 2 - BaseGame.Width / 8,
BaseGame.Height / 2 - BaseGame.YToRes(10),
BaseGame.Width / 4, BaseGame.Height * 2 / 5));
Texture.alphaSprite.End();
}
ui.RenderTextsAndMouseCursor();
}
// Only catch exceptions here in release mode, when debugging
// we want to see the source of the error. In release mode
// we want to play and not be annoyed by some bugs ^^
#if !DEBUG
catch (Exception ex)
{
Log.Write("Render loop error: " + ex.ToString());
if (renderLoopErrorCount++ > 100)
throw;
}
#endif
finally
{
// Dummy block to prevent error in debug mode
}
base.Draw(gameTime);
// Apply device changes
if (mustApplyDeviceChanges)
{
graphicsManager.ApplyChanges();
mustApplyDeviceChanges = false;
}
}
///
/// Render delegate for rendering methods, also used for many other
/// methods.
///
public delegate void RenderHandler();
///
/// Render
///
protected virtual void Render()
{
}
///
/// Post user interface rendering, in case we need it.
/// Used for rendering the car selection 3d stuff after the UI.
///
protected virtual void PostUIRender()
{
// Overwrite this for your custom render code ..
}
///
/// Clear background
///
public static void ClearBackground()
{
//unsure if it clears depth correctly: Device.Clear(BackgroundColor);
Device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,
BackgroundColor, 1.0f, 0);
}
///
/// Remember scene render target. This is very important because
/// for our post screen shaders we have to render our whole scene
/// to this render target. But in the process we will use many other
/// shaders and they might set their own render targets and then
/// reset it, but we need to have this scene still to be set.
/// Don't reset to the back buffer (with SetRenderTarget(0, null), this
/// would stop rendering to our scene render target and the post screen
/// shader will not be able to process our screen.
/// The whole reason for this is that we can't use StrechRectangle
/// like in Rocket Commander because XNA does not provide that function
/// (the reason for that is cross platform compatibility with the XBox360).
/// Instead we could use ResolveBackBuffer, but that method is VERY SLOW.
/// Our framerate would drop from 600 fps down to 20, not good.
/// However, multisampling will not work, so we will disable it anyway!
///
static RenderTarget2D remSceneRenderTarget = null;
///
/// Remember the last render target we set, this way we can check
/// if the rendertarget was set before calling resolve!
///
static RenderTarget2D lastSetRenderTarget = null;
///
/// Remember render to texture instances to allow recreating them all
/// when DeviceReset is called.
///
static List remRenderToTextures =
new List();
///
/// Add render to texture instance to allow recreating them all
/// when DeviceReset is called with help of the remRenderToTextures list.
///
public static void AddRemRenderToTexture(RenderToTexture renderToTexture)
{
remRenderToTextures.Add(renderToTexture);
}
///
/// Current render target we have set, null if it is just the back buffer.
///
public static RenderTarget2D CurrentRenderTarget
{
get
{
return lastSetRenderTarget;
}
}
///
/// Set render target
///
/// Is scene render target
internal static void SetRenderTarget(RenderTarget2D renderTarget,
bool isSceneRenderTarget)
{
Device.SetRenderTarget(renderTarget);
if (isSceneRenderTarget)
remSceneRenderTarget = renderTarget;
lastSetRenderTarget = renderTarget;
}
///
/// Reset render target
///
/// Full reset to back buffer
internal static void ResetRenderTarget(bool fullResetToBackBuffer)
{
if (remSceneRenderTarget == null ||
fullResetToBackBuffer)
{
remSceneRenderTarget = null;
lastSetRenderTarget = null;
Device.SetRenderTarget(null);
}
else
{
Device.SetRenderTarget(remSceneRenderTarget);
lastSetRenderTarget = remSceneRenderTarget;
}
}
}
}