Constructing, Drawing and Texturing a Cube with Vertices in XNA on Windows Phone 7

This may be a precursor to something bigger…

I’m working on what I think will be a pretty cool game for the marketplace, and I’ll share the source code here eventually. Right now I had to conceptually understand something: how to draw a cube using only vertices.

You see, in my XNA experience I’ve never dealt directly with vertices; I’ve always just drawn models or sprites on the screen. In this case, now, I need to be able to generate a cube and later have the option to change just once face at a time. That will come later!

Right now I’ve got some working code which constructs and textures a cube and then draws it. This is in XNA 4.0 for Windows Phone 7. The starting point I used was this excellent XNA 3.1 article. The code I use to set up the vertices in ManualCube.cs is more or less a direct ripoff; there wasn’t much to change. Where I made changes was in the rendering, and also one area that was causing a memory leak.

Download my solution here:

image

So, let’s be honest. It’s not like this is the most amazing thing in the gaming world. The point is, the method I’m using adds a lot more control over what you can do with the resulting object.

In what way is this better than making a cube in Blender/Mod Tool/whatever, skinning it, exporting it to FBX and going on with your life?

Well, in theory, you can change the appearance of the cube’s faces individually, or even animate them. You have total control over the appearance of your shape, the effects you choose to use, and more. When you have a lot of dynamic-ness especially related to the textures or colors on the shape, this is a good way to go.

The ManualCube Class

This is kinda the meat and potatoes of this method. Basically in this class we are manually setting up a ton of values that compute vertex locations (with normals and UV texture coordinates) for a cube. A cube has six faces, which is made of two triangles, each of which have 3 vertices. So we have 6 vertices per face and 6 faces = 36 total vertices to enter data for.

The texture coordinates are important because they indicate how the texture will be displayed on the face: rotated, scaled, translated, etc.

This class is different from Cube.cs in the original article in that the _shapeBuffer variable is no longer used. In the Render method, I stuck it in a using statement (see line 8 below) so that it is properly disposed of. I ran out of memory pretty quickly with the original code.

    1: public void RenderToDevice(GraphicsDevice device)
    2: {
    3:     // Build the cube, setting up the _vertices array
    4:     if (_isConstructed == false)
    5:         ConstructCube();
    6:     
    7:     // Create the shape buffer and dispose of it to prevent out of memory
    8:     using (VertexBuffer buffer = new VertexBuffer(
    9:         device,
   10:         VertexPositionNormalTexture.VertexDeclaration,
   11:         NUM_VERTICES,
   12:         BufferUsage.WriteOnly))
   13:     {
   14:         // Load the buffer
   15:         buffer.SetData(_vertices);
   16:  
   17:         // Send the vertex buffer to the device
   18:         device.SetVertexBuffer(buffer);
   19:     }
   20:  
   21:     // Draw the primitives from the vertex buffer to the device as triangles
   22:     device.DrawPrimitives(PrimitiveType.TriangleList, 0, NUM_TRIANGLES);     
   23: }   

RenderToDevice fills a vertex buffer and makes the graphics device use it for what it will draw next. RenderToDevice needs to be called from the main game’s draw method in order for anything to happen, and of course, you need to properly set up the effect that will be used to draw it.

