Demo.cs
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
namespace Graphics3DDemo
{
/// <summary>
/// This is the main type for your game
/// </summary>
public class Demo : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
Matrix view;
Matrix projection;
public Demo()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
base.Initialize();
// set up view from up high and directly south of the play field
// (thinking of decreasing y as north)
view = Matrix.CreateLookAt(new Vector3(0.5f, 3.0f, -2.0f),
new Vector3(0.5f, 0.0f, 0.0f),
new Vector3(0.0f, 0.0f, -1.0f));
float aspect = (float)Window.ClientBounds.Width / (float)Window.ClientBounds.Height;
// set up perspective projection with 45 degree field of view and enough depth
// to get everything in our scene
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspect, 0.1f, 10.0f);
}
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
// TODO: Add your update logic here
base.Update(gameTime);
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
// graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
// define the triangle fan for the pyramid for use with our render method
Vector3[] pyramidSideFan = new Vector3[]
{
new Vector3(0.5f, 0.5f, -0.3f),
new Vector3(0.4f, 0.4f, -0.1f), // clockwise!
new Vector3(0.6f, 0.4f, -0.1f),
new Vector3(0.6f, 0.6f, -0.1f),
new Vector3(0.4f, 0.6f, -0.1f),
new Vector3(0.4f, 0.4f, -0.1f)
};
// define the fan for the play field (2 triangles to make a rectangle)
Vector3[] fieldFan = new Vector3[]
{
new Vector3(0.0f, 0.0f, 0.0f),
new Vector3(1.0f, 0.0f, 0.0f),
new Vector3(1.0f, 1.0f, 0.0f),
new Vector3(0.0f, 1.0f, 0.0f),
new Vector3(0.0f, 0.0f, 0.0f)
};
// use our VertexPositionNormalColor type (just so we can have solid
// colors and not have to load textures)
GraphicsDevice.VertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionNormalColor.VertexElements);
// set up effect with no special lighting
BasicEffect effect1 = new BasicEffect(GraphicsDevice, null);
effect1.World = Matrix.Identity;
effect1.View = view;
effect1.Projection = projection;
effect1.VertexColorEnabled = true;
// set up default lighting (one directional light)
BasicEffect effect2 = new BasicEffect(GraphicsDevice, null);
effect2.World = Matrix.Identity;
effect2.View = view;
effect2.Projection = projection;
effect2.VertexColorEnabled = true;
effect2.LightingEnabled = true;
effect2.EnableDefaultLighting();
// set up our own directional light
BasicEffect effect3 = new BasicEffect(GraphicsDevice, null);
effect3.World = Matrix.Identity;
effect3.View = view;
effect3.Projection = projection;
effect3.VertexColorEnabled = true;
effect3.LightingEnabled = true;
effect3.DirectionalLight0.Enabled = true;
Vector3 lightDir = new Vector3(1.0f, -1.0f, 1.0f);
lightDir.Normalize();
effect3.DirectionalLight0.Direction = lightDir;
effect3.DirectionalLight0.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);
effect3.DirectionalLight0.SpecularColor = new Vector3(1.0f, 1.0f, 1.0f);
// cycle through effects
BasicEffect[] allEffects = new BasicEffect[] { effect1, effect2, effect3};
int frameTime = 2; // 2 seconds per effect
BasicEffect effect = allEffects[(gameTime.TotalRealTime.Seconds % (allEffects.Length * frameTime)) / frameTime];
// draw stuff
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Begin();
renderTriangleFan(pyramidSideFan, Color.Red);
renderTriangleFan(fieldFan, Color.Green);
pass.End();
}
effect.End();
base.Draw(gameTime);
}
/// <summary>
/// A vertex type that specifies a position, surface normal, and color.
/// </summary>
public struct VertexPositionNormalColor
{
public Vector3 Position;
public Vector3 Normal;
public Color Color;
public static int SizeInBytes = 7 * sizeof(float); // 3 for Vector3, 1 for Color
public static VertexElement[] VertexElements = new VertexElement[]
{
new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
new VertexElement(0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
new VertexElement(0, sizeof(float) * 6, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0)
};
}
/// <summary>
/// Draws the given triangle fan on the current graphics device.
/// This should be invoked within a technique's pass.
/// </summary>
/// <param name="fan">A list of points on the fan, with the fan point in element 0</param>
/// <param name="c">The color of the triangles in the fan</param>
void renderTriangleFan(Vector3[] fan, Color c)
{
// we convert the fan to a list so we can give each face its
// proper normal
int numTriangles = fan.Length - 2;
VertexPositionNormalColor[] verts = new VertexPositionNormalColor[numTriangles * 3];
// go over all triangles in the fan
for (int t = 0; t < numTriangles; t++)
{
// do positions of vertices of triangle number t
verts[t * 3].Position = fan[0];
verts[t * 3 + 1].Position = fan[t + 1];
verts[t * 3 + 2].Position = fan[t + 2];
// and set colors
for (int i = 0; i < 3; i++)
{
verts[t * 3 + i].Color = c;
}
// compute normal and assign to vertices
Vector3 side1 = fan[t + 1] - fan[0];
Vector3 side2 = fan[t + 2] - fan[0];
Vector3 normal = Vector3.Cross(side2, side1);
normal.Normalize();
for (int i = 0; i < 3; i++)
{
verts[t * 3 + i].Normal = normal;
}
}
// draw the triangle list
GraphicsDevice.DrawUserPrimitives<VertexPositionNormalColor>(PrimitiveType.TriangleList, verts, 0, numTriangles);
}
/// <summary>
/// Draws a triangle on the current graphics device. This should be invoked
/// during a pass through a technique. The points should be given in clockwise order.
/// </summary>
/// <param name="p1">a point on a triangle</param>
/// <param name="p2">the second point on the triangle</param>
/// <param name="p3">the last point on the triangle</param>
/// <param name="c">the color to draw the triangle in</param>
void renderTriangle(Vector3 p1, Vector3 p2, Vector3 p3, Color c)
{
VertexPositionNormalColor[] verts = new VertexPositionNormalColor[3];
// set the coordinates
verts[0].Position = p1;
verts[1].Position = p2;
verts[2].Position = p3;
// set the colors (all the same -- we want a uniform surface)
for (int i = 0; i < verts.Length; i++)
{
verts[i].Color = c;
}
// find the normal and copy it to all vertices
Vector3 p12 = p2 - p1;
Vector3 p13 = p3 - p1;
Vector3 normal = Vector3.Cross(p13, p12);
normal.Normalize();
for (int i = 0; i < verts.Length; i++)
{
verts[i].Normal = normal;
}
// draw!
GraphicsDevice.DrawUserPrimitives<VertexPositionNormalColor>(PrimitiveType.TriangleList, verts, 0, 1);
}
}
}
This code can also be downloaded from the file
Demo.cs.