Points

Introduction

Defining our Vertices

Initializing Data

Input Handling

Rendering Points

Lesson Downloads

MSDN Links For Functions/Concepts Introduced Here

Introduction

In this lesson we are going to plot points defined in Screen Space. We'll generate 2 different data sets and plot them. We will also show some very basic input handling which will allow you to control which data set of points we'll plot.

Defining our Vertices

As mentioned in our 3D Primer, a point is defined by a single vertex. For our vertices we need a position, and RHW and to make things look more interesting, a colour. Here are our vertex definitions and our declaration of 2 arrays to hold our data.

int g_width=640;  //The width of our display


struct point_vertex{
    float x, y, z, rhw;  // The transformed(screen space) position for the vertex.

    DWORD colour;        // The vertex colour.

};

const DWORD point_fvf=D3DFVF_XYZRHW|D3DFVF_DIFFUSE;

point_vertex sine_data[g_width]; //Enough data to plot from one edge to the other

point_vertex random_data[g_width];

Initializing Data

Since our points will be distributed on a flat plane, we won't really use the Z coordinate so we'll set it to 1.0. The rhw will also be set to 1.0 in each case. I won't go into a lot of detail about how I generate the data since it's not really important. Basically the first data set is a sine wave that's scaled and offset. The green channel of the points is determined by how close they are to the left edge, and the blue channel by their proximity to the right edge. The red channel is calculated from the magnitude (absolute value of the distance from the horizontal axis). Our second data set is just a bunch of random points with random colours. Here's the code to generate the second data set.

int count;
   srand(GetTickCount());
   for(count=0;count<g_width;count++){
      random_data[count].x=(float)(rand()%g_width);
      random_data[count].y=(float)(rand()%g_height);
      random_data[count].z=1.0f;
      random_data[count].rhw=1.0f;
      random_data[count].colour=D3DCOLOR_XRGB(rand()%255,rand()%255,rand()%255);
   }

The X and Y coordinate are set to random values, and the Z and RHW are both 1.0. Since we don't have a Z Buffer and Transformed Vertices have no perspective, our Z coordinate doesn't matter. Also, since we're plotting points, they'd be a single pixel regardless. In the past lessons I've always shown colours being defined as a single hex value. Here we use a built in macro to generate the colour from 2 separate components. Each channel is from 0 to 255, and this macro sets the Alpha channel (transparency) to 255 (0xFF). Since we haven't set up support for transparency the Alpha channel will be ignored.

Input Handling

The majority of the code from the lesson in Initializing Direct3D is the same in this lesson. We added vertex definitions and a function to initialize the data. The other 2 changes are in the render function and the message handler.

In the original code the application quit when any key was hit. That makes it hard to accept user input. I've changed it so the only key it reacts to is the 't' key which will toggle which data set is currently being rendered. The key code is in the wparam field and it is the actual character code that was pressed rather than the raw key. What this means is that it will take into account for locale (different regional keyboards) and also is case-sensitive. If we wanted it to work for both upper and lower case we would have to explicitly test for that. If we wanted a raw key code we could handle WM_KEYDOWN rather than the WM_CHAR we're using here. In fact, many applications handle both. WM_KEYDOWN will cover non-ASCII keys like the arrow keys and the Escape key. And you can still use WM_CHAR to grab character-based input too.

So when the 't' key is pressed, the boolean to determine which data set to render is toggled. NOTE:WM_CHAR will send key repeats. If you hold 't' down the data set will flash back and forth between the 2 data sets. The rate at which key repeat messages is sent depends on your system preferences. Since we're handling WM_CHAR messages, we return 0 to indicate that the message has been handled. Here's our message handler.

LRESULT CALLBACK default_window_proc(HWND p_hwnd,UINT p_msg,WPARAM p_wparam,LPARAM p_lparam){
   
   switch(p_msg){
      case WM_CHAR:  // A key has been pressed, process the key

         if (p_wparam=='t') {
            random_mode=!random_mode;
         }
         return 0;
      case WM_CLOSE:       //User hit the Close Window button, end the app

      case WM_LBUTTONDOWN: //user hit the left mouse button

         g_app_done=true;
         return 0;
   }
   
   return (DefWindowProc(p_hwnd,p_msg,p_wparam,p_lparam));
   
}

Rendering Points

After all of the lead up and theory, we finally get to draw something. The render function is almost the same as it was in Initializing D3D. The first difference is a call to SetFVF. This is where we pass our vertex FVF in to the device so it knows how to interpret the data we'll be sending it. There's not much to the call, but here it is.

   g_d3d_device->SetFVF(point_fvf);

When you set the FVF it stays current until another one is set. So this could be moved to the init_scene function. If the device is lost we'd have to reset it as well. I kept it in the render function to keep the code together in a more logical, if not completely practical, way.

Next is the call that does the rendering. There are a variety of Draw calls, but the one we use here is DrawPrimitiveUP. The UP stands for User Pointer. In a later lesson (Vertex Buffers) I'll show you a more efficient way of storing your data, but for now this works well. Here's the prototype for the function.

HRESULT DrawPrimitiveUP(
    D3DPRIMITIVETYPE PrimitiveType,
    unsigned int PrimitiveCount,
    const void *pVertexStreamZeroData,
    unsigned int VertexStreamZeroStride
);
PrimitiveType
The type of primitive we'll be rendering. In this case it's a point list (D3DPT_POINTLIST).
PrimitiveCount
How many primitives to draw. Direct3D has no way of knowing how many primitives there are in our array. This can also be used to draw only a portion of the point list.
pVertexStreamZeroData
This is just a pointer to our vertex array.
VertexStreamZeroStride
This specifies the size in bytes of each vertex. After reading each vertex it needs to know how far to move forward to read the next one.

Here's our call to DrawPrimitiveUP.

   g_d3d_device->DrawPrimitiveUP(D3DPT_POINTLIST,        //PrimitiveType

                                 g_width,                //PrimitiveCount

                                 data,                   //pVertexStreamZeroData

                                 sizeof(point_vertex));  //VertexStreamZeroStride

We're drawing a Point List, and there are the same number of primitives as pixels across our display. We also pass in a pointer to our data and the size of each vertex. It couldn't be much simpler than that!

Now when you run the example program you'll see a random scattering of coloured dots. Hitting 't' will toggle you between that dot cloud and the plotting of a Sine Wave. Feel free to tinker with the data to plot different shapes. Remember that if you change the size of the data set you have to change the array definition and the value passed to DrawPrimitiveUP.

Lesson Downloads

MSDN Links For Functions/Concepts Introduced Here


Back