#region using...
using System;
using System.Collections.Generic;
using System.Linq;
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.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
////
// To draw INDEXED primitives, we'll need 4 basic objects:
// 1. A BasicEffect. Used to DRAW.
// 2. A VertexBuffer. Used to STORE ALL THE VERTICES of
// the object we wish to draw.
// 3. A VertexDeclaration. Explains to the GPU what PROPERTIES
// each vertex has. In this example, each vertex will
// simply have a POSITION in space, and a COLOR. Could
// also have had texture coordinates, but we're not using those here.
// 4. An INDEX BUFFER: Explains to the GPU what
// ORDER the vertices in the VertexBuffer should be visited.
// That is what the INDEX BUFFER is for.
// Imagine you are a kid again going trick or treating.
// This is your neighbourhood:
// ^ ^ ^ ^ ^
// |_| |_| |_| |_| |_|
// 100 102 104 106 108
//
//
//
// 101 103 105 107 109
// ^ ^ ^ ^ ^
// |_| |_| |_| |_| |_|
// Before leaving your house, you look at this map
// of houses and you identify the houses you know
// NEVER have candy. You mark the ones that
// DO with an X.
// ^ ^ ^ ^ ^
// |X| |_| |X| |_| |X|
// 100 102 104 106 108
//
//
//
// 101 103 105 107 109
// ^ ^ ^ ^ ^
// |_| |X| |_| |X| |_|
// OK, so you want to tell everyone in the
// neighbourhood what order your are visiting
// these houses.
// The thing is, each of these houses has
// real space coordinates right?
// Vertex space coordinates of the center
// of each home:
// House 100: ( 10, 40, 5 )
// House 101: ( 20, 40, 30 )
// House 102: ( 30, 40, 5 )
// House 103: ( 40, 40, 30 )
// House 104: ( 50, 40, 5 )
// .. and so on ..
// So you could EITHER tell everyone the
// physical location (coordinates) of each
// house you plan to visit:
// I am visiting:
// ( 10, 40, 5 ), ( 40, 40, 30 ) ...
// ^ ^ ^ ^ ^
// |X| |_| |X| |_| |X|
// 100 102 104 106 108
// \ / \ /
// \ / \ /
// \ / \ /
// 101 103 105 107 109
// ^ ^ ^ ^ ^
// |_| |X| |_| |X| |_|
//
// Or you could simply list the INDICES
// ("house numbers") of the homes you
// would like to hit.
// Which would be:
// 100, 103, 104, 107, 108
// The house numbers are kind of like
// indices ("indexes").
// Does that kind of make sense?
////////////////////////
// Lets talk more about this.
// Now say you're crazy and you're going to fire
// eggs at each of the houses who are participating
// in Halloween this year using your patented
// egg-launcher.
// So, you have two options to pass the data
// about the coordinates of the homes to hit
// for your egg launcher.
// You could:
// 1. Feed the egg launcher the coordinates of
// the homes to hit with eggs directly (looks like
// ( 20, 40, 30 )... etc)
// OR 2. Pass the egg launcher TWO arrays:
// The array of "positions of each home" (akin to
// a "vertex array"), and THEN, SEPARATELY, pass it
// an array of "homes to hit, by index", (INDEX BUFFER).
// The INDEX BUFFER basically lists the houses to HIT
// by index, and the machine would know
// to LOOK UP the physical coordinates of the
// homes to hit FROM the "positions of each home" array
// (the vertex buffer).
// VERTEX BUFFER:
// Positions of each home, stored in an array:
// +---------------+----------------+---------------+
// | ( 10, 40, 5 ) | ( 20, 40, 30 ) | ( 30, 40, 5 ) |
// +---------------+----------------+---------------+
// 100 101 102
// INDEX BUFFER
// Homes to hit (also stored in an array):
// +-----+-----+
// | 101 | 103 |
// +-----+-----+
//
// As you work through the INDEX BUFFER, firing
// eggs at each home listed there,
// to know the physical coordinates of a house
// at fire time, you simply look it up in the
// "VERTEX BUFFER",
// which is that "positions of each home" array.
// So, when drawing graphics, the VERTEX BUFFER
// is a list of all the vertices that make up the model.
// The INDEX BUFFER is the visitation order that the
// GPU should use to "weave" triangle.
// So you notice immediately that the INDEX BUFFER
// way has a really great advantage. Say you really
// hate Mr. Smithers and you want his home hit (home 104) with
// 2 eggs in a row. Instead of passing the machine
// data with repeated vertices like this:
// ( 20, 40, 30 ), ( 40, 40, 30 ), ( 50, 40, 5 ), ( 50, 40, 5 )
// Your index buffer could look like this:
//
// +-----+-----+-----+-----+
// | 101 | 103 | 104 | 104 |
// +-----+-----+-----+-----+
// So that's a bit more compact notation for
// when a certain vertex is visited more than once
// (which happens QUITE often when drawing a model!)
// Include the fact that each vertex in D3D gets passed with
// texture coordinates (2 floats) AND normals (3 floats),
// and its easy to see where the savings comes in.
///////////////////////////
// OK, last thing, then I'll get to the code, I promise.
// The other thing that this is like is PEGS ON A KNITTING LOOM.
// If you've seen one of those things, it has these pegs that
// you weave the yarn about over and over again. A loom might
// have 15-20 pegs or so
//
/*
3 4 5
2 * * * 6
1 * * 7
* *
1 * *
3 1 * * * 8
2 1 1 9
1 0
My sweet sweet knitting loom
with sweet numbered pegs
*/
// So to knit a sock or glove or something,
// you might visit pegs in the following order:
// 4, 10, 5, 11, 6, 12, 7, 13..
// In this example, the actual PHYSICAL PEGS are
// the VERTEX BUFFER (the pegs have
// actual physical space coordinates don't they?)
// The "winding order" or the order that I want you
// to spin the yarn about the pegs, is NOT specified
// by physical space coordinates (I'm not giving you the
// physical distance to travel the yarn from peg 4 to peg 10,
// I'm not saying "start at 4 cm horizontally from the top (4, 0, 0)
// and connect with (4, -4, 0)).
// Same goes for drawing using an index buffer.
// INSTEAD of directly specifying physical Vertex positions
// in space while drawing, we specify vertex positions
// ONCE and store them off in a VERTEX BUFFER.
// We THEN AFTER THAT create a SECOND array called
// the INDEX BUFFER which specifies the ORDER TO
// VISIT THE VERTICES THAT ARE ALREADY STORED IN
// (and numbered off!) in that VERTEX BUFFER.
// Each VERTEX actually has a position in space.
// So we store the vertices as this array:
// pointList ARRAY:
//
// +-----------------+-----------------+-----------------+
// | ( 1, 1, 1 ) | ( 2, 5, 9 ) | ( 8, 5, 7 ) |
// +-----------------+-----------------+-----------------+
// 0 1 2
// This forms the VERTEX BUFFER.
// (Each triplet in the array represents a vector)
// So, to DRAW A TRIANGLE, between the points I'd specify
// 0, 2, 1 in the INDEX BUFFER.
// INDEX BUFFER ibData:
//
// +-------+-------+-------+
// | 0 | 2 | 1 |
// +-------+-------+-------+
// 0 1 2
// When the GPU goes to draw that
// it puts the two together (the INDEX BUFFER values, which
// reference the VERTEX BUFFER values) and so it would draw
// a triangle between (1,1,1), (8,5,7), (2,5,9), winding it
// in that EXACT order.
//
////
// Vars for rotating the tetrahedron when you run the program.
float rot ;
Vector3 axis ;
BasicEffect renderer;
VertexBuffer vb;
VertexPositionColor[] pointList ;
VertexDeclaration vd;
IndexBuffer ib ;
short[] ibData ;
public Game1()
{
graphics = new GraphicsDeviceManager( this );
Content.RootDirectory = "Content";
this.IsMouseVisible = true ;
axis = new Vector3( random( 0, 1 ), random( 0, 1 ), random( 0, 1 ) );
axis.Normalize();
}
protected override void LoadContent()
{
/////
// Initialization.
// This is a really long set of steps we need to do
// to load vertex data INTO THE GPU.
// "preload" the GPU up with all the vertices
// you want it to draw.
// 1. Initialize the renderer as a BasicEffect:
renderer = new BasicEffect( GraphicsDevice, null );
renderer.VertexColorEnabled = true;
renderer.View = Matrix.CreateLookAt( -10 * Vector3.UnitZ, Vector3.Zero, Vector3.UnitY );
renderer.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 1.0f, 1.0f, 1000.0f );
//
//////
//////
// 2. Next, we will create and set up a bunch of vertices
// that we can draw.
// Define the vertices of a tetrahedron and put them in
// the vertex buffer.
float edgeLength = 1.0f ;
float a = edgeLength / (float)( 2.0 * Math.Sqrt( 2.0 ) );
Vector3 topFront0 = v( a, a, a );
Vector3 bottomFront1 = v( -a, -a, a );
Vector3 topBack2 = v( -a, a, -a );
Vector3 bottomBack3 = v( a, -a, -a );
pointList = new VertexPositionColor[]
{
vpc( topFront0, Color.Black ),
vpc( bottomFront1, Color.Red ),
vpc( topBack2, Color.GreenYellow ),
vpc( bottomBack3, Color.AliceBlue )
};
// Load verts onto GPU
vb = new VertexBuffer(
GraphicsDevice, VertexPositionColor.SizeInBytes * pointList.Length, BufferUsage.None
);
vb.SetData<VertexPositionColor>( pointList );
// 3. don't forget the vertex declaration
vd = new VertexDeclaration(
GraphicsDevice,
VertexPositionColor.VertexElements
);
// And so there we have the 4 vertices of a tetrahedron.
// But oh! A tet has 4 vertices, and 4 FACES! This means
// we need to draw _4_ triangles between these 4 vertices.
// Now normally, you might think, "well, ok, to draw these
// 4 triangles, we'll just issue 4 triangle-draw calls, and
// simply REPEAT/overlap a few of those vertex."
// Nah son. Repeating vertices? Nah.
// Instead, we'll use an INDEX BUFFER. Refer to the
// Halloween example above for a quick understanding
// of what we're doing here.. we're basically going to
// list out the VISITATION ORDER of the vertices that
// we already specified above. So we declare the
// vertices ONE TIME, in ONE PLACE, and then we
// say the order that these vertices should be
// visited at draw time.
// Sure beats having to specify the same vertex several
// times over, but it means that each vertex can only have
// ONE COLOR (so coloration will be less customizable).
// 4. Index buffer. First define the order in which the vertices
// (of the VERTEX BUFFER!) will be visited
// How did I work out this visitation order? Using Wikipedia and a pen and paper!
ibData = new short[]{
0, 3, 1,
0, 1, 2,
0, 2, 3,
2, 1, 3
};
// Now set them down into the GPU.
ib = new IndexBuffer( GraphicsDevice, typeof(short), ibData.Length, BufferUsage.None );
ib.SetData<short>( ibData );
//end setup
}
protected override void Update( GameTime gameTime )
{
// Esc. to exit.
if( Keyboard.GetState().IsKeyDown( Keys.Escape ) )
this.Exit();
rot += (float)gameTime.ElapsedGameTime.Ticks / TimeSpan.TicksPerSecond ;
renderer.World = Matrix.CreateFromAxisAngle( axis, rot ) ;
base.Update( gameTime );
}
protected override void Draw( GameTime gameTime )
{
GraphicsDevice.Clear( Color.DarkGray );
renderer.GraphicsDevice.VertexDeclaration = vd;
renderer.GraphicsDevice.Indices = ib ;
renderer.Begin(); // start rendering.
foreach( EffectPass pass in renderer.CurrentTechnique.Passes )
{
pass.Begin();
// Set the size of the points
renderer.GraphicsDevice.RenderState.PointSize = 8.0f;
// Tell the GPU what vertex array to pull vertex data from.
renderer.GraphicsDevice.Vertices[ 0 ].SetSource( vb, 0, VertexPositionColor.SizeInBytes );
// Actually draw.
renderer.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList, // draw Tris, every 3 vertices
// listed in the INDEX BUFFER are going to produce ONE TRIANGLE
// on the screen.
0, // ... start at the beginning of the list ...
0, // lowest vertex index from VB that can be drawn
pointList.Length, // draw as many vertices as are specified by the VERTEX BUFFER (_NOT_ the INDEX buffer).
// This is very important to get right, and it is a bit counter-intuitive.
// Say we're drawing a tetrahedron (as we are in this case). That shape
// has 4 TRIANGLES drawn between 4 POINTS.. a total of 4 triangles
// are drawn, so essentially that's 12 vertices that are visited.
// HOWEVER, the value that gets passed here is __4__, NOT 12.
// Because we visit 4 vertices, over and over again, to draw
// the 4 triangles.
0,
ibData.Length / 3 // how many total triangles are we drawing?
// we're hitting 12 vertices, to draw 4 triangles, so we
// are drawing.. uh, 4 triangles. DUH! :)
);
// end the rendering pass.
pass.End();
}
// Stop rendering.
renderer.End();
////
base.Draw( gameTime );
}
#region aux
/// <summary>Convenience</summary>
public static Vector3 v( double x, double y, double z )
{
return new Vector3( (float)x, (float)y, (float)z );
}
/// <summary>Convenience. After all, Shakespeare did say
/// that brevity is the soul of with. He didn't say anything
/// about efficiency, however.</summary>
public static VertexPositionColor vpc( Vector3 p, Color c )
{
return new VertexPositionColor( p, c );
}
public static Color c( double r, double g, double b )
{
return new Color( (float)r, (float)g, (float)b );
}
static Random rand = new Random();
public static float random( float low, float high )
{
return MathHelper.Lerp( low, high, (float)rand.NextDouble() ) ;
}
#endregion
static void Main( string[] args )
{
Game1 game = new Game1();
game.Run();
}
}