Also, in the code that sets up the vertices in ConstructCube, at the end, I set a local boolean called _isConstructed to true. That way, we are not constructing the cube from scratch every single frame.

 private void ConstructCube()
 {
     _vertices = new VertexPositionNormalTexture[NUM_VERTICES];
  
     // Calculate the position of the vertices on the top face.
     Vector3 topLeftFront = Position + new Vector3(-1.0f, 1.0f, -1.0f) * Size;
     Vector3 topLeftBack = Position + new Vector3(-1.0f, 1.0f, 1.0f) * Size;
     Vector3 topRightFront = Position + new Vector3(1.0f, 1.0f, -1.0f) * Size;
     Vector3 topRightBack = Position + new Vector3(1.0f, 1.0f, 1.0f) * Size;
  
     // Calculate the position of the vertices on the bottom face.
     Vector3 btmLeftFront = Position + new Vector3(-1.0f, -1.0f, -1.0f) * Size;
     Vector3 btmLeftBack = Position + new Vector3(-1.0f, -1.0f, 1.0f) * Size;
     Vector3 btmRightFront = Position + new Vector3(1.0f, -1.0f, -1.0f) * Size;
     Vector3 btmRightBack = Position + new Vector3(1.0f, -1.0f, 1.0f) * Size;
  
     // Normal vectors for each face (needed for lighting / display)
     Vector3 normalFront = new Vector3(0.0f, 0.0f, 1.0f) * Size;
     Vector3 normalBack = new Vector3(0.0f, 0.0f, -1.0f) * Size;
     Vector3 normalTop = new Vector3(0.0f, 1.0f, 0.0f) * Size;
     Vector3 normalBottom = new Vector3(0.0f, -1.0f, 0.0f) * Size;
     Vector3 normalLeft = new Vector3(-1.0f, 0.0f, 0.0f) * Size;
     Vector3 normalRight = new Vector3(1.0f, 0.0f, 0.0f) * Size;
  
     // UV texture coordinates
     Vector2 textureTopLeft = new Vector2(1.0f * Size.X, 0.0f * Size.Y);
     Vector2 textureTopRight = new Vector2(0.0f * Size.X, 0.0f * Size.Y);
     Vector2 textureBottomLeft = new Vector2(1.0f * Size.X, 1.0f * Size.Y);
     Vector2 textureBottomRight = new Vector2(0.0f * Size.X, 1.0f * Size.Y);
  
     // Add the vertices for the FRONT face.
     _vertices[0] = new VertexPositionNormalTexture(topLeftFront, normalFront, textureTopLeft);
     _vertices[1] = new VertexPositionNormalTexture(btmLeftFront, normalFront, textureBottomLeft);
     _vertices[2] = new VertexPositionNormalTexture(topRightFront, normalFront, textureTopRight);
     _vertices[3] = new VertexPositionNormalTexture(btmLeftFront, normalFront, textureBottomLeft);
     _vertices[4] = new VertexPositionNormalTexture(btmRightFront, normalFront, textureBottomRight);
     _vertices[5] = new VertexPositionNormalTexture(topRightFront, normalFront, textureTopRight);
  
     // Add the vertices for the BACK face.
     _vertices[6] = new VertexPositionNormalTexture(topLeftBack, normalBack, textureTopRight);
     _vertices[7] = new VertexPositionNormalTexture(topRightBack, normalBack, textureTopLeft);
     _vertices[8] = new VertexPositionNormalTexture(btmLeftBack, normalBack, textureBottomRight);
     _vertices[9] = new VertexPositionNormalTexture(btmLeftBack, normalBack, textureBottomRight);
     _vertices[10] = new VertexPositionNormalTexture(topRightBack, normalBack, textureTopLeft);
     _vertices[11] = new VertexPositionNormalTexture(btmRightBack, normalBack, textureBottomLeft);
  
     // Add the vertices for the TOP face.
     _vertices[12] = new VertexPositionNormalTexture(topLeftFront, normalTop, textureBottomLeft);
     _vertices[13] = new VertexPositionNormalTexture(topRightBack, normalTop, textureTopRight);
     _vertices[14] = new VertexPositionNormalTexture(topLeftBack, normalTop, textureTopLeft);
     _vertices[15] = new VertexPositionNormalTexture(topLeftFront, normalTop, textureBottomLeft);
     _vertices[16] = new VertexPositionNormalTexture(topRightFront, normalTop, textureBottomRight);
     _vertices[17] = new VertexPositionNormalTexture(topRightBack, normalTop, textureTopRight);
  
     // Add the vertices for the BOTTOM face. 
     _vertices[18] = new VertexPositionNormalTexture(btmLeftFront, normalBottom, textureTopLeft);
     _vertices[19] = new VertexPositionNormalTexture(btmLeftBack, normalBottom, textureBottomLeft);
     _vertices[20] = new VertexPositionNormalTexture(btmRightBack, normalBottom, textureBottomRight);
     _vertices[21] = new VertexPositionNormalTexture(btmLeftFront, normalBottom, textureTopLeft);
     _vertices[22] = new VertexPositionNormalTexture(btmRightBack, normalBottom, textureBottomRight);
     _vertices[23] = new VertexPositionNormalTexture(btmRightFront, normalBottom, textureTopRight);
  
     // Add the vertices for the LEFT face.
     _vertices[24] = new VertexPositionNormalTexture(topLeftFront, normalLeft, textureTopRight);
     _vertices[25] = new VertexPositionNormalTexture(btmLeftBack, normalLeft, textureBottomLeft);
     _vertices[26] = new VertexPositionNormalTexture(btmLeftFront, normalLeft, textureBottomRight);
     _vertices[27] = new VertexPositionNormalTexture(topLeftBack, normalLeft, textureTopLeft);
     _vertices[28] = new VertexPositionNormalTexture(btmLeftBack, normalLeft, textureBottomLeft);
     _vertices[29] = new VertexPositionNormalTexture(topLeftFront, normalLeft, textureTopRight);
  
     // Add the vertices for the RIGHT face. 
     _vertices[30] = new VertexPositionNormalTexture(topRightFront, normalRight, textureTopLeft);
     _vertices[31] = new VertexPositionNormalTexture(btmRightFront, normalRight, textureBottomLeft);
     _vertices[32] = new VertexPositionNormalTexture(btmRightBack, normalRight, textureBottomRight);
     _vertices[33] = new VertexPositionNormalTexture(topRightBack, normalRight, textureTopRight);
     _vertices[34] = new VertexPositionNormalTexture(topRightFront, normalRight, textureTopLeft);
     _vertices[35] = new VertexPositionNormalTexture(btmRightBack, normalRight, textureBottomRight);
  
     _isConstructed = true;
 }



Drawing the Cube

I have set up a new effect in this class: a BasicEffect called cubeEffect, which we’ll use to set our world, view and projection matrices, as well as the texture to apply.

In XNA 4.0 you don’t have to call Begin and End on an effect, or on effect passes. The new way of drawing with an effect is actually very simple and streamlined as you’ll see in my Draw method below:

 protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);            

    // Set the World matrix which defines the position of the cube
    cubeEffect.World = Matrix.CreateRotationY(MathHelper.ToRadians(rotation)) *
        Matrix.CreateRotationX(MathHelper.ToRadians(rotation)) * Matrix.CreateTranslation(modelPosition);

    // Set the View matrix which defines the camera and what it's looking at
    cubeEffect.View = Matrix.CreateLookAt(cameraPosition, modelPosition, Vector3.Up);

    // Set the Projection matrix which defines how we see the scene (Field of view)
    cubeEffect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio, 1.0f, 1000.0f);

    // Enable textures on the Cube Effect. this is necessary to texture the model
    cubeEffect.TextureEnabled = true;
    cubeEffect.Texture = cubeTexture;

    // Enable some pretty lights
    cubeEffect.EnableDefaultLighting();

    // apply the effect and render the cube
    foreach (EffectPass pass in cubeEffect.CurrentTechnique.Passes)
    {
        pass.Apply();
        cubeToDraw.RenderToDevice(GraphicsDevice);
    }

    base.Draw(gameTime);
}

This is a great start to what I think is a cool game. Thanks also to Shawn Hargreaves for answering some of my silly questions.