Whew, that's a lot to answer! Let me see what I can clear up for you. [smile]
1. Creating some sort of class to encapsulate your rendering process is usually the most straightforward path to proceed. As you've already noticed, as you move towards making an actual game you're just going to be throwing more and more at your renderer and you're likely to need more support functions to go with it. If you're following OOP, making those functions part of a class is the way to go.
2. For your simple application here, I see no problem with having the small number of global variables you're using. I mean unless clicking on points and making them turn a color is going to be some integral part of an actual game or program, there's no need to worry about it. As for how you're storing them in an array...you've pointed out how since you've used a statically allocated array it is "stuck" at one fixed size which you cannot exceed. For this kind of problem you really have 3 options:
a) Limit the number of points to the size of array - simple, easy to implement, comes with restrictions
b) Use dynamically allocated arrays that you resize as needed. This isn't too difficult to implement, you just need to be careful with your new's and delete's to ensure no memory leaks.
// Initially allocate the arrayint g_mouseVertsSize = 100;CUSTOMVERTEX* g_mouseVerts = new CUSTOMVERTEX [g_mouseVertsSize];int g_mouseVertsCount = 0;// Then in your AddMousePoint function...if ( mouseVertsCount + 1 >= g_MouseVertsSize ){ // resize by double g_mouseVertsSize *= 2; CUSTOMVERTEX* newMouseVerts = new CUSTOMVERTEX [g_mouseVertsSize]; // copy over old data CopyMemory( newMouseVerts, g_mouseVerts, g_mouseVertsCount * sizeof(CUSTOMVERTEX) ); // delete old array delete [] g_mouseVerts; g_mouseVerts = newMouseVerts;}
c) Use some sort of container class that will grow dynamically for you. std::vector is the obvious choice, you can simply add points to it and it will grow as necessary. The problem with std::vector is that once your points are in there, there is no easy way for you to send all the points to Direct3D (since you can't provide a pointer to an array of vertices for DrawPrimitiveUP()). You could copy the points to a dynamically allocated array, but this isn't much better than option b). What would be nice is using a container that automatically resizes but can provide you with a pointer to an internal array, such as the CAtlArray class (part of Microsoft's ATL library, which comes with Pro versions of Visual Studio).
3. In the case of locking an vertex buffer like that, typically a pointer to a type that can be used to access the internal contents of the buffer is used for convenience. So for example with your buffer, which was initially filled with elements of type CUSTOMVERTEX, a pointer of type CUSTOMVERTEX* is used to iterate through the vertex buffer so that the contents can be accessed. If you wanted you could declare the pointer as type void* so that it's not necessary to cast to void** passing the address of the pointer to the Lock() function, however this would mean that after locking you would need to cast that pointer to some other type before reading anything or writing anything to the buffer. void* is a generic type and can't be used for accessing data, all it can be used for is to point to data.
As for why the Lock() function takes a pointer to a pointer (AKA a variable of type void** and not void* as a parameter), the reason is because of the differences in C and C++. First let's talk about what the Lock() function does: it takes as a parameter a pointer, and then it takes that pointer and points it at some data representing the contents of the vertex buffer. Basically before you call the function you don't know where that data is, so you call it and D3D tells you. So this means that the function has to be able to change the parameter. Now you've said your new to C++, but I'm sure you've gone over how parameters are handled in C++. By default, the variables a function gets are
copies of the original, and any changes you make will have no effect on the original variable. When it is necessary for the original to be changed, parameters are passed by reference. Like in this example:
void Add ( int a, int b, int& c ){ c = a + b;}
Okay, great. So you may be wondering: why isn't the ppbData parameter of Lock() passed by reference? The reason why is because the D3D API is written so it can be used from both C and C++ by making use of COM interfaces. And in C, there is no pass by reference. Instead, things have to be done by explicitly using pointers to types. In C the equivalent function to what I wrote above would look like this:
void Add ( int a, int b, int* c ){ *c = a + b;}
Now it should be apparent why the Lock() function looks the way it does. Since it ultimately wants to change the value of a pointer, it needs the a pointer to that pointer so it the changes will be reflected in the original. This is why you send "&pVertices", since the & operator means "get a pointer to this".
4. No, you're not really doing that in the most optimal way. You're right when you assume that locking a vertex buffer is slow, it's something that really shouldn't be done unless you have to. In general with 3D programming on the PC, when you have a triangle (or group of triangles) that you wanted to be transformed by several matrices, you want this transformation to be done on the GPU and not on the CPU. The way you're doing it, where you Lock the vertex buffer and manually apply the rotation and translation matrices, all of that calculation happens on the CPU. While for 4 vertices this is absolutely no problem at all, for 4,000 it would be quite wasteful. However for you to start getting this happening on the GPU, you would need to make some modifications.
You see the way you're doing things now is with pre-transformed vertices. The x and y components of your vertices correspond to on-screen coordinates, and the GPU is drawing them there accordingly. This is not common for 3D graphics work, since as I already mentioned it requires all transformation work to be done on the GPU. Instead what's usually done is the vertices are sent in their original positions (referred to as object space) and then, on the GPU, transformed by matrices. In the simplest method, using the fixed-function pipeline, these transforms are specified through IDirect3DDevice9::SetTransform(). There are typically three transforms used:
-the world transform, which usually translates and rotates primitives to their in-world positions and orientation
-the view transform, which translates and rotates primitives so that they are in positions and orientations that are relative to the virtual camera, or eye.
-the projection transform, projects the primitives into 2D space. Usually a perspective projection is used, which makes distant objects appear smaller than closer ones.
If this is all new to you, you should probably read a bit more on these topics or look through some of the SDK samples that deal with this early material. While it may not be completely necessary for 2D work, it's definitely essential for anything in 3D or for understanding how D3D works under the hood.
Speaking of 2D work...if 2D is your goal you may want to look at ID3DXSprite. It's a helper class for rendering of 2D sprites, and can get you started without messing with any of the lower level D3D stuff.