// Project: XnaShooter, File: Model.cs
// Namespace: XnaShooter.Graphics, Class: Model
// Path: C:\code\XnaBook\XnaShooter\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 XnaShooter.Game;
using XnaShooter.Helpers;
using XnaShooter.Shaders;
#endregion
namespace XnaShooter.Graphics
{
///
/// Model class for loading and displaying x files including all materials
/// and textures. Provides load and render functions to render static
/// non animated models (mostly 1 mesh), for animated models we need a more
/// advanced class, which is not required for this game yet.
///
public class Model : IGraphicContent
{
#region Variables
///
/// Name of this model, also used to load it from the content system.
///
string name = "";
///
/// All x files
///
public const string Extension = "x";
///
/// Underlying xna model object. Loaded with the content system.
///
XnaModel xnaModel = null;
///
/// Default object matrix to fix models from 3ds max to our engine!
///
Matrix objectMatrix =
// left handed models (else everything is mirrored with x files)
Matrix.CreateRotationX(MathHelper.Pi / 2.0f);
///
/// Scaling for this object, used for distance comparisons.
///
float downScaling = 1.0f;
///
/// Since we only support one mesh and only one mesh part in there,
/// store it into this variable!
///
ModelMeshPart meshPart = null;
///
/// Store the only renderable mesh helper we need here.
/// Rendering happens in the MeshRenderManager class.
///
MeshRenderManager.RenderableMesh renderableMesh = null;
#endregion
#region Properties
///
/// Name for this model, this is the content name.
///
/// String
public string Name
{
get
{
return name;
} // get
} // Name
///
/// Object downscaled size to be standard size of 1.0
///
/// Float
public float ObjectDownscaledSize
{
get
{
return downScaling;
} // get
} // ObjectDownscaledSize
///
/// Xna model
///
/// Xna model
public XnaModel XnaModel
{
get
{
return xnaModel;
} // get
} // XnaModel
///
/// Number of mesh parts
///
/// Int
public int NumOfMeshParts
{
get
{
int ret = 0;
//obs: foreach (ModelMesh mesh in xnaModel.Meshes)
for (int meshNum = 0; meshNum < xnaModel.Meshes.Count; meshNum++)
ret += xnaModel.Meshes[meshNum].MeshParts.Count;
return ret;
} // get
} // NumOfMeshParts
#endregion
#region Constructor
///
/// Create model
///
/// Set model name
public Model(string setModelName)
{
name = setModelName;
Load();
BaseGame.RegisterGraphicContentObject(this);
// Get matrix transformations of the model
// Has to be done only once because we don't use animations in this game.
Matrix[] transforms = new Matrix[xnaModel.Bones.Count];
xnaModel.CopyAbsoluteBoneTransformsTo(transforms);
// Calculate scaling for this object, used for distance comparisons.
if (xnaModel.Meshes.Count > 0)
downScaling =// scaling =
xnaModel.Meshes[0].BoundingSphere.Radius *
transforms[0].Right.Length();
if (downScaling > 0)
downScaling = 1.0f / downScaling;
objectMatrix =
// Use transformation of our first mesh (we support only one)
transforms[xnaModel.Meshes[0].ParentBone.Index] *
Matrix.CreateRotationX(MathHelper.Pi / 2.0f) *
Matrix.CreateScale(downScaling);
} // Model(setModelName)
#endregion
#region Load
public void Load()
{
if (xnaModel == null)
{
xnaModel = BaseGame.Content.Load(
@"Content\" + name);
// Go through all meshes in the model
//obs: foreach (ModelMesh mesh in xnaModel.Meshes)
//obs: for (int meshNum = 0; meshNum < xnaModel.Meshes.Count; meshNum++)
//{
ModelMesh mesh = xnaModel.Meshes[0];
meshPart = mesh.MeshParts[0];
string meshName = mesh.Name;
// Only support one effect
Effect effect = mesh.Effects[0];
// And for each effect this mesh uses (usually just 1, multimaterials
// are nice in 3ds max, but not efficiently for rendering stuff).
//obs: foreach (Effect effect in mesh.Effects)
//obs: for (int effectNum = 0; effectNum < mesh.Effects.Count; effectNum++)
//{
//Effect effect = mesh.Effects[effectNum];
// Get technique from meshName
int techniqueIndex = -1;
if (meshName.Length > 0)
{
try
{
//Log.Write("meshName="+meshName);
string techniqueNumberString = meshName.Substring(
meshName.Length - 1, 1);
#if !XBOX360
// Faster and does not throw an exception!
int.TryParse(techniqueNumberString, out techniqueIndex);
#else
techniqueIndex = Convert.ToInt32(techniqueNumberString);
#endif
//Log.Write("techniqueIndex="+techniqueIndex);
} // try
catch { } // ignore if that failed
} // if (meshName.Length)
// No technique found or invalid?
if (techniqueIndex < 0 ||
techniqueIndex >= effect.Techniques.Count ||
// Or if this is an asteroid (use faster diffuse technique!)
StringHelper.BeginsWith(name, "asteroid"))
{
// Try to use last technique
techniqueIndex = effect.Techniques.Count - 1;
} // if (techniqueIndex)
// If the technique ends with 20, but we can't do ps20,
// use the technique before that (which doesn't use ps20)
if (BaseGame.CanUsePS20 == false &&
effect.Techniques[techniqueIndex].Name.EndsWith("20"))
techniqueIndex--;
// Set current technique for rendering below
effect.CurrentTechnique = effect.Techniques[techniqueIndex];
//} // foreach (effect)
// Add all mesh parts!
//obs: for (int partNum = 0; partNum < mesh.MeshParts.Count; partNum++)
//{
//obs: ModelMeshPart part = mesh.MeshParts[partNum];
// The model mesh part is not really used, we just extract the
// index and vertex buffers and all the render data.
// Material settings are build from the effect settings.
// Also add this to our own dictionary for rendering.
renderableMesh = BaseGame.MeshRenderManager.Add(
mesh.VertexBuffer, mesh.IndexBuffer, meshPart, effect);
//} // for (partNum)
//} // foreach (mesh)
} // if
} // Load()
#endregion
#region Dispose
///
/// Dispose
///
public void Dispose()
{
xnaModel = null;
} // Dispose()
#endregion
#region Render
///
/// Render
///
/// Render matrix
public void Render(Matrix renderMatrix, float alpha)
{
// Make sure alpha is valid
if (alpha <= 0)
return; // skip, no reason to render this!
if (alpha > 1)
alpha = 1;
/*obs
float distanceSquared = Vector3.DistanceSquared(
BaseGame.CameraPos, renderMatrix.Translation);
// Check out if object is behind us or not visible, then we can skip
// rendering. This is the GREATEST performance gain in the whole game!
// Object must be away at least 20 units!
if (distanceSquared > 20 * 20)
{
Vector3 objectDirection =
Vector3.Normalize(BaseGame.CameraPos - renderMatrix.Translation);
// Half field of view should be fov / 2, but because of
// the aspect ratio (1.33) and an additional offset we need
// to include to see objects at the borders.
float objAngle = Vector3Helper.GetAngleBetweenVectors(
BaseGame.CameraRotation, objectDirection);
if (objAngle > BaseGame.FieldOfView)//.ViewableFieldOfView)
// Skip.
return;
} // if (distanceSquared)
*/
// Just render the only renderable mesh we got. The mesh part adds
// the render matrix to be picked up in the mesh rendering later.
renderableMesh.thisFrameRenderMatricesAndAlpha.Add(
objectMatrix * renderMatrix);
} // Render(renderMatrix, alpha)
///
/// Render
///
/// Render matrix
public void Render(Matrix renderMatrix)
{
Render(renderMatrix, 1.0f);
} // Render(renderMatrix)
///
/// Render
///
/// Render position
public void Render(Vector3 renderPos)
{
Render(Matrix.CreateTranslation(renderPos));
} // Render(renderPos)
#endregion
#region Generate shadow
///
/// Generate shadow for this model in the generate shadow pass
/// of our shadow mapping shader. All objects rendered here will
/// cast shadows to our scene (if they are in range of the light)
///
/// Render matrix
public void GenerateShadow(Matrix renderMatrix)
{
/*obs
// Find out how far the object is away from the shadow,
// we can ignore it if it is outside of the shadow generation range.
// Everything smaller than 0.5 meter can be ignored.
float maxDistance =
//nice, but not good for shadow mapping, have to use fixed value!
1.15f * ShaderEffect.shadowMapping.ShadowDistance;
if (Vector3.DistanceSquared(
ShaderEffect.shadowMapping.ShadowLightPos, renderMatrix.Translation) >
maxDistance * maxDistance)
// Don't render, too far away!
return;
*/
// Multiply object matrix by render matrix.
//renderMatrix = objectMatrix * renderMatrix;
//int wheelNumber = 0;
//obs: foreach (ModelMesh mesh in xnaModel.Meshes)
//for (int meshNum = 0; meshNum < xnaModel.Meshes.Count; meshNum++)
{
ModelMesh mesh = xnaModel.Meshes[0];
//ModelMesh mesh = xnaModel.Meshes[meshNum];
// Use the ShadowMapShader helper method to set the world matrices
ShaderEffect.shadowMapping.UpdateGenerateShadowWorldMatrix(
//transforms[mesh.ParentBone.Index] *
//renderMatrix);
objectMatrix * renderMatrix);
//obs: foreach (ModelMeshPart part in mesh.MeshParts)
//for (int partNum = 0; partNum < mesh.MeshParts.Count; partNum++)
//{
// ModelMeshPart part = mesh.MeshParts[partNum];
// Render just the vertices, do not use the shaders of our model.
// This is the same code as ModelMeshPart.Draw() uses, but
// this method is internal and can't be used by us :(
BaseGame.Device.VertexDeclaration = meshPart.VertexDeclaration;
BaseGame.Device.Vertices[0].SetSource(
mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
BaseGame.Device.Indices = mesh.IndexBuffer;
BaseGame.Device.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
meshPart.BaseVertex, 0,
meshPart.NumVertices, meshPart.StartIndex,
meshPart.PrimitiveCount);
//} // foreach (part)
} // foreach (mesh)
} // GenerateShadow(renderMatrix)
#endregion
#region Use shadow
///
/// Use shadow for our scene. We render all objects that should receive
/// shadows here. Called from the ShadowMappingShader.UseShadow method.
///
/// Render matrix
public void UseShadow(Matrix renderMatrix)
{
// If this is an object that uses alpha textures, never receive
// shadows, this only causes troubes and needs a much more complex
// shader. This affects usually only palms anyway, which look better
// without shadowing on.
//if (hasAlpha)
// return;
/*obs
// Find out how far the object is away from the shadow,
// we can ignore it if it is outside of the shadow generation range.
// Everything smaller than 0.25 meter can be ignored.
// Note: For receiving we usually use more objects than for generating
// shadows.
float maxDistance =
//nice, but not good for shadow mapping, have to use fixed value!
1.15f * ShaderEffect.shadowMapping.ShadowDistance;
if (Vector3.DistanceSquared(
ShaderEffect.shadowMapping.ShadowLightPos, renderMatrix.Translation) >
maxDistance * maxDistance)
// Don't render, too far away!
return;
*/
// Multiply object matrix by render matrix.
//renderMatrix = objectMatrix * renderMatrix;
//obs: foreach (ModelMesh mesh in xnaModel.Meshes)
//for (int meshNum = 0; meshNum < xnaModel.Meshes.Count; meshNum++)
{
ModelMesh mesh = xnaModel.Meshes[0];
//ModelMesh mesh = xnaModel.Meshes[meshNum];
// Use the ShadowMapShader helper method to set the world matrices
ShaderEffect.shadowMapping.UpdateCalcShadowWorldMatrix(
//transforms[mesh.ParentBone.Index] *
//renderMatrix);
objectMatrix * renderMatrix);
//obs: foreach (ModelMeshPart part in mesh.MeshParts)
//for (int partNum = 0; partNum < mesh.MeshParts.Count; partNum++)
//{
//ModelMeshPart part = mesh.MeshParts[partNum];
// Render just the vertices, do not use the shaders of our model.
// This is the same code as ModelMeshPart.Draw() uses, but
// this method is internal and can't be used by us :(
BaseGame.Device.VertexDeclaration = meshPart.VertexDeclaration;
BaseGame.Device.Vertices[0].SetSource(
mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
BaseGame.Device.Indices = mesh.IndexBuffer;
BaseGame.Device.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
meshPart.BaseVertex, 0,
meshPart.NumVertices, meshPart.StartIndex,
meshPart.PrimitiveCount);
//} // foreach (part)
} // foreach (mesh)
} // UseShadow(renderMatrix)
#endregion
#region Unit Testing
#if DEBUG
#region TestSingleModel
///
/// Test single model
///
static public void TestModels()
{
List testModels = new List();
int modelNum = 0;
TestGame.Start("TestModels",
delegate
{
testModels.Add(new Model("OwnShip"));
testModels.Add(new Model("Corvette"));
testModels.Add(new Model("Firebird"));
testModels.Add(new Model("RocketFrigate"));
testModels.Add(new Model("Rocket"));
testModels.Add(new Model("SmallTransporter"));
testModels.Add(new Model("Asteroid"));
testModels.Add(new Model("ItemMg"));
testModels.Add(new Model("ItemGattling"));
testModels.Add(new Model("ItemPlasma"));
testModels.Add(new Model("ItemRockets"));
testModels.Add(new Model("ItemEmp"));
testModels.Add(new Model("Building"));
testModels.Add(new Model("Building2"));
testModels.Add(new Model("Building3"));
testModels.Add(new Model("Building4"));
testModels.Add(new Model("Kaktus"));
testModels.Add(new Model("Kaktus2"));
testModels.Add(new Model("KaktusBenny"));
testModels.Add(new Model("KaktusSeg"));
testModels.Add(new Model("BackgroundGround"));
//testModels.Add(new Model("BackgroundWall"));
},
delegate
{
/*tst
for (int num = 0; num < 200; num++)
{
BaseGame.DrawLine(
new Vector3(-12.0f + num / 4.0f, 13.0f, 0),
new Vector3(-17.0f + num / 4.0f, -13.0f, 0),
new Color((byte)(255 - num), 14, (byte)num));
} // for
*/
TextureFont.WriteText(2, 30,
"cam pos=" + BaseGame.CameraPos);
TextureFont.WriteText(2, 60,
"Press space to toggle model");
if (Input.KeyboardSpaceJustPressed)
modelNum = (modelNum+1)%testModels.Count;
testModels[modelNum].Render(Matrix.CreateScale(5));
// And flush render manager to draw all objects
BaseGame.MeshRenderManager.Render();
});
} // TestSingleModel()
#endregion
#region TestRenderModel
public static void TestRenderModel()
{
Model testModel = null;
TestGame.Start("TestRenderModel",
delegate
{
testModel = new Model("Apple");
},
delegate
{
testModel.Render(Matrix.CreateScale(10));
});
} // TestRenderModel()
#endregion
#endif
#endregion
} // class Model
} // namespace XnaShooter.Graphics