Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
MoofWars/TTileGrid.cp
/* |
File: TTileGrid.cp |
Contains: A TileGrid is a class that describes a 2 dimensional array of tiles that can be |
drawn as the background for a game. This current version only supplies |
graphics routines, but collision and other logic should be fairly easy to |
generate. |
Written by: Timothy Carroll |
Copyright: Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
Change History (most recent first): |
7/2/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
4/2/97 Timothy Carroll fTileData is a handle, but the destructor |
was calling DisposePtr on it. Ouch! Bad |
programmer, no donut. Because my handle |
was cast to a **CellGridType, I had to recast |
it back when I disposed of it, and I did it |
wrong. Spotlight caught this one for me. |
1/23/97 Timothy Carroll Added include for Moofwars.h so that MrC |
will compile |
8/15/96 Timothy Carroll Initial Release |
*/ |
#include <Memory.h> |
#include <Resources.h> |
#include "Moofwars.h" |
#include "TTileGrid.h" |
#include "scaling.h" |
/************************************************************************************* |
TTileGrid::TTileGrid |
~TTileGrid::TTileGrid |
*************************************************************************************/ |
TTileGrid::TTileGrid (void) |
{ |
fWidth = 0; |
fHeight = 0; |
fDefaultTile = 0; |
fTiles = NULL; |
fTileData = NULL; |
} |
TTileGrid::~TTileGrid (void) |
{ |
if (fTiles != NULL) |
DisposeTileCollection (fTiles); |
if (fTileData != NULL) |
DisposeHandle ((Handle) fTileData); |
} |
/************************************************************************************* |
TTileGrid::CreateGridFromResource |
*************************************************************************************/ |
OSErr |
TTileGrid::CreateGridFromResource (SInt16 resID) |
{ |
OSErr theErr = noErr; |
TileGridResHeader **grid; |
Ptr srcPtr, destPtr; |
grid = (TileGridResHeader **) Get1Resource (TileGridResType, resID); |
theErr = ResError(); |
FAIL_OSERR (theErr,"\pFailed to load GRID resource") |
FAIL_NIL (grid, "\pFailed to load GRID resource") |
if ((**grid).version != 0) SIGNAL_ERROR ("\pUnable to read this GRID format") |
fWidth = (**grid).width; |
fHeight = (**grid).height; |
fDefaultTile = (**grid).defaultTile; |
fTiles = NewTileCollection ((**grid).tileResID); |
FAIL_NIL (fTiles, "\pFailed to load the Tiles needed for GRID.") |
fTiles->LockCollection(); |
fTileData = (CellGridType **) NewHandle (fWidth * fHeight * sizeof (CellGridType)); |
theErr = MemError(); |
FAIL_OSERR (theErr, "\pFailed to allocate memory for the GRID data") |
FAIL_NIL (fTileData, "\pFailed to allocate memory for the GRID data") |
destPtr = (Ptr) *fTileData; |
srcPtr = (Ptr) (*grid)+sizeof (TileGridResHeader); |
BlockMoveData (srcPtr, destPtr, fWidth * fHeight * sizeof (CellGridType)); |
goto cleanup; |
error: |
if (theErr == noErr) |
theErr = paramErr; |
cleanup: |
if (grid != NULL) |
ReleaseResource ((Handle) grid); |
return theErr; |
} |
/************************************************************************************* |
Accessor Functions |
*************************************************************************************/ |
CellGridType |
TTileGrid::GetGridValue (SInt32 h, SInt32 v) |
{ |
SInt32 offset; |
if ((h < 0) || (h >= fWidth) || (v < 0) || (v >= fHeight)) |
return fDefaultTile; |
offset = (v*fHeight + h); |
return *((*fTileData)+offset); |
} |
void |
TTileGrid::SetGridValue (SInt32 h, SInt32 v, CellGridType value) |
{ |
SInt32 offset; |
if ((h < 0) || (h >= fWidth) || (v < 0) || (v >= fHeight)) |
return; |
offset = (v*fHeight + h); |
*((*fTileData)+offset) = value; |
} |
/************************************************************************************* |
TTileGrid::DrawGrid |
The heart of the current TTileGrid code. This routine uses the values stored in the grid to draw |
into a rectangle on the screen. |
Inputs: |
We input a rectangle (in screen coordinates) to draw to, and the top left corner (in world coordinates) |
that corresponds to the top left corner of the rectangle. This routine figures out which tiles need to be |
drawn and draws them as quickly as possible. |
Further optimizations: |
In our worse case, we clip both vertically and horizontally. A normal unclipped tile grid would be 20x15 |
for a 640x480 screen, or 300 tiles. The fully clipped case would be 21x16 or 336 tiles. Of these tiles, |
70 of them will be drawn with the clipped algorithm or about 20%. So speeding up the handling of clipped |
tiles would improve the speed of grid drawing in most cases. |
The best way to speed up clipping is probably to differentiate between vertical and horizontal clipping. |
If we are just vertical clipping, we can use the same fast inner blit loops that we've been using for |
the unclipped blitters -- we just draw less rows. Horizontal clipping can use the fact that we always |
have a set of data aligned to the same alignment as the destination. |
As mentioned below, there is one switch that can be moved from once per tile to once per grid. The main |
reason this isn't done in the code below is that I haven't found a clean C++ way to do it that doesn't |
involve copying the same loop 8 times. I figured that clarity was better in this case, because the |
performance difference was negligible. |
*************************************************************************************/ |
void |
TTileGrid::DrawGrid (Rect *screenRect, SInt32 topGlobal, SInt32 leftGlobal) |
{ |
SInt32 tilesWidth, tilesHeight; // number of tiles in each direction to draw. |
SInt32 indexH, indexV; // index coordinates of the tile currently being drawn |
SInt32 startH; // base index to start each line at |
SInt32 drawY, drawX; // screen coordinates of the tile currently being drawn |
SInt32 startX; // base x coordinate to start each line of tiles at. |
Boolean horizontalClip, verticalClip; // whether or not we need to clip |
SInt32 hLoop, vLoop; // used to loop over the tiles being drawn |
unsigned char *destPtr; // Points to the destination inside the buffer |
unsigned char *destRowPtr; // Points to the beginning of the line in a buffer |
UInt32 vertOffset; // Use this to increment destRowPtr to the next line of tiles |
UInt32 alignment; // we can calculate the alignment for all the tiles, which |
// is their exact byte alignment. This lets us call a custom |
// blitter for that alignment. See the TTileCollection |
// blitters for more info. |
SInt32 temp; |
// ASSERTIONS |
// The screenRectangle should start on a multiple of 8 bytes, and should be an integral number of tiles |
// tall and wide. This helps us determine exactly which tiles to draw. |
#if qDebugging |
if (screenRect->left % 8 != 0) |
{ |
DebugStr ("\pscreenRect improperly aligned"); |
return; |
} |
if ((screenRect->right - screenRect->left) % 32 != 0) |
{ |
DebugStr ("\pscreenRect improper width"); |
return; |
} |
if ((screenRect->bottom - screenRect->top) % 32 != 0) |
{ |
DebugStr ("\pscreenRect improper width"); |
return; |
} |
#endif |
// First, determine the number of tiles in the screen rectangle. |
tilesWidth = (screenRect->right - screenRect->left) >> 5; |
tilesHeight = (screenRect->bottom - screenRect->top) >> 5; |
// We need to determine where we actually need to start drawing, both in screen coordinates and in |
// indices into the Grid values. We also determine if we actually need to perform any clipping. |
// |
// If the top/left coordinate in world coordinates is a multiple of 32, then we are exactly aligned to |
// the screen rectangle, and no clipping needs to take place. This is actually the fastest case, because |
// we don't call our slower clipping tiles, and we also call the fastest tile blitter. |
// |
// If it isn't a multiple of 32, then we're not aligned and we need to draw an extra tile to get the |
// full row. Because we handle the clipped case separately, we actually decrement the number of tiles |
// we'll be drawing. |
temp = leftGlobal % 32; |
startH = leftGlobal / 32; |
if (temp != 0) |
{ |
tilesWidth--; |
horizontalClip = true; |
if (leftGlobal < 0) |
{ |
startH = (leftGlobal-31) / 32; |
if (temp < 0) temp+=32; |
} |
} |
else |
horizontalClip = false; |
// However much our start point is offset to the right, we need to offset that far to the left to |
// determine our initial drawing point. |
startX = screenRect->left - temp; |
// We do the same for the vertical components. |
temp = topGlobal % 32; |
indexV = topGlobal / 32; |
if (temp != 0) |
{ |
tilesHeight--; |
verticalClip = true; |
if (topGlobal < 0) |
{ |
indexV = (topGlobal-31) /32; |
if (temp < 0) temp+= 32; |
} |
} |
else |
verticalClip = false; |
drawY = screenRect->top - temp; |
// Calculate our initial destination pointer and our offset between rows. We also |
// calculate the alignment for the entire grid. |
destRowPtr = gDestBaseAddr + drawY * gRowBytes + startX; |
vertOffset = gRowBytes << 5; |
alignment = ((long) destRowPtr) & 0x07; |
// handle top clip |
if (verticalClip) |
{ |
drawX = startX; |
indexH = startH; |
if (horizontalClip) |
temp = tilesWidth+2; |
else |
temp = tilesWidth; |
for (hLoop = 0; hLoop < temp; hLoop++) |
{ |
UInt32 index; |
index = GetGridValue (indexH, indexV); |
fTiles->CopyImageClipped (index, drawY, drawX); |
indexH++; |
drawX +=32; |
} |
indexV++; |
drawY += 32; |
destRowPtr += vertOffset; |
} |
for (vLoop = 0; vLoop < tilesHeight; vLoop++) |
{ |
drawX = startX; |
indexH = startH; |
destPtr = destRowPtr; |
if (horizontalClip) |
{ |
UInt32 index; |
index = GetGridValue (indexH, indexV); |
fTiles->CopyImageClipped (index, drawY, drawX); |
indexH++; |
drawX +=32; |
destPtr += 32; |
} |
for (hLoop = 0; hLoop < tilesWidth; hLoop++) |
{ |
UInt32 index; |
index = GetGridValue (indexH, indexV); |
// *********** NOTE: ************* |
// Alignment is actually the same for all tiles in the grid, which means we could |
// optimize this switch outside all the loops. Only problem with this is that we |
// end up copying a lot of loop code for each alignment. I had this in the code and |
// took it out for this sample because the function had grown huge, but it is something |
// that can be optimized further. In a C implementation, you could probably get a function |
// pointer to the appropriate blitter, once, outside the loop and call from inside. |
switch (alignment) |
{ |
case 0: fTiles->CopyImageUnclipped0 (index, destPtr); break; |
case 1: fTiles->CopyImageUnclipped1 (index, destPtr); break; |
case 2: fTiles->CopyImageUnclipped2 (index, destPtr); break; |
case 3: fTiles->CopyImageUnclipped3 (index, destPtr); break; |
case 4: fTiles->CopyImageUnclipped4 (index, destPtr); break; |
case 5: fTiles->CopyImageUnclipped5 (index, destPtr); break; |
case 6: fTiles->CopyImageUnclipped6 (index, destPtr); break; |
case 7: fTiles->CopyImageUnclipped7 (index, destPtr); break; |
} |
indexH++; |
drawX +=32; |
destPtr += 32; |
} |
if (horizontalClip) |
{ |
UInt32 index; |
index = GetGridValue (indexH, indexV); |
fTiles->CopyImageClipped (index, drawY, drawX); |
} |
indexV++; |
drawY += 32; |
destRowPtr += vertOffset; |
} |
// handle bottom clip |
if (verticalClip) |
{ |
SInt32 temp; |
drawX = startX; |
indexH = startH; |
if (horizontalClip) |
temp = tilesWidth+2; |
else |
temp = tilesWidth; |
for (hLoop = 0; hLoop < temp; hLoop++) |
{ |
UInt32 index; |
index = GetGridValue (indexH, indexV); |
fTiles->CopyImageClipped (index, drawY, drawX); |
indexH++; |
drawX +=32; |
} |
} |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-10-14