- Website design & DirectX code : Riemer Grootjans -
- Terrain creation basics
At last, we've
seen enough topics to start creating our terrain. Lets start small, with
3x3 given points. However, we will make our engine dynamic, so
next chapter we can load a much larger number of points. To do
this, we have to create 2 new variables in our
class:
private int WIDTH = 4;
private int HEIGHT
= 3;
We will
suppose the 4x3 points are equidistant. So the only thing we
don't know you about our points is the Z coordinate. We will
use an array to hold this information, so we'll also add
this line to the top of our class as well:
private int[,]
heightData;
For now, use
this method to fill the array :
private void
LoadHeightData()
{
heightData
= new int[4,3];
heightData[0,0]=0;
heightData[1,0]=0;
heightData[2,0]=0;
heightData[3,0]=0;
heightData[0,1]=1;
heightData[1,1]=0;
heightData[2,1]=2;
heightData[3,1]=2;
heightData[0,2]=2;
heightData[1,2]=2;
heightData[2,2]=4;
heightData[3,2]=2;
}
Since we only have to
load the data once, we will call it in our Main method. Place
it as the first call in your Main method:
our_directx_form.LoadHeightData();
With our height array
filled, we can now create our vertices. Since we have a 4x3
terrain, 12 (=WIDTH*HEIGHT) vertices will do. The points are
equidistant, so we can already change our
VertexDeclaration method. We will not use the Z coordinate
yet, to see the difference.
private void
VertexDeclaration()
{
vb = new
VertexBuffer(typeof(CustomVertex.PositionColored),
WIDTH*HEIGHT, device, Usage.Dynamic | Usage.WriteOnly,
CustomVertex.PositionColored.Format,
Pool.Default);
vertices
= new
CustomVertex.PositionColored[WIDTH*HEIGHT];
for
(int x=0;x<WIDTH;x++)
{
for (int y=0;
y<HEIGHT;y++)
{
vertices[x+y*WIDTH].SetPosition(new
Vector3(x, y, 0));
vertices[x+y*WIDTH].Color
= Color.White.ToArgb();
}
}
vb.SetData(vertices,
0 ,LockFlags.None);
}
Nothing magic going on here, you simply define the
definition of your 12 points and make them white. Next comes a
more difficult part: creating the needed triangles to connect
the 12 vertices. Of course, this is again done using indexing.
The best way to do this is by creating two sets of
vertices:
We'll start by drawing the right-topped set of triangles.
To do this, change your IndicesDeclaration method llike
this:
private void
IndicesDeclaration()
{
ib = new
IndexBuffer(typeof(int), (WIDTH-1)*(HEIGHT-1)*3, device,
Usage.WriteOnly, Pool.Default);
indices
= new
int[(WIDTH-1)*(HEIGHT-1)*3];
for
(int x=0;x<WIDTH-1;x++)
{
for (int y=0;
y<HEIGHT-1;y++)
{
indices[(x+y*(WIDTH-1))*3] =
(x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*3+1]
= (x+1)+y*WIDTH;
indices[(x+y*(WIDTH-1))*3+2]
= x+y*WIDTH;
}
}
ib.SetData(indices,
0, LockFlags.None);
}
We will need 2 rows of 3 triangles, giving 6 triangles.
These will require 6 * 3 = 18 indices
(=(WIDTH-1)*(HEIGHT-1)*3). You create the needed structures
for this in the first 2 lines. Then, you fill the indices
array. Again you scan the X and Y coordinates, and create your
triangles. Every triangle needs 3 indices, that's where the *3
comes from. Remember culling? It requires us to define the
points in counter-clockwise order. So first you define the
top-right vertex, then the bottom-down vertex and the
bottom-left vertex. That's all there is to it. The only thing
that's left is to draw the triangles by changing this piece of
code in your OnPaint method :
device.BeginScene();
device.VertexFormat
=
CustomVertex.PositionColored.Format;
device.SetStreamSource(0,
vb, 0);
device.Indices =
ib;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0, 0, WIDTH*HEIGHT, 0,
indices.Length/3);
device.EndScene();
Try positioning your camera at (0,0,15) and run the
program. You should see 6 triangles in the left half of your
window, every point of every triangle at the same Z
coordinate. Now change the height of your points according to
your heightData array :
vertices[x+y*WIDTH].SetPosition(new
Vector3(x, y, heightData[x,y]));
Running this, you will notice how the triangles on the left
are slightly moving upwards. Now it's time to draw the second
set of triangles. We need the same vertices, so the only
thing we have to change is the IndicesDeclaration method:
private void
IndicesDeclaration()
{
ib = new
IndexBuffer(typeof(int), (WIDTH-1)*(HEIGHT-1)*6, device, Usage.WriteOnly,
Pool.Default);
indices
= new int[(WIDTH-1)*(HEIGHT-1)*6];
for
(int x=0;x<WIDTH-1;x++)
{
for (int y=0;
y<HEIGHT-1;y++)
{
indices[(x+y*(WIDTH-1))*6] =
(x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*6+1]
= (x+1)+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+2]
= x+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+3]
= (x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*6+4]
= x+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+5]
= x+(y+1)*WIDTH;
}
}
ib.SetData(indices,
0, LockFlags.None);
}
We will be drawing twice as much vertices now, that's why
the *3 has been replaced by *6 everywhere. You see the second
set of triangles also has been drawn counter-clockwise.
Running this code will give you a better 3 dimensional view.
We've especially taken care only to use the variables WIDTH
and HEIGHT, so these are the only things we need change to
increase the size of our map, togerther with the heightData
array. It would be nice to find a mechanism to fill this last
one automatically, which we'll do in the next chapter.
Our code so far:
using System;
using
System.Drawing;
using
System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
using System.Data;
using
Microsoft.DirectX;
using
Microsoft.DirectX.Direct3D;
namespace
DirectX_Tutorial
{
public class WinForm :
System.Windows.Forms.Form
{
private int WIDTH =
4;
private int HEIGHT = 3;
private Device device;
private System.ComponentModel.Container components =
null;
private float angle = 0f;
private CustomVertex.PositionColored[]
vertices;
private int[,]
heightData;
private int[] indices;
private IndexBuffer ib;
private VertexBuffer vb;
public WinForm()
{
InitializeComponent();
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.Opaque, true);
}
public void
InitializeDevice()
{
PresentParameters presentParams = new
PresentParameters();
presentParams.Windowed =
true;
presentParams.SwapEffect =
SwapEffect.Discard;
device = new Device(0, DeviceType.Hardware, this,
CreateFlags.SoftwareVertexProcessing,
presentParams);
device.RenderState.FillMode =
FillMode.WireFrame;
device.RenderState.CullMode =
Cull.None;
}
private void
CameraPositioning()
{
device.Transform.Projection =
Matrix.PerspectiveFovLH((float)Math.PI/4,
this.Width/this.Height, 1f, 50f);
device.Transform.View = Matrix.LookAtLH(new
Vector3(0,0,15), new
Vector3(0,0,0), new
Vector3(0,1,0));
device.RenderState.Lighting =
false;
device.RenderState.CullMode =
Cull.None;
}
private void
VertexDeclaration()
{
vb = new
VertexBuffer(typeof(CustomVertex.PositionColored),
WIDTH*HEIGHT, device, Usage.Dynamic | Usage.WriteOnly,
CustomVertex.PositionColored.Format,
Pool.Default);
vertices = new
CustomVertex.PositionColored[WIDTH*HEIGHT];
for (int
x=0;x<WIDTH;x++)
{
for (int y=0;
y<HEIGHT;y++)
{
vertices[x+y*WIDTH].SetPosition(new Vector3(x, y,
heightData[x,y]));
vertices[x+y*WIDTH].Color =
Color.White.ToArgb();
}
}
vb.SetData(vertices, 0
,LockFlags.None);
}
private void
IndicesDeclaration()
{
ib = new IndexBuffer(typeof(int),
(WIDTH-1)*(HEIGHT-1)*6, device, Usage.WriteOnly,
Pool.Default);
indices = new
int[(WIDTH-1)*(HEIGHT-1)*6];
for (int
x=0;x<WIDTH-1;x++)
{
for (int y=0;
y<HEIGHT-1;y++)
{
indices[(x+y*(WIDTH-1))*6] =
(x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*6+1] =
(x+1)+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+2] =
x+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+3] =
(x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*6+4] =
x+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+5] =
x+(y+1)*WIDTH;
}
}
ib.SetData(indices, 0,
LockFlags.None);
}
protected override void
OnPaint(System.Windows.Forms.PaintEventArgs
e)
{
device.Clear(ClearFlags.Target, Color.DarkSlateBlue ,
1.0f, 0);
device.BeginScene();
device.VertexFormat =
CustomVertex.PositionColored.Format;
device.SetStreamSource(0, vb,
0);
device.Indices = ib;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,
0, 0, WIDTH*HEIGHT, 0,
indices.Length/3);
device.EndScene();
device.Present();
this.Invalidate();
angle += 0.05f;
}
private void
LoadHeightData()
{
heightData = new int[4,3];
heightData[0,0]=0;
heightData[1,0]=0;
heightData[2,0]=0;
heightData[3,0]=0;
heightData[0,1]=1;
heightData[1,1]=0;
heightData[2,1]=2;
heightData[3,1]=2;
heightData[0,2]=2;
heightData[1,2]=2;
heightData[2,2]=4;
heightData[3,2]=2;
}
protected override void Dispose (bool
disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
private void
InitializeComponent()
{
this.components = new
System.ComponentModel.Container();
this.Size = new
System.Drawing.Size(500,500);
this.Text = "DirectX
Tutorial";
}
static void Main()
{
using (WinForm our_directx_form = new
WinForm())
{
our_directx_form.LoadHeightData();
our_directx_form.InitializeDevice();
our_directx_form.CameraPositioning();
our_directx_form.VertexDeclaration();
our_directx_form.IndicesDeclaration();
our_directx_form.Show();
|