CS 489.02 - Computers and Games - Spring 2009
XNA 3D Graphics


Loyola College > Department of Computer Science > Dr. James Glenn > CS 489.02 > Examples and Lecture Notes > XNA 3D Graphics

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.