// Project: XnaGraphicEngine, File: Model.cs
// Namespace: XnaGraphicEngine.Graphics, Class: Model
// Path: C:\code\XnaBook\XnaGraphicEngine\Graphics, Author: Abi
// Code lines: 36, Size of file: 900 Bytes
// Creation date: 27.11.2006 03:27
// Last modified: 27.11.2006 03:50
// Generated with Commenter by abi.exDream.com

#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
using XnaModel = Microsoft.Xna.Framework.Graphics.Model;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using XnaGraphicEngine.Game;
using XnaGraphicEngine.Helpers;
using Microsoft.Xna.Framework.Input;
#endregion

namespace XnaGraphicEngine.Graphics
{
	/// <summary>
	/// Model
	/// </summary>
	public class Model : IGraphicContent
	{
		#region Variables
		/// <summary>
		/// Name of this model, also used to load it from the content system.
		/// </summary>
		string name = "";

		/// <summary>
		/// Underlying xna model object. Loaded with the content system.
		/// </summary>
		XnaModel xnaModel = null;

		/// <summary>
		/// Transform matrices for each mesh part, we only have to get them once
		/// if the model is not animated.
		/// </summary>
		Matrix[] transforms = null;

		/// <summary>
		/// Scaling of model. Used to create objecMatrix.
		/// </summary>
		float scaling = 1.0f;

		/// <summary>
		/// Default object matrix to fix models from 3ds max to our engine!
		/// </summary>
		Matrix objectMatrix =
			Matrix.CreateRotationX(MathHelper.Pi / 2.0f);

		/// <summary>
		/// Normalize cube map, initialized when needed!
		/// </summary>
		TextureCube normalizeCubeMap = null;
		#endregion

		#region Properties
		/// <summary>
		/// Name for this model, this is the content name.
		/// </summary>
		/// <returns>String</returns>
		public string Name
		{
			get
			{
				return name;
			} // get
		} // Name
		#endregion

		#region Constructor
		/// <summary>
		/// Create model
		/// </summary>
		/// <param name="device">Device</param>
		/// <param name="filename">Filename</param>
		public Model(string setModelName)
		{
			name = setModelName;

			Load();

			BaseGame.RegisterGraphicContentObject(this);

			// Get matrices for each mesh part
			transforms = new Matrix[xnaModel.Bones.Count];
			xnaModel.CopyAbsoluteBoneTransformsTo(transforms);

			// Calculate scaling for this object, used for rendering.
			scaling = xnaModel.Meshes[0].BoundingSphere.Radius *
				transforms[0].Right.Length();
			if (scaling == 0)
				scaling = 0.0001f;

			// Apply scaling to objectMatrix to rescale object to size of 1.0
			objectMatrix *= Matrix.CreateScale(1.0f / scaling);
		} // Model(device, filename)
		#endregion

		#region Load
		public void Load()
		{
			if (xnaModel == null)
				xnaModel = BaseGame.Content.Load<XnaModel>(
					@"Content\" + name);
		} // Load()
		#endregion

		#region Dispose
		public void Dispose()
		{
			xnaModel = null;
		} // Dispose()
		#endregion

		#region Render
		bool useShaderModel11 = false;
		/// <summary>
		/// Render
		/// </summary>
		/// <param name="renderMatrix">Render matrix</param>
		public void Render(Matrix renderMatrix)
		{
			if (xnaModel == null ||
				xnaModel.Meshes.Count == 0 ||
				xnaModel.Meshes[0].MeshParts.Count == 0)
				return;

			if (Input.KeyboardKeyJustPressed(Keys.LeftControl))
				useShaderModel11 = !useShaderModel11;

			// Apply objectMatrix
			renderMatrix = objectMatrix * renderMatrix;

			// Go through all meshes in the model
			foreach (ModelMesh mesh in xnaModel.Meshes)
			{
				// Assign world matrix for each used effect
				BaseGame.WorldMatrix =
					transforms[mesh.ParentBone.Index] *
					renderMatrix;

				// And for each effect this mesh uses (usually just 1, multimaterials
				// are nice in 3ds max, but not efficiently for rendering stuff).
				foreach (Effect effect in mesh.Effects)
				{
					// Set technique (not done automatically by XNA framework).
					if (useShaderModel11)
					{
						effect.CurrentTechnique = effect.Techniques["Specular"];
						if (normalizeCubeMap == null)
							normalizeCubeMap = BaseGame.Content.Load<TextureCube>(
								@"Content\NormalizeCubeMap");
						effect.Parameters["NormalizeCubeTexture"].SetValue(
							normalizeCubeMap);
					} // if
					else
					{
						effect.CurrentTechnique = effect.Techniques["Specular20"];
					} // else

					// Set matrices, we use world, viewProj and viewInverse in
					// the ParallaxMapping.fx shader
					effect.Parameters["world"].SetValue(
						BaseGame.WorldMatrix);
					// Note: these values should only be set once every frame!
					// to improve performance again, also we should access them
					// with EffectParameter and not via name (which is slower)!
					// For more information see Chapter 6 and 7.
					effect.Parameters["viewProj"].SetValue(
						BaseGame.ViewProjectionMatrix);
					effect.Parameters["viewInverse"].SetValue(
						BaseGame.InverseViewMatrix);

					// Also set light direction (not really used here, but later)
					effect.Parameters["lightDir"].SetValue(
						BaseGame.LightDirection);
				} // foreach (effect)

				// Render with help of the XNA ModelMesh Draw method, which just goes
				// through all mesh parts and renders them (with vertex and index
				// buffers).
				mesh.Draw();
			} // foreach (mesh)
		} // Render(renderMatrix)

		/// <summary>
		/// Render
		/// </summary>
		/// <param name="renderPos">Render position</param>
		public void Render(Vector3 renderPos)
		{
			Render(Matrix.CreateTranslation(renderPos));
		} // Render(renderPos)
		#endregion

		#region Unit Testing
#if DEBUG
		public static void TestRenderModel()
		{
			Model testModel = null;
			TestGame.Start("TestRenderModel",
				delegate
				{
					testModel = new Model("Apple");
					TestGame.game.SetCamera(new RotationCamera(TestGame.game));
				},
				delegate
				{
					TextureFont.WriteText(20, 40,
						"Press Space to disable rotating camera");
					TextureFont.WriteText(20, 80,
						"Press Ctrl to switch to Shader Model 1.1");

					if (Input.KeyboardSpaceJustPressed)
						TestGame.game.SetCamera(new SimpleCamera(TestGame.game));

					testModel.Render(Matrix.CreateScale(10));
				});
		} // TestRenderModel()
#endif
		#endregion
	} // class Model
} // namespace XnaGraphicEngine.Graphics