abrash-black-book/17-04.html
2013-12-30 12:21:49 +11:00

241 lines
10 KiB
HTML

<HTML>
<HEAD>
<META name=vsisbn content="1576101746">
<META name=vstitle content="Michael Abrash's Graphics Programming Black Book, Special Edition">
<META name=vsauthor content="Michael Abrash">
<META name=vspublisher content="The Coriolis Group">
<META name=vspubdate content="07/01/97">
<META name=vscategory content="Web and Software Development: Game Development,Web and Software Development: Graphics and Multimedia Development">
<TITLE>Michael Abrash's Graphics Programming Black Book Special Edition: The Game of Life</TITLE>
<!-- HEADER -->
<!-- Empty Reference Subhead -->
<!--ISBN=1576101746//-->
<!--TITLE=Michael Abrash's Graphics Programming Black Book Special Edition//-->
<!--AUTHOR=Michael Abrash//-->
<!--PUBLISHER=The Coriolis Group, Inc.//-->
<!--CHAPTER=17//-->
<!--PAGES=331-334//-->
<!--UNASSIGNED1//-->
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="17-03.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="17-05.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<P><BR></P>
<P>There&#146;s a kicker here, though, and that&#146;s the counting of neighbors for cells at the edge of the cellmap. When cellmap wrapping is enabled (so that the cellmap becomes essentially a toroid, with each edge joined seamlessly to the opposite edge, as opposed to having a border of off-cells), neighbors that reside on the other edge of the cellmap can&#146;t be accessed by the standard fixed offset, as shown in Figure 17.1. So, in general, we could improve performance by hard-wiring our neighbor-counting for the bit-per-cell cellmap format, but it seems we&#146;d need a lot of conditional code to handle wrapping, and that would slow things back down again.
</P>
<P><A NAME="Fig1"><!-- </A><A HREF="javascript:displayWindow('images/17-01.jpg',408,258 )"> --><IMG SRC="images/17-01.jpg"><BR><!-- </A>
<BR><A HREF="javascript:displayWindow('images/17-01.jpg',408,258)"> --><FONT COLOR="#000077"><B>Figure 17.1</B></FONT></A>&nbsp;&nbsp;<I>Edge-wrapping complications.</I>
</P>
<P>When a problem doesn&#146;t lend itself well to optimization, make it a practice to see if you can change the problem definition to one that allows for greater efficiency. In this case, we&#146;ll change the problem by putting padding bytes around the edge of the cellmap, and duplicating each edge of the cellmap in the padding bytes at the opposite side, as shown in Figure 17.2. That way, a hard-wired neighbor count will find exactly what it should&#151;the opposite edge&#151;without any special code at all.
</P>
<P>But doesn&#146;t that extra copying of the edges take time? Sure, but only a little; we can build it into the cellmap copying function, and then frankly we won&#146;t even notice it. Avoiding tens or hundreds of thousands of calls to <B>cell_state(),</B> on the other hand, will be <I>very</I> noticeable. Listing 17.3 shows the alterations to Listing 17.1 required to implement a hard-wired neighbor-counting function. This is a minor change, in truth, implemented in about half an hour and not making the code significantly larger&#151;but Listing 17.3 is 3.6 times faster than Listing 17.1, as shown in Table 17.1. We&#146;re up to about 10 generations per second on a 486; not where we want to be, but it is a vast improvement.</P>
<P><A NAME="Fig2"><!-- </A><A HREF="javascript:displayWindow('images/17-02.jpg',407,260 )"> --><IMG SRC="images/17-02.jpg"><BR><!-- </A>
<BR><A HREF="javascript:displayWindow('images/17-02.jpg',407,260)"> --><FONT COLOR="#000077"><B>Figure 17.2</B></FONT></A>&nbsp;&nbsp;<I>The &#147;padding cells&#148; solution.</I>
</P>
<P><B>LISTING 17.3 L17-3.CPP</B></P>
<!-- CODE //-->
<PRE>
/* cellmap class definition, constructor, copy_cells(), set_cell(),
clear_cell(), cell_state(), count_neighbors(), and
next_generation() for fast, hard-wired neighbor count approach.
Otherwise, the same as Listing 17.1 */
class cellmap {
private:
unsigned char *cells;
unsigned int width;
unsigned int width_in_bytes;
unsigned int height;
unsigned int length_in_bytes;
public:
cellmap(unsigned int h, unsigned int v);
~cellmap(void);
void copy_cells(cellmap &ampsourcemap);
void set_cell(unsigned int x, unsigned int y);
void clear_cell(unsigned int x, unsigned int y);
int cell_state(int x, int y);
int count_neighbors(int x, int y);
void next_generation(cellmap&amp dest_map);
};
/* cellmap constructor. Pads around cell storage area with 1 extra
byte, used for handling edge wrapping. */
cellmap::cellmap(unsigned int h, unsigned int w)
{
width = w;
width_in_bytes = ((w &#43; 7) / 8) &#43; 2; // pad each side with
// 1 extra byte
height = h;
length_in_bytes = width_in_bytes * (h &#43; 2); // pad top/bottom
// with 1 extra byte
cells = new unsigned char[length_in_bytes]; // cell storage
memset(cells, 0, length_in_bytes); // clear all cells, to start
}
/* Copies one cellmap&#146;s cells to another cellmap. If wrapping is
enabled, copies edge (wrap) bytes into opposite padding bytes in
source first, so that the padding bytes off each edge have the
same values as would be found by wrapping around to the opposite
edge. Both cellmaps are assumed to be the same size. */
void cellmap::copy_cells(cellmap &ampsourcemap)
{
unsigned char *cell_ptr;
int i;
#if WRAP_EDGES
// Copy left and right edges into padding bytes on right and left
cell_ptr = sourcemap.cells &#43; width_in_bytes;
for (i=0; i&ltheight; i&#43;&#43;) {
*cell_ptr = *(cell_ptr &#43; width_in_bytes - 2);
*(cell_ptr &#43; width_in_bytes - 1) = *(cell_ptr &#43; 1);
cell_ptr &#43;= width_in_bytes;
}
// Copy top and bottom edges into padding bytes on bottom and top
memcpy(sourcemap.cells, sourcemap.cells &#43; length_in_bytes -
(width_in_bytes * 2), width_in_bytes);
memcpy(sourcemap.cells &#43; length_in_bytes - width_in_bytes,
sourcemap.cells &#43; width_in_bytes, width_in_bytes);
#endif
// Copy all cells to the destination
memcpy(cells, sourcemap.cells, length_in_bytes);
}
/* Turns cell on. x and y are offset by 1 byte down and to the right, to compensate for the
padding bytes around the cellmap. */
void cellmap::set_cell(unsigned int x, unsigned int y)
{
unsigned char *cell_ptr =
cells &#43; ((y &#43; 1) * width_in_bytes) &#43; ((x / 8) &#43; 1);
*(cell_ptr) |= 0x80 &gt&gt (x &amp 0x07);
}
/* Turns cell off. x and y are offset by 1 byte down and to the right,
to compensate for the padding bytes around the cell map. */
void cellmap::clear_cell(unsigned int x, unsigned int y)
{
unsigned char *cell_ptr =
cells &#43; ((y &#43; 1) * width_in_bytes) &#43; ((x / 8) &#43; 1);
*(cell_ptr) &amp= ~(0x80 &gt&gt (x &amp 0x07));
}
/* Returns cell state (1=on or 0=off). x and y are offset by 1 byte
down and to the right, to
compensate for the padding bytes around
the cell map. */
int cellmap::cell_state(int x, int y)
{
unsigned char *cell_ptr =
cells &#43; ((y &#43; 1) * width_in_bytes) &#43; ((x / 8) &#43; 1);
return (*cell_ptr &amp (0x80 &gt&gt (x &amp 0x07))) ? 1 : 0;
}
/* Counts the number of neighboring on-cells for specified cell. */
int cellmap::count_neighbors(int x, int y)
{
unsigned char *cell_ptr, mask;
unsigned int neighbor_count;
// Point to upper left neighbor
cell_ptr = cells &#43; ((y * width_in_bytes) &#43; ((x &#43; 7) / 8));
mask = 0x80 &gt&gt ((x - 1) &amp 0x07);
// Count upper left neighbor
neighbor_count = (*cell_ptr &amp mask) ? 1 : 0;
// Count left neighbor
if ((*(cell_ptr &#43; width_in_bytes) &amp mask)) neighbor_count&#43;&#43;;
// Count lower left neighbor
if ((*(cell_ptr &#43; (width_in_bytes * 2)) &amp mask)) neighbor_count&#43;&#43;;
// Point to upper neighbor
if ((mask &gt&gt= 1) == 0) {
mask = 0x80;
cell_ptr&#43;&#43;;
}
// Count upper neighbor
if ((*cell_ptr &amp mask)) neighbor_count&#43;&#43;;
// Count lower neighbor
if ((*(cell_ptr &#43; (width_in_bytes * 2)) &amp mask)) neighbor_count&#43;&#43;;
// Point to upper right neighbor
if ((mask &gt&gt= 1) == 0) {
mask = 0x80;
cell_ptr&#43;&#43;;
}
// Count upper right neighbor
if ((*cell_ptr &amp mask)) neighbor_count&#43;&#43;;
// Count right neighbor
if ((*(cell_ptr &#43; width_in_bytes) &amp mask)) neighbor_count&#43;&#43;;
// Count lower right neighbor
if ((*(cell_ptr &#43; (width_in_bytes * 2)) &amp mask)) neighbor_count&#43;&#43;;
return neighbor_count;
}
/* Calculates the next generation of current_map and stores it in
next_map. */
void cellmap::next_generation(cellmap&amp next_map)
{
unsigned int x, y, neighbor_count;
for (y=0; y&ltheight; y&#43;&#43;) {
for (x=0; x&ltwidth; x&#43;&#43;) {
neighbor_count = count_neighbors(x, y);
if (cell_state(x, y) == 1) {
if ((neighbor_count != 2) &amp&amp (neighbor_count != 3)) {
next_map.clear_cell(x, y); // turn it off
draw_pixel(x, y, OFF_COLOR);
}
} else {
if (neighbor_count == 3) {
next_map.set_cell(x, y); // turn it on
draw_pixel(x, y, ON_COLOR);
}
}
}
}
}
</PRE>
<!-- END CODE //-->
<P><BR></P>
<CENTER>
<TABLE BORDER>
<TR>
<TD><A HREF="17-03.html">Previous</A></TD>
<TD><A HREF="index.html">Table of Contents</A></TD>
<TD><A HREF="17-05.html">Next</A></TD>
</TR>
</TABLE>
</CENTER>
<hr width="90%" size="1" noshade>
<div align="center">
<font face="Verdana,sans-serif" size="1">Graphics Programming Black Book &copy; 2001 Michael Abrash</font>
</div>
<!-- all of the reference materials (books) have the footer and subfoot reveresed -->
<!-- reference_subfoot = footer -->
<!-- reference_footer = subfoot -->
<!-- BEGIN SUB FOOTER -->
</BODY>
</HTML>
<!-- END FOOTER -->