The IDirectDrawClipper InterfaceSuppose you have a graphic that you want to appear such that only half of it is on the screen. How would you do it? If you've programmed games in DOS, you've probably done clipping the hard way. Well, in DirectX, it's trivial! First of all, it's pretty easy to do manually, since DirectX uses RECTs for blitting, and changing the coordinates of a RECT is much easier than figuring out which parts of the graphic's memory should be copied, like you would have to do in DOS. But second, DirectDraw provides an entire interface to take care of this for you, called IDirectDrawClipper. The clipping capabilities included in DirectDraw are about as flexible as you could ask for. Not only can you clip to any RECT area on any surface, but you can clip to multiple areas! That is, if you wanted a main viewing window, a status bar on the side of your screen, and a text area on the lower part of the screen, with a black divider partitioning the screen into those three areas, you could set up a DirectDraw clipper that would clip to all three regions. How cool is that? There are several steps involved in creating a clipper to do this kind of work for you. The first is to actually retrieve a pointer to the IDirectDrawClipper interface. Not surprisingly, this is accomplished with a call to IDirectDraw7::CreateClipper, as shown here: HRESULT CreateClipper( DWORD dwFlags, LPDIRECTDRAWCLIPPER FAR *lplpDDClipper, IUnknown FAR *pUnkOuter ); Before you make this call, you'll need to declare a pointer of type LPDIRECTDRAWCLIPPER, so you can pass its address to the function. Remember to test for failure, as always. Here are the parameters: DWORD dwFlags: Nice and easy -- this isn't used yet and should be set to 0. LPDIRECTDRAWCLIPPER FAR *lplpDDClipper: Pass the address of your LPDIRECTDRAWCLIPPER pointer. IUnknown FAR *pUnkOuter: You know what to do. Just say no... er, NULL. :) Once that's out of the way and you have your interface pointer, the next thing to do is to create a clip list. A clip list is basically a list of the RECTs you want to clip to. The structure that's used is called an RGNDATA, and it contains enough information to define an arbitrary region, which can be made up of multiple components. Let's look at the structure. typedef struct _RGNDATA { RGNDATAHEADER rdh; char Buffer[1]; } RGNDATA; It's not at all clear what those parameters are for, so I'll go over them in detail. RGNDATAHEADER rdh: This is a structure nested within the RGNDATA that contains information about the second parameter, Buffer. Its fields include things like how many areas comprise the region, what shape those areas are, etc. We'll cover that structure in just a second. char Buffer[1]: This isn't actually meant to be a single-valued array; it's going to be an area in memory of arbitrary size that holds the data for the actual clipping areas. As such, instead of declaring an RGNDATA structure, what we do is to declare a pointer to the structure, and then use malloc() to set enough memory aside for the RGNDATAHEADER, and the clip list itself. One thing I'll mention now: RECTs in the clip list should be ordered from top to bottom, then from left to right, and must not overlap. I realize that this is all a little hazy right now, but all will be made clear. To that end, here's the RGNDATAHEADER structure, which is relatively easy to understand. typedef struct _RGNDATAHEADER { DWORD dwSize; DWORD iType; DWORD nCount; DWORD nRgnSize; RECT rcBound; } RGNDATAHEADER; DWORD dwSize: This is the size of the structure in bytes. Simply set it to sizeof(RGNDATAHEADER). DWORD iType: This represents the shape of each of the areas which make up the region. It is included so that it may be expanded upon later. Right now, the only valid setting for this member is RDH_RECTANGLES, which is what we want anyway. DWORD nCount: This is the number of rectangles that make up the region. In other words, it's the number of RECTs you plan to use in your clip list. DWORD nRgnSize: Set this to the size of the buffer that will be receiving the region data itself. Since we're using RECTs, this size will be sizeof(RECT) * nCount. RECT rcBound: This is a RECT that bounds all the rectangles in your clip list. Usually you'll set this to the dimensions of the surface on which the clipping will occur. Now that we've seen all of the structures involved, we can generate a clip list. First we declare an LPRGNDATA pointer and allocate enough memory to it to hold our clip list, then simply fill out the fields of each structure according to their descriptions above. Let's look at the simplest case, which you'll probably use often, which is that of only a single clipping area. Furthermore, let's make it size of the whole screen, in a 640x480 display mode. Here's the code that will get the job done. // first set up the pointer -- we allocate enough memory for the RGNDATAHEADER // along with one RECT. If we were using multiple clipping area, we would have // to allocate more memory. LPRGNDATA lpClipList = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER) + sizeof(RECT)); // this is the RECT we want to clip to: the whole display area RECT rcClipRect = {0, 0, 640, 480}; // now fill out all the structure fields memcpy(lpClipList->Buffer, &rcClipRect, sizeof(RECT)); // copy the actual clip region lpClipList->rdh.dwSize = sizeof(RGNDATAHEADER); // size of header structure lpClipList->rdh.iType = RDH_RECTANGLES; // type of clip region lpClipList->rdh.nCount = 1; // number of clip regions lpClipList->rdh.nRgnSize = sizeof(RECT); // size of lpClipList->Buffer lpClipList->rdh.rcBound = rcClipRect; // the bounding RECT Once you have a clip list, you need to set it as such with your clipper. The call you want is SetClipList(), which is of course a method of the IDirectDrawClipper interface. Here's what it looks like: HRESULT SetClipList( LPRGNDATA lpClipList, DWORD dwFlags ); All you need to do is pass a pointer to the RGNDATA structure you just filled out. The dwFlags parameter is not used, so just set it to 0. Now that the clip list is set, there's just one more step, and that's attaching the clipper to the surface you want to do your clipping on. This requires a call to SetClipper(), which is a method of the surface you want to attach the clipper to, not the clipper itself. HRESULT SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); And you know what to do with that: just pass your interface pointer and you're all set. Anytime you try to blit to a surface that has a clipper attached to it, the clipper does all the work. So if you want to show a tile that's half-on, half-off the screen, go ahead and blit to a RECT like {-10, -10, 6, 6}, or whatever it happens to be. Pretty sweet, hey? The last thing I'll say about clippers is that you should remember to free() the memory that you allocated with malloc(), no matter what happens with your clipper. That is, if the call to SetClipList() or SetClipper() fail for some reason, make sure you're still freeing the memory before you return an error code or whatever you do to handle errors. You won't be needing the LPRGNDATA pointer anymore after you use it to set the clip list, so its memory should be deallocated immediately. ClosingThat just about wraps up the series on general DirectDraw stuff! Can you believe how much we've covered in just six articles? Congratulate yourself if you're still reading these; you've come a long way. :) To better illustrate some of the things we've covered today, I've thrown a little demo program together for you. It demonstrates loading a bitmap resource; using the blitter for image copying, color filling, and scaling; and using a clipper to make it all easy. The program is available here. There are still some things we haven't covered that didn't quite fit into the articles like I wanted them to, like page flipping as an alternative to double-buffering, and using DirectDraw in a windowed app, but that's all right, because we'll just pick them up as we go on. Now that the initiation is over, I'm going to be shifting the focus of these articles off of general Windows programming, and onto developing a tile-based RPG. Future articles will include such things as developing a good input mechanism with DirectInput, writing a basic scripting engine, playing background sound and music, developing utilities to help you with game design, etc. Next time, we'll take a look at developing a simple scrolling engine for a tile-based game. It's easier than you might think! As always, until then, feel free to send me your questions at ironblayde@aeon-software.com, or reach me on ICQ at UIN #53210499. Practice up on the techniques we've covered so far, because you're going to need them all. :) Later, everyone! Copyright © 2000 by Joseph D. Farrell. All rights reserved. |