147 lines
7.4 KiB
HTML
147 lines
7.4 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 Idea of BSP Trees</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=59//-->
|
|
<!--PAGES=1110-1112//-->
|
|
<!--UNASSIGNED1//-->
|
|
<!--UNASSIGNED2//--></HEAD><BODY LINK=#0000FF ALINK=#000099 VLINK=#0000FF BGCOLOR=#FFFFFF>
|
|
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="59-04.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="59-06.html">Next</A></TD>
|
|
</TR>
|
|
</TABLE>
|
|
</CENTER>
|
|
<P><BR></P>
|
|
<P>Take a few minutes to look over Listing 59.4 and relate it to Listing 59.2. The structure is different, but upon examination it becomes clear that both listings reflect the same underlying model: For each node, visit the left subtree, visit the node, visit the right subtree. And although Listing 59.4 is longer, that’s mostly because I commented it heavily to make sure its workings are understood; there are only 13 lines that actually do anything in Listing 59.4.
|
|
</P>
|
|
<P>Let’s look at it another way. All the code in Listing 59.2 does is say: “Here I am at a node. First I’ll visit the left subtree if there is one, then I’ll visit this node, then I’ll visit the right subtree if there is one. While I’m visiting the left subtree, I’ll just push a marker on a stack that tells me to come back here when the left subtree is done. If, after visiting a node, there are no right children to visit and nothing left on the stack, I’m finished. The code does this at each node—and that’s <I>all</I> it does. That’s all Listing 59.4 does, too, but people tend to get tangled up in pushes and pops and <B>while</B> loops when they use data recursion. When the implementation model changes to one with which they are unfamiliar, they abandon the perfectly good model they used before and try to rederive it in the new context by the seat of their pants.</P>
|
|
<TABLE WIDTH="100%"><TR>
|
|
<TD VALIGN="TOP" ALIGN="LEFT" WIDTH="5%"><IMG SRC="images/59-02i.jpg"><TD VALIGN="TOP" ALIGN="LEFT" WIDTH="95%"><SMALL><I>Here’s a secret when you’re faced with a situation like this: Step back and get a clear picture of what your code has to do. Omit no steps. You should build a model that is so consistent and solid that you can instantly answer any question about how the code should behave in any situation. For example, my interviewees often decide, by trial and error, that there are two distinct types of right children: Right children visited after popping back to visit a node after the left subtree has been visited, and right children visited after descending to a node that has no left child. This makes the traversal code a mass of special cases, each of which has to be detected by the programmer by trying out scenarios. Worse, you can never be sure with this approach that you’ve caught all the special cases.</I></SMALL>
|
|
<TR>
|
|
<TD>
|
|
<TD><SMALL><I>The alternative is to develop and apply a unifying model. There aren’t really two types of right children; the rule is that all right children are visited after their parents are visited, period. The presence or absence of a left child is irrelevant. The possibility that a right child may be reached via different code paths depending on the presence of a left child does not affect the overall model. While this distinction may seem trivial it is in fact crucial, because if you have the model down cold, you can always tell if the implementation is correct by comparing it with the model.</I></SMALL>
|
|
</TABLE>
|
|
<H4 ALIGN="LEFT"><A NAME="Heading10"></A><FONT COLOR="#000077">Measure and Learn</FONT></H4>
|
|
<P>How much difference does all this fuss make, anyway? Listing 59.5 is a sample program that builds a tree, then calls <B>WalkTree</B> () to walk it 1,000 times, and times how long this takes. Using 32-bit Visual C<SMALL>++</SMALL> 1.10 running on Windows NT, with default optimization selected, Listing 59.5 reports that Listing 59.4 is about 20 percent faster than Listing 59.2 on a 486/33, a reasonable return for a little code rearrangement, especially when you consider that the speedup is diluted by calling the <B>Visit()</B> function and by the cache miss that happens on virtually every node access. (Listing 59.5 builds a rather unique tree, one in which every node has exactly two children. Different sorts of trees can and do produce different performance results. Always know what you’re measuring!)</P>
|
|
<P><B>Listing 59.5 L59_5.C</B></P>
|
|
<!-- CODE //-->
|
|
<PRE>
|
|
// Sample program to exercise and time the performance of
|
|
// implementations of WalkTree().
|
|
// Tested with 32-bit Visual C++ 1.10 under Windows NT.
|
|
#include <stdio.h>
|
|
#include <conio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include “tree.h”
|
|
long VisitCount = 0;
|
|
void main(void);
|
|
void BuildTree(NODE *pNode, int RemainingDepth);
|
|
extern void WalkTree(NODE *pRootNode);
|
|
void main()
|
|
{
|
|
NODE RootNode;
|
|
int i;
|
|
long StartTime;
|
|
// Build a sample tree
|
|
BuildTree(&RootNode, 14);
|
|
// Walk the tree 1000 times and see how long it takes
|
|
StartTime = time(NULL);
|
|
for (i=0; i<1000; i++)
|
|
{
|
|
WalkTree(&RootNode);
|
|
}
|
|
printf(“Seconds elapsed: %ld\n”,
|
|
time(NULL) - StartTime);
|
|
getch();
|
|
}
|
|
//
|
|
// Function to add right and left subtrees of the
|
|
// specified depth off the passed-in node.
|
|
//
|
|
void BuildTree(NODE *pNode, int RemainingDepth)
|
|
{
|
|
if (RemainingDepth == 0)
|
|
{
|
|
pNode->pLeftChild = NULL;
|
|
pNode->pRightChild = NULL;
|
|
}
|
|
else
|
|
{
|
|
pNode->pLeftChild = malloc(sizeof(NODE));
|
|
if (pNode->pLeftChild == NULL)
|
|
{
|
|
printf(“Out of memory\n”);
|
|
exit(1);
|
|
}
|
|
pNode->pRightChild = malloc(sizeof(NODE));
|
|
if (pNode->pRightChild == NULL)
|
|
{
|
|
printf(“Out of memory\n”);
|
|
exit(1);
|
|
}
|
|
BuildTree(pNode->pLeftChild, RemainingDepth - 1);
|
|
BuildTree(pNode->pRightChild, RemainingDepth - 1);
|
|
}
|
|
}
|
|
//
|
|
// Node-visiting function so WalkTree() has something to
|
|
// call.
|
|
//
|
|
void Visit(NODE *pNode)
|
|
{
|
|
VisitCount++;
|
|
}
|
|
|
|
</PRE>
|
|
<!-- END CODE //-->
|
|
<P><BR></P>
|
|
<CENTER>
|
|
<TABLE BORDER>
|
|
<TR>
|
|
<TD><A HREF="59-04.html">Previous</A></TD>
|
|
<TD><A HREF="index.html">Table of Contents</A></TD>
|
|
<TD><A HREF="59-06.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 © 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 -->
|
|
|
|
|