////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// scene.cpp
// Project: Nostril (aka Postal)
//
// This module impliments the CScene class
//
// History:
//		01/05/97 MJR	Started.
//
//		01/31/97	JMI	Added cheezy alpha to Render().
//
//		02/06/97	JMI	Added 3D transform and render to Render() for CSprite3's.
//							For now, the world transform is set up on each 3D render,
//							but I will fix that next to be affected once.
//
//		02/07/97	JMI	Now uses m_transWorld member as the world transform.
//
//		02/07/97	JMI	Added the alpha blit for alphable sprite.
//							May not work yet, but Jeff wants it anyways.
//
//		02/07/97	JMI	Now it works, but there's some fudge in presuming the 3D
//							guy's diameter for the collision detection necessary for
//							the alpha X Ray.
//
//		02/07/97	JMI	Now the scene has its own pipeline and CSprite3 does not.
//							B/c of this we no longer needed the m_transWorld, b/c we
//							could incorporate it right into the view.
//							Also, removed m_ppipe from CSprite3 and added m_sRadius
//							so the X Ray logic could do correct collision between
//							the X Rayee and X Rayables. 
//							Also, added a function to initialize/create the pipeline.
//
//		02/10/97	JMI	Changed the number of "Randy Units" represented by our
//							SCREEN_DIAMETER_FOR_3D to 25.5 (was 30.0).
//
//		02/10/97	JMI	Added support for child objects of 3D sprites (CSprite3's).
//
//		02/11/97	JMI	Changed 3D support to recursive inline so indefinite child
//							depths can be supported.
//
//		02/11/97	JMI	Fixed bug in SetupPipeline() that was incorrectly trans-
//							lating the view transform along Z a distance of 
//							dModelRadius.  Now it transforms 0 along Z.
//							Also, changed MODEL_DIAMETER to the actual units used in
//							SoftImage and, consequently, set SCREEN_DIAMETER_FOR_3D
//							to the actual pixel size we want to use.  I imagine we'll
//							have to make both of these larger eventually to fit all
//							the animations.
//
//		02/13/97	JMI	Render() now uses the hood passed to it to get the lighting
//							effects.  Render() now takes a CHood* as a parm.
//
//		02/13/97	JMI	Render3D() now uses textures and fog.
//
//		02/14/97	JMI	Render3D() now passes the CSprite3.m_psphere values to Jeff
//							who returns them and sizes an image.  Currently, for 
//							clipping purposes, the dude is rendered in the image and
//							then rspBlitT'd to the screen (so, if his textures all map
//							to 0, he won't show up b/c 0 is the transparent color pass-
//							ed to rspBlitT).
//							Also, CSprite3 now contains an m_psphere ptr which must be    
//							set to the bounding sphere data for the CSprite3 frame (x, y,  
//							z, w).                                                     
//							Also, added TransformPts() function which will map an array
//							of pts through the optionally provided transform and then  
//							through the scenes transforms.                             
//
//		02/15/97	JMI	Now there are 3 ways a 3D object is dealt with in Render3D:
//							1) Not transformed or rendered -- it is entirely off the
//							destination region.
//							2) Rendered into an intermediate buffer Jeff allocates and
//							then rspBlitT'ed to the destination buffer -- it is 
//							partially off the destination region and, therefore, 
//							requires clippage.
//							3) Rendered directly into the destination region -- it is
//							entirely within the region.
//
//		02/14/97	JMI	Added m_transNoZ which is a transform that is equivalent
//							to the scene's pipeline (m_pipeline), but does not scale
//							Z to the maximum value.  It instead scales Z the same
//							as X and Y.  This is now used by TransformPts().
//		02/16/97	JMI	Now the Render3D should clip to the bounding cube of the
//							points to efficientize the clippage further.
//							Need to clean up Render3D(). I feel like I'm doing a little
//							too much stuff.
//		02/17/97	JMI	Also, increased model diameter (MODEL_DAIMETER) to 30.0
//							since the dude was 10.0 off the origin (very close to
//							exceeding the Z buf height.
//							Also, Render3D() now uses brighter lighting for objects
//							that have the InHighIntensity flag set and also uses
//							the m_sBrightness flag as the center point for the fog.
//							Also, added TransformPtsToRealm() which translate points
//							through the specified transform, the scene's pipeline's 
//							transforms, and finally maps them to Realm 3D coordinates.
//
//		02/17/97	JMI	Now the alpha X Ray effect auto centers on 3D dudes.
//
//		02/19/97	JMI	Now Alpha2d can handle sprites with no alpha.
//
//		02/19/97	JMI	Now does not affect the passed in brightness in Render3D.
//
//		02/21/97	JMI	Increased MODEL_DIAMETER to 50.0 to avoid problem with
//							children rigids exceeding the Z and clip buffers.
//
//		02/23/97	JMI	Had forgotten to Make1() the NoZ* transforms in 
//							SetupPipeline().
//
//		02/24/97	JMI	Had forgotten to specify dest clipping rect for FSPR8 blit
//							of Alpha2d.
//							
//		02/24/97	JMI	Collapsed CAlphaSprite2 into CSprite2 and added 
//							m_sAlphaLevel so a constant alpha level can be specified.
//							Using a constant alpha level is faster than using a mask.
//							Also, made it so the m_type field of the various sprites
//							are set automatically on construction so YOU SHOULD NO
//							LONGER SET THE CSprite*::m_type!
//
//		02/24/97	JMI	Added homogeneous alpha blit but it is untested as far as
//							its use in this module.
//
//		02/25/97	JMI	PATCHED FOR DEMO:  Instead of ASSERTing in Render3D() when
//							a CSprite3 is hosed, it will try to detect and skip it
//							for now.
//
//		02/26/97	JMI	Took out above patch.
//
//		03/05/97	JMI	Added Render() function to render a 3D sprite into a
//							specified background.
//
//		03/21/97	JMI	Added InDeleteOnRender flag and added support for new
//							CSpriteLine2d CSprite derived class.
//
//		03/27/97	JMI	Added SCALE3D parameter to allow quick scaling.
//
//		03/28/97	JMI	Put in VERY TEMP clippage to keep until the rspLine can
//							clip to a specified area (now clips to the destination).
//
//		04/02/97	JMI	Now use slightly faster method to determine the blit 
//							position for the sprite rendered into the clip buffer.
//							Changed MODEL_DIAMETER to 28.444444445 (from 50.0) so
//							we can divide it by 2 many times before getting errors
//							due to using integer division.  The idea of that 
//							particular number is to get a pixel size of the 3d world
//							of 128 which, of course, is a power of 2.
//
//		04/02/97	JMI	Put MODEL_DIAMETER back to 50.0 b/c rocket man's rocket
//							is too far away from him when the animation starts.
//
//		04/02/97	JMI	Put MODEL_DIAMETER back to 30.0.  Now Render3D() checks
//							to make sure the item fits into the Z buffer and, instead
//							of ASSERTing like it used to, it just TRACEs and does not
//							do the render.
//
//		04/02/97	JMI	In last fix, I used ||'s to separate newly combined ASSERTs
//							into an if ().  It should've been &&'s.  Fixed.
//
//		04/02/97	JMI	Removed old, commented out ASSERT in Render3D().  Also,
//							made Render3D() aware of InHidden flag.  Although the
//							loop in Render() takes care of the top-level objects'
//							InHidden flag, Render3D() was not, so child items that
//							were hidden would have been drawn, I think.  Now they
//							definitely won't.
//
//		04/14/97	JMI	Added use of TEMP flag m_bDontBlit to allow us to speed up
//							the Snap() call by not blitting.
//
//		04/29/97	JMI	Made the child concept part of the base class sprite (was
//							part of the 3D sprite (CSprite3)) and added a parent 
//							pointer.
//							Now that we have the parent sprite pointer, I was able to
//							make sure the sprites remove themselves from their parents
//							and remove their children when they are destroyed.
//							Also, added member functions to CScene to handle rendering
//							2D sprites, 3D sprites, and 2D lines.
//
//		05/02/97	JMI	If an alpha was 100 or over for a general alpha blit, it
//							was using the regular blit (opaque) instead of an rspBlitT
//							(transparent).
//
//		05/02/97	JMI	Added CSpriteCylinder3d, a cylinder primitive.
//
//		05/13/97	JMI	Now Render3D() tries to maximize the size of an object it
//							can render by centering top-level 3D objects in the 3D
//							screen based on their bounding sphere.
//							This change required additional fields in CSprite3.
//
//		05/13/97	JMI	Casted instances of warning C4018 (signed/unsigned mismatch)
//							to make MSVC 4.1(Alpha) happy (these seem to fall under
//							Warning Level 3 in 4.1(Alpha) but not in 4.2(Intel)).
//
//		05/15/97	JMI	I had thought that the max alpha (i.e., opaque) level was 
//							100 and was, therefore, checking to see if the level was
//							less than 100 before calling the alpha blit.  As it turns
//							out, it is 255.  Fixed.
//
//		05/15/97	JMI	Now obeys g_GameSettings.m_sAlphaBlend and m_sXRayEffect.
//
//		05/16/97	JMI	Code in Render3D() is set up so that no more than 1 level
//							deep of child will currently work for CSprite3.  The 
//							solution would be to add an m_transCumm to CSprite3 that
//							could accumulate the combined transforms as CSprite3's are
//							Render3D()'d.
//
//		05/19/97	JMI	Now, by pointing mysprite.m_pszText at some text, you can
//							display text at the sprites m_sX2, m_sY2.
//
//		05/20/97	JMI	Now DrawLine2D() uses rspLine() with a clip rect.
//
//		05/20/97	JMI	Text (CSprite::m_pszText) drawn via ms_print is now clipped
//							to a column that is guaranteed to be within the clip rect.
//
//		05/22/97	JMI	Now obeys g_GameSettings.m_s3dFog.
//
//		05/22/97	JMI	Now the collision detection for the alpha affect is done
//							using the width and height of the xray mask instead of the
//							dude's xrayee's width and height.
//
//		06/05/97	JMI	m_pipeline.m_pZB->Clear() was being called for top-level
//							3D items even if they were OFFSCREEN.  WHoopsee.  Fixed.
//
//		06/05/97	JMI	In Render3D(), the indirect render, when copying from the
//							temp (indirect) buf to the composite buffer, did not take
//							into account the fact that the center of the render may
//							not have been the center of the current renderee's bounding
//							sphere (which happens in the case of a child object (they
//							are blit relative to the center of the top-level parent's
//							bounding sphere) ).
//
//		06/10/97 JRD	Returned lighting to non-adjusting because the spheres weren't
//							accurate enough.  Luckily, the fix in the 3d export made the
//							non-adaptive lighting MUCH better.
//
//		06/10/97 JRD	Updated to centered based adaptive lighting - a good compromise!
//
//		06/14/97 MJR	Added g_bSceneDontBlit to replace the flag in gamesettings
//							that was being misused for that purpose.
//
//		06/16/97 JRD	Shrunk Z-buffer from 30 to 16 Randy Units
//
//		06/25/97	JMI	Added SetXRayAll() and m_bXRayAll which enable or disable 
//							xraying of all 'alpha' _and_ 'opaque' layers.
//							Also, changed InXrayable to InAlpha and added InOpaque to
//							differentiate between these two types which are now, at 
//							times, both xrayable.
//
//		06/26/97	JMI	Now uses phood->Map3Dto2D() (was using the global 
//							version defined in reality.h which is no more).
//
//		06/29/97 MJR	Converted vector of layers into simple array of layers
//							(motivated by trying to get it to work on the mac).
//
//		07/01/97	JMI	Added m_transScene2Realm which is used in 
//							TransformPtsToRealm() to convert from the scene to the 
//							realm.  This value is set via the SetupPipeline() call.
//							Currently, the code exists but is commented out b/c it
//							does not seem to bridge the gap between scene and realm
//							coords correctly.
//
//		07/10/97	JMI	Now Render2D() can uses an alpha mask with an alpha level
//							via the new rspGeneralAlphaBlit() which accepts a level.
//
//		07/17/97	JRD	Moved the pipeline tranform so it would only be called 
//							if a 3d object was on screen.  (watch for side effects)
//
//		07/20/97	JMI	Added some ASSERTs on m_sAlphaLevel in Render2D().
//
//		07/03/97 JRD	Altered the load to update the pipeline based on the 
//							3d scale for that level.
//
//		08/04/97	JMI	Now Render3D() verifies that a sprite has a valid m_pthing
//							before using it to display info on a sprite that could not
//							be drawn.
//
//		08/07/97	JMI	Moved global single instance of gdSCALE3D defined in 
//							scene.cpp into scene as an instantiable member, m_dScale3d.
//							Also, now SetupPipeline() does what UpdatePipeline() used
//							to (got rid of UpdatePipeline()).
//
//		08/18/97	JMI	Added ASSERTs to make sure inserted sprite's layer number
//							is greater than or equal to 0.
//
//		08/18/97	JMI	If the InDeleteOnRender flag was specified in a sprite,
//							it was removed from the scene list, deleted, and then its
//							flags were checked for the XRay bit which it usually had
//							because it was memset'ed to 0xdd on deletion.  This caused
//							it to be the XRayee.  Fixed.
//							Also, made the Render() call used for blitting things into
//							the background call the basic Render().
//							Also, increased MODEL_DIAMETER to 18.0 (was 16.0).
//
//		08/18/97	JMI	Changed Render() used by dead things to update themselves
//							into the background to be called DeadRender3D() to better
//							describe what it does and differentiate it from the other
//							more generic Render()ers.  Also, added two parameters so
//							it could call itself with 3D children.
//
//		08/23/97	JMI	Now offsets child light offset by the parent's.
//
//		10/03/99	JMI	Changed Render3D() to take a light scheme instead of a hood
//							to make it more general.
//
////////////////////////////////////////////////////////////////////////////////
#define SCENE_CPP

#include "RSPiX.h"
#include "scene.h"
#include "game.h"
#include "alphablitforpostal.h"
#include "reality.h"


////////////////////////////////////////////////////////////////////////////////
// Macros/types/etc.
////////////////////////////////////////////////////////////////////////////////

#define SCREEN2MODEL_RATIO			(4.5 * m_dScale3d)

// This represents the number of "Randy units" represented by 
// SCREEN_DIAMETER_FOR_3D.
#define MODEL_DIAMETER				20.0	// In "Randy Units".
#define MODEL_ZSPAN					30.0  // must NEVER be exceeded or lighting is hosed

// Diameter in pixels for screen for 3D objects.
// Should be largest to hold largest 3D object.
#define SCREEN_DIAMETER_FOR_3D	(MODEL_DIAMETER * SCREEN2MODEL_RATIO)	

int16_t	gsGlobalBrightnessPerLightAttribute = 5;  
/* short gsGlobalLightingAdjustment = 128; /* neutral center */
// NOTE: This max value completely depends on the actual lighting effect curve:
// it should be set near the bright spot of the curve.
int16_t gsGlobalLightingAdjustment = 128; // NEUTRAL BABY!
//160 /* Max White */ - 6 * gsGlobalBrightnessPerLightAttribute;

// Used to control whether or not scene will actually blit.  When set to true,
// scene will execute quite a bit faster, except you won't see anything. :)
bool	g_bSceneDontBlit;

// Font stuff.
#define FONT_CELL_HEIGHT			15
#define FONT_FORE_COLOR				250
#define FONT_BACK_COLOR				0
#define FONT_SHADOW_COLOR			0

const double	c_dMaxScale		= 10.0;
const double	c_dMinScale		= 0.2;

////////////////////////////////////////////////////////////////////////////////
// Variables/data
////////////////////////////////////////////////////////////////////////////////
// Used to draw within the scene.
RPrint	CScene::ms_print;

////////////////////////////////////////////////////////////////////////////////
// Default (and only) constructor
////////////////////////////////////////////////////////////////////////////////
CScene::CScene()
	{
	// No array of layers yet
	m_pLayers = 0;
	m_sNumLayers = 0;
	
	// This can fail, so I had wanted to put it in SetupPipeline(), but there's
	// not much difference really since that function does not return an error
	// either.  Since this only needs to be done once, I decided to put it
	// here....let me know if you think it sucks.

	// Note that this function initializes m_dScale3d and other members.
	SetupPipeline();

	// If the static print has no font . . .
	if (ms_print.GetFont() == NULL)
		{
		ms_print.SetFont(FONT_CELL_HEIGHT, &g_fontBig);
		ms_print.SetColor(FONT_FORE_COLOR, FONT_BACK_COLOR, FONT_SHADOW_COLOR);
		ms_print.SetWordWrap(FALSE);
		}

	// Default to something (anything)
	m_bXRayAll = false;
	}


////////////////////////////////////////////////////////////////////////////////
// Destructor
////////////////////////////////////////////////////////////////////////////////
CScene::~CScene()
	{
	Clear();
	}


////////////////////////////////////////////////////////////////////////////////
// Clear the scene (clears out all layers and sprites)
////////////////////////////////////////////////////////////////////////////////
void CScene::Clear(void)
	{
	// Clear all sprites from all layers
	RemoveAllSprites();

	// Delete the layers
 	delete []m_pLayers;
 	m_pLayers = 0;
 	m_sNumLayers = 0;
	}


////////////////////////////////////////////////////////////////////////////////
// Remove all sprites (from all layers)
///////////////////////////////////////////////////////////////////////////////
void CScene::RemoveAllSprites(void)
	{
	// Clear sprites from each layer
	for (int16_t s = 0; s < m_sNumLayers; s++)
		RemoveSprites(s);
	}


////////////////////////////////////////////////////////////////////////////////
// Remove sprites from specified layer
////////////////////////////////////////////////////////////////////////////////
void CScene::RemoveSprites(
	int16_t sLayer)											// In:  Layer number (0 to n-1)
	{
	// Get pointer to layer
	Layer* pLayer = &(m_pLayers[sLayer]);

	// Go through layer's collection of sprites
	for (msetSprites::iterator i = pLayer->m_sprites.begin(); i != pLayer->m_sprites.end(); i++)
		{
		// Get pointer to sprite
		CSprite* pSprite = *i;

		// Clear inserted flag
		pSprite->m_sPrivFlags &= ~CSprite::PrivInserted;

		// Delete sprite if delete-on-clear flag is set
		if (pSprite->m_sInFlags & CSprite::InDeleteOnClear)
			delete pSprite;
		}

	// Clear the sprite container
#if _MSC_VER >= 1020
	pLayer->m_sprites.clear();
#else
	pLayer->m_sprites.erase(pLayer->m_sprites.begin(), pLayer->m_sprites.end());
#endif
	}


////////////////////////////////////////////////////////////////////////////////
// Set number of layers (destroys any existing layers!)
////////////////////////////////////////////////////////////////////////////////
void CScene::SetLayers(
	int16_t sNumLayers)										// In:  Number of layers
	{
	// Validate parameters
	ASSERT(sNumLayers > 0);

	// Clear all layers (and sprites)
	Clear();

	// Create array of layers
	m_pLayers = new Layer[sNumLayers];
	m_sNumLayers = sNumLayers;
	}


////////////////////////////////////////////////////////////////////////////////
// Update existing sprite or add new sprite
////////////////////////////////////////////////////////////////////////////////
void CScene::UpdateSprite(
	CSprite* pSprite)										// In:  Sprite to add
	{
	ASSERT(pSprite != NULL);

	// Check if it's already in the scene
	if (pSprite->m_sPrivFlags & CSprite::PrivInserted)
		{
		// Check if layer has changed
		if (pSprite->m_sLayer != pSprite->m_sSavedLayer)
			{
			// Erase from old layer
			m_pLayers[pSprite->m_sSavedLayer].m_sprites.erase(pSprite->m_iter);

			// Add to specified layer and save iterator for fast access later on
			ASSERT(pSprite->m_sLayer < m_sNumLayers);
			ASSERT(pSprite->m_sLayer >= 0);
			pSprite->m_iter = m_pLayers[pSprite->m_sLayer].m_sprites.insert(pSprite);
			pSprite->m_sSavedLayer = pSprite->m_sLayer;
			}

		// Check if priority has changed
		if (pSprite->m_sPriority != pSprite->m_sSavedPriority)
			{
			// For now, just erase it and then re-insert it.  There is definitely
			// a faster way to do this, but since we'll likely be revamping the
			// entire draw-order logic, I'll leave it like this.
			m_pLayers[pSprite->m_sSavedLayer].m_sprites.erase(pSprite->m_iter);
			pSprite->m_iter = m_pLayers[pSprite->m_sLayer].m_sprites.insert(pSprite);
			pSprite->m_sSavedPriority = pSprite->m_sPriority;
			}
		}
	else
		{
		// Add to specified layer and save iterator for fast access later on
		ASSERT(pSprite->m_sLayer < m_sNumLayers);
		ASSERT(pSprite->m_sLayer >= 0);
		pSprite->m_iter = m_pLayers[pSprite->m_sLayer].m_sprites.insert(pSprite);
 
		// Save layer and priority so we can detect changes to them
		pSprite->m_sSavedLayer = pSprite->m_sLayer;
		pSprite->m_sSavedPriority = pSprite->m_sPriority;

		// Set inserted flag
		pSprite->m_sPrivFlags |= CSprite::PrivInserted;
		}
	}


////////////////////////////////////////////////////////////////////////////////
// Remove sprite (safe to call even if sprite isn't in scene).
////////////////////////////////////////////////////////////////////////////////
void CScene::RemoveSprite(
	CSprite* pSprite)										// In:  Sprite to remove
	{
	ASSERT(pSprite != NULL);

	// Make sure sprite is currently inserted -- if not, skip it.  This is
	// not treated as an error because there may be situations where the
	// caller doesn't know the sprite has already been removed from the layer,
	// such as when someone else has called Clear() or some similar function.
	if(pSprite->m_sPrivFlags & CSprite::PrivInserted)
		{
		// Erase sprite from layer.  Knowing the iterator makes this very fast.
		ASSERT(pSprite->m_sSavedLayer < m_sNumLayers);
		m_pLayers[pSprite->m_sSavedLayer].m_sprites.erase(pSprite->m_iter);

		// Clear inserted flag
		pSprite->m_sPrivFlags &= ~CSprite::PrivInserted;
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Render a 3D sprite.
//
// See the Render3D(..., RAlpha*, ...) for details.
//
////////////////////////////////////////////////////////////////////////////////
void									// Returns nothing.
CScene::Render3D(		
	RImage*		pimDst,			// Destination image.
	int16_t			sDstX,			// Destination 2D x coord.
	int16_t			sDstY,			// Destination 2D y coord.
	CSprite3*	ps3Cur,			// 3D sprite to render.
	CHood*		phood,			// Da hood, homey.
	RRect*		prcDstClip)		// Dst clip rect.
	{
	RAlpha* palphaLight;
	// If high intensity indicated . . .
	if (ps3Cur->m_sInFlags & CSprite::InHighIntensity)
		{
		// Use spot lighting.
		palphaLight	= phood->m_pltSpot;
		}
	else
		{
		// Use ambient lighting.
		palphaLight	= phood->m_pltAmbient;
		}

	Render3D(					// Returns happy thoughts.
		pimDst,					// Destination image.
		sDstX,					// Destination 2D x coord.
		sDstY,					// Destination 2D y coord.
		ps3Cur,					// Tree of 3D sprites to render.
		palphaLight,			// Da hood, homey.
		prcDstClip);			// Dst clip rect.
	}

////////////////////////////////////////////////////////////////////////////////
// Render a 3D sprite.
//
// This function will, for a top level 3D sprite:
// 0) Clear the Z buffer.
//	1) Transform the bounding sphere to screen coordinates.
// 2) Ignore the sprite if the bounding sphere indicates the sprite is entirely
// offscreen.
// 3) Center the Render() on the bounding sphere to get maximum Z buffer room
// on X/Z plane.
// 4) Render into a clip image if the bounding sphere indicates the sprite is
// partially offscreen.  The clip image then gets rspBlitT'd to the composite
// buffer with clipping.
//
// For a non-top level 3D sprite:
// 1) Combine all parent transforms with this sprite's transform.
// 2) Transform the bounding sphere to screen coordinates.
// 3) Center the Render() on the top-level parent's bounding sphere so it matches 
// the parents' position(s).
// 4) Render into a clip image if the bounding sphere indicates the sprite is
// partially offscreen.  The clip image then gets rspBlitT'd to the composite
// buffer with clipping.
//
////////////////////////////////////////////////////////////////////////////////
void									// Returns nothing.
CScene::Render3D(		
	RImage*		pimDst,			// Destination image.
	int16_t			sDstX,			// Destination 2D x coord.
	int16_t			sDstY,			// Destination 2D y coord.
	CSprite3*	ps3Cur,			// 3D sprite to render.
	RAlpha*		plight,			// Light to render with.
	RRect*		prcDstClip)		// Dst clip rect.
	{
	// This transform is the product of the parent and the
	// child's transform used to determine the position of
	// a child object absolutely.
	RTransform	transChildAbs;
	RTransform*	ptransRender;	// The transform used.

	int16_t		sCurX;
	int16_t		sCurY;
	RP3d		pt3dSrcCenter, pt3dSrcRadius;		// Center and point on outside of bounding
															// sphere in "Randy" coords.
	int16_t		sClipLeft;			// Amount clipped off left edge of dest region.
	int16_t		sClipTop;			// Amount clipped off top edge of dest region.
	int16_t		sClipRight;			// Amount clipped off right edge of dest region.
	int16_t		sClipBottom;		// Amount clipped off bottom edge of dest region.
	int16_t		sRadius;				// Radius of bounding sphere in pixels.
	int16_t		sDiameter;			// Diameter of bounding sphere in pixels.
	int16_t		sCenterX;			// Center of bounding sphere as screen coord.
	int16_t		sCenterY;			// Center of bounding sphere as screen coord.
	RImage*	pimRender;			// Image to render into.
	bool		bIndirectRender;	// Rendering to intermediate buffer.
	int16_t		sIndirectRenderX;	// Render coord when rendering to clip buffer.
	int16_t		sIndirectRenderY;	// Render coord when rendering to clip buffer.
	int16_t		sDirectRenderZ;	// Render Coord in either case
	int16_t		sDirectRenderX;	// Render coord when rendering to pimDst buffer.
	int16_t		sDirectRenderY;	// Render coord when rendering to pimDst buffer.
	int16_t		sRenderX;			// Render coord passed to Render().
	int16_t		sRenderY;			// Render coord passed to Render().
	int16_t		sBlitX;				// Blit pos for 3D model.
	int16_t		sBlitY;				// Biit pos for 3D model.
	int16_t		sRenderOffX;		// Offset to Render().
	int16_t		sRenderOffY;		// Offset to Render().

	ASSERT(ps3Cur->m_psop != NULL);
	ASSERT(ps3Cur->m_ptex != NULL);
	ASSERT(ps3Cur->m_pmesh != NULL);
	ASSERT(ps3Cur->m_ptrans != NULL);
	ASSERT(ps3Cur->m_psphere != NULL);

	// If there's a parent . . .
	if (ps3Cur->m_psprParent != NULL)
		{
		// NOTE: This does NOT work for more than 1 level of child depth.
		// To make that work, we must put a transform in the CSprite3.
		// Apply child and parent to transChildAbs.
		transChildAbs.Mul( ((CSprite3*)ps3Cur->m_psprParent)->m_ptrans->T, ps3Cur->m_ptrans->T);

		// Use transChildAbs.
		ptransRender	= &transChildAbs;
		}
	else
		{
		// Use current top-level matrix.
		ptransRender	= ps3Cur->m_ptrans;
		}

	// Here's the deal with m_pipeline. :
	// X == Origin.
	// @ == Center of sphere of points.
	//	 ________________m_pimClipBuf________________
	//	|			(m_sX, m_sY)	(m_sX + m_sW, m_sY)	|
	//	|					 ___________						|
	//	|					|    ooo    |						|
	//	|					|   o\o/o   |						|
	//	|					|    o*o    |						|
	//	|					|     o     |						|
	//	|					|ooooo@ooooo|						|
	//	|					|     o     |						|
	//	|					|    o o    |						|
	//	|					|   o   o   |						|
	//	|					|_oo__X__oo_|						|
	//	|	(m_sX, m_sY + m_sH)	(m_sX + m_sW, m_sY + m_sH)
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|															|
	//	|____________________________________________|

	// Get the bounding info.
	// Setup src pts.

	// Get current sphere.
	pt3dSrcCenter	= *(ps3Cur->m_psphere);
	pt3dSrcRadius.x	= pt3dSrcCenter.x + pt3dSrcCenter.w;
	pt3dSrcRadius.y	= pt3dSrcCenter.y + pt3dSrcCenter.w;
	pt3dSrcRadius.z	= pt3dSrcCenter.z + pt3dSrcCenter.w;
	pt3dSrcRadius.w	= 1;
	pt3dSrcCenter.w	= 1;

	// Let the pipeline know of the bounding sphere.
	m_pipeline.BoundingSphereToScreen(pt3dSrcCenter, pt3dSrcRadius, *ptransRender);

	// Check screen location:
	// Get radius of sphere of points (SphOP).
	sDiameter	= MAX(m_pipeline.m_sW, m_pipeline.m_sH);
	sRadius		= sDiameter / 2;

// ****TEMP****
if (g_bSceneDontBlit == false)
{
// ****END TEMP****

	// Determine destination of hotspot on screen relative to parent, if any.
	sCurX		= ps3Cur->m_sX2 + sDstX;
	sCurY		= ps3Cur->m_sY2 + sDstY;

	// Determine center of sphere of points relative to origin.
	int16_t	sOrgRelCenX	= (m_pipeline.m_sCenX - (int16_t)(SCREEN_DIAMETER_FOR_3D / 2));
	int16_t	sOrgRelCenY	= (m_pipeline.m_sCenY - (int16_t)(SCREEN_DIAMETER_FOR_3D / 2));
	// Determine center of sphere of points on screen.
	sCenterX		= sCurX + sOrgRelCenX;
	sCenterY		= sCurY + sOrgRelCenY;
	// Determine amount that is off of the clip rect.
	sClipLeft	= prcDstClip->sX - (sCenterX - sRadius);
	sClipTop		= prcDstClip->sY - (sCenterY - sRadius);
	sClipRight	= (sCenterX + sRadius) - (prcDstClip->sX + prcDstClip->sW);
	sClipBottom	= (sCenterY + sRadius) - (prcDstClip->sY + prcDstClip->sH);
	// If on screen at all . . .
	if (sClipLeft < sDiameter && sClipTop < sDiameter && sClipRight < sDiameter && sClipBottom < sDiameter)
		{
		// Transform pts through *ptransRender, view, and finally screen transforms.
		m_pipeline.Transform(
			ps3Cur->m_psop,		// Sea of 3D points to form
										// mesh around.
			*ptransRender);		// The transformation.

		// If there's no parent . . .
		if (ps3Cur->m_psprParent == NULL)
			{
			// Clear Z buffer for new 3D tree.
			m_pipeline.m_pZB->Clear();
			}

		// Determine destination for sprite on screen.
		// Get upper left hand corner by subtracting the radius from the screen center of points.
		sBlitX	= sCenterX - sRadius;
		sBlitY	= sCenterY - sRadius;

		//////////////////////////////////////////////////////////////////////////////
		// GLOBAL LIGHTING FACTOR: (ADAPTIVE)
		//////////////////////////////////////////////////////////////////////////////
		// Here we do dynamic light adjustment based on the current sphere.
		// Stage two aligns front of sphere:
		// *** RESTORING DO NON-ADJUSTMENT! ***
		// re-attempting center adjustment level
		int16_t sLightOffset = 0;	// Must be initialized here (the other assignments are +=).
		//	- (m_pipeline.m_sCenZ + m_pipeline.m_sZ); 

		// If no parent . . .
		if (ps3Cur->m_psprParent == NULL)
			{
			// For indirect Render:  
			// Render so center of sphere is at center of clip image.
			sIndirectRenderX	= 0;
			sIndirectRenderY	= 0;
			// For direct Render:
			// Determine destination for render on screen.
			// The position you supply to Render() is one 3D screen radius
			// up and to the left.
			sDirectRenderX		= sCenterX - (int16_t)(SCREEN_DIAMETER_FOR_3D / 2);
			sDirectRenderY		= sCenterY - (int16_t)(SCREEN_DIAMETER_FOR_3D / 2);
			sDirectRenderZ		= m_pipeline.m_sCenZ; // don't need to know
			// Offset by the center relative to the origin.
			sRenderOffX			= -sOrgRelCenX;
			sRenderOffY			= -sOrgRelCenY;
			}
		else
			{
			// MUST BE 3D PARENT!
			ASSERT(ps3Cur->m_psprParent->m_type == CSprite::Standard3d);
			CSprite3*	ps3Parent	= (CSprite3*)(ps3Cur->m_psprParent);

			// Use the position the parent rendered at.
			sDirectRenderX		= ps3Parent->m_sDirectRenderX;
			sDirectRenderY		= ps3Parent->m_sDirectRenderY;
			sDirectRenderZ		= ps3Parent->m_sDirectRenderZ;

			sIndirectRenderX	= ps3Parent->m_sIndirectRenderX;
			sIndirectRenderY	= ps3Parent->m_sIndirectRenderY;

			// Offset by the amount the parent was offset.
			sRenderOffX			= ps3Parent->m_sRenderOffX;
			sRenderOffY			= ps3Parent->m_sRenderOffY;

			// Offset lighting by parent's offset.
			sLightOffset		+= ps3Parent->m_sBrightness;
			}

		// If this sprite has children . . .
		if (ps3Cur->m_psprHeadChild != NULL)
			{
			// Store Render() positions for children to use.
			ps3Cur->m_sDirectRenderX	= sDirectRenderX;
			ps3Cur->m_sDirectRenderY	= sDirectRenderY;
			ps3Cur->m_sDirectRenderZ	= sDirectRenderZ;
			ps3Cur->m_sIndirectRenderX	= sIndirectRenderX;
			ps3Cur->m_sIndirectRenderY	= sIndirectRenderY;
			// Store Render() offsets for children to use.
			ps3Cur->m_sRenderOffX		= sRenderOffX;
			ps3Cur->m_sRenderOffY		= sRenderOffY;
			}

		// If only partially on screen . . .
		if (sClipLeft > 0 || sClipTop > 0 || sClipRight > 0 || sClipBottom > 0)
			{
			ASSERT(m_pipeline.m_pimClipBuf != NULL);
			// Use clip image.
			pimRender			= m_pipeline.m_pimClipBuf;
			// Remember to blit to composite.
			bIndirectRender	= true;
			// Use appropriate position.
			sRenderX				= sIndirectRenderX;
			sRenderY				= sIndirectRenderY;
			}
		else
			{
			// Use composite buffer.
			pimRender			= pimDst;
			// Remember we don't need to blit to composite.
			bIndirectRender	= false;
			// Use appropriate position.
			sRenderX				= sDirectRenderX;
			sRenderY				= sDirectRenderY;
			}

		// use either the current z center or parent to adjust:
		// Note that we've already offset this by the parent and that is why
		// we do a += (see above (search for sLightOffset) ).
		sLightOffset += ps3Cur->m_sBrightness + gsGlobalLightingAdjustment - sDirectRenderZ;

		// Make sure we don't overrun the Z buffer . . .
		// Note that m_pipeline.m_sCen? are in image coords (i.e., (0, 0) is
		// the upper, left hand corner and (SCREEN_DIAMETER_FOR_3D, 
		// SCREEN_DIAMETER_FOR_3D) is the lower, right hand corner).
		if (	m_pipeline.m_sCenX - sRadius >= -sRenderOffX
			&&	m_pipeline.m_sCenX + sRadius < SCREEN_DIAMETER_FOR_3D - sRenderOffX
			&&	m_pipeline.m_sCenY - sRadius >= -sRenderOffY
			&&	m_pipeline.m_sCenY + sRadius < SCREEN_DIAMETER_FOR_3D - sRenderOffY)
			{
			// If fog enabled . . .
			if (g_GameSettings.m_s3dFog != FALSE)
				{
				// Render with textures and fog.
				m_pipeline.Render(
					pimRender,						// Dst image.
					sRenderX,						// 2D Dst coord.
					sRenderY,						// 2D Dst coord.
					ps3Cur->m_pmesh,				// Src mesh.
					m_pipeline.m_pZB,				// Z buffer (use its own for now).
					ps3Cur->m_ptex,				// Textures.
					sLightOffset,					// Fog offset.  Fogool?
					plight,							// Ambient lighting schtuff.
					sRenderOffX,					// Offset render/z-buffer to center of sphere of points.
					sRenderOffY);					// Offset render/z-buffer to center of sphere of points.
				}
			else
				{
				// Render with textures, no fog.
				m_pipeline.Render(
					pimRender,						// Dst image.
					sRenderX,						// 2D Dst coord.
					sRenderY,						// 2D Dst coord.
					ps3Cur->m_pmesh,				// Src mesh.
					m_pipeline.m_pZB,				// Z buffer (use its own for now).
					ps3Cur->m_ptex,				// Textures.
					sRenderOffX,					// Offset render/z-buffer to center of sphere of points.
					sRenderOffY);					// Offset render/z-buffer to center of sphere of points.
				}

			// If we rendered into an intermediate buffer b/c of clipping . . .
			if (bIndirectRender == true)
				{
				// Get it into destination.
				rspBlitT(
					0,																// Transparent index.
					pimRender,													// Src.
					pimDst,														// Dst.
					m_pipeline.m_sCenX - sRadius + sRenderOffX,	// Src.
					m_pipeline.m_sCenY - sRadius + sRenderOffY,	// Src.
					sBlitX,														// Dst.
					sBlitY,														// Dst.
					m_pipeline.m_sW,											// Both.
					m_pipeline.m_sH,											// Both.
					prcDstClip,													// Dst.
					NULL);														// Src.

				m_pipeline.ClearClipBuffer();
				}
			else
				{
				// Already in pimDst.
				}
#if 0	// Set to 1 to see origin target, center of points X, 
		// rect for bounding cube, and rect for clip image.
			// Draw bounds.
			rspRect(
				1, 
				RSP_WHITE_INDEX,
				pimDst,
				sCenterX - sRadius,
				sCenterY - sRadius,
				sDiameter, 
				sDiameter,
				prcDstClip);

			// Draw target at hotspot on screen.
			#define HOTSPOT_TARGET_LENGTH		5
			rspLine(
				RSP_WHITE_INDEX, 
				pimDst, 
				sCurX - HOTSPOT_TARGET_LENGTH / 2, 
				sCurY, 
				sCurX + HOTSPOT_TARGET_LENGTH / 2 + 1, 
				sCurY);
//				prcDstClip);
			rspLine(RSP_WHITE_INDEX, 
				pimDst, 
				sCurX, 
				sCurY - HOTSPOT_TARGET_LENGTH / 2, 
				sCurX, 
				sCurY + HOTSPOT_TARGET_LENGTH / 2 + 1);
//				prcDstClip);

			// Draw origin with X.
			#define ORIGIN_TARGET_LENGTH	6
			rspLine(
				251/*Yellow*/, 
				pimDst, 
				sCenterX - ORIGIN_TARGET_LENGTH / 2, 
				sCenterY - ORIGIN_TARGET_LENGTH / 2,
				sCenterX + ORIGIN_TARGET_LENGTH / 2 + 1, 
				sCenterY + ORIGIN_TARGET_LENGTH / 2 + 1);
//				prcDstClip);
			rspLine(
				251/*Yellow*/, 
				pimDst, 
				sCenterX + ORIGIN_TARGET_LENGTH / 2, 
				sCenterY - ORIGIN_TARGET_LENGTH / 2,
				sCenterX - ORIGIN_TARGET_LENGTH / 2 - 1, 
				sCenterY + ORIGIN_TARGET_LENGTH / 2 + 1);
//				prcDstClip);

			// Draw image.
			rspRect(1, RSP_WHITE_INDEX, pimDst, 
				sCenterX - (short)(SCREEN_DIAMETER_FOR_3D / 2), 
				sCenterY - (short)(SCREEN_DIAMETER_FOR_3D / 2), 
				m_pipeline.m_pimClipBuf->m_sWidth, 
				m_pipeline.m_pimClipBuf->m_sHeight,
				prcDstClip);

			// Note whether clipping.
			if (bIndirectRender == true)
				{
				rspLine(251/*Yellow*/, pimDst, sCenterX, sCenterY, 1, 1);
				}
#endif
			}
		else
			{
			char	szMsg[1024];
			if (ps3Cur->m_pthing)
				{
				sprintf(szMsg, "Render3D(): %s with ID %d",
					CThing::ms_aClassInfo[ps3Cur->m_pthing->GetClassID()].pszClassName,
					ps3Cur->m_pthing->GetInstanceID());
				}
			else
				{
				strcpy(szMsg, "Render3D(): Unknown class with unknown ID");
				}
			
			// If there's a parent . . .
			CSprite*	psprParent	= ps3Cur->m_psprParent;
			if (psprParent != NULL)
				{
				if (psprParent->m_pthing)
					{
					sprintf(szMsg, "%s, child of %s with ID %d,",
						szMsg,
						CThing::ms_aClassInfo[psprParent->m_pthing->GetClassID()].pszClassName,
						psprParent->m_pthing->GetInstanceID());
					}
				}

			strcat(szMsg, " would exceed the Z buffer, if rendered.  Not gonna do it.\n");
			TRACE(szMsg);
			}
		}

// ****TEMP****
}
// ****END TEMP****

	// Store this location (it is used by Render() to do collision circle for XRay).
	ps3Cur->m_sCenX	= ps3Cur->m_sX2 + m_pipeline.m_sCenX - (int16_t)(SCREEN_DIAMETER_FOR_3D / 2);
	ps3Cur->m_sCenY	= ps3Cur->m_sY2 + m_pipeline.m_sCenY - (int16_t)(SCREEN_DIAMETER_FOR_3D / 2);
	ps3Cur->m_sRadius	= sRadius;
	}

////////////////////////////////////////////////////////////////////////////////
// Line function until we have one that can clip to other than the dest image.
////////////////////////////////////////////////////////////////////////////////
inline
void DrawLine2d(			// Returns nothing.
	U8			u8Color,		// Color to draw line with.
	RImage*	pimDst,		// Destination image.
	int16_t		sDstX1,		// Start pt.
	int16_t		sDstY1,		// Start pt.
	int16_t		sDstX2,		// End pt.
	int16_t		sDstY2,		// End pt.
	RRect*	prcDstClip)	// Dst clip rect.
	{
	#if 0
		// If entirely on view . . .
		// *****VERY CHEESY, INCORRECT CLIP FOR LINE TOTALLY ON SCREEN****
		RRect	rcClipPt1(sDstX1, sDstY1, 1, 1);
		RRect	rcClipPt2(sDstX2, sDstY2, 1, 1);
		if (	rcClipPt1.ClipTo(prcDstClip) != -1
			&&	rcClipPt2.ClipTo(prcDstClip) != -1)
			{
			// Draw.
			rspLine(
				u8Color, 
				pimDst, 
				sDstX1,
				sDstY1,
				sDstX2,
				sDstY2);
			}
	#else
		// Draw.
		rspLine(
			u8Color, 
			pimDst, 
			sDstX1,
			sDstY1,
			sDstX2,
			sDstY2,
			prcDstClip);
	#endif
	}

////////////////////////////////////////////////////////////////////////////////
// Line function for 3D line.
////////////////////////////////////////////////////////////////////////////////
inline
void DrawLine3d(			// Returns nothing.
	U8			u8Color,		// Color to draw line with.
	RImage*	pimDst,		// Destination image.
	int16_t		sDstX1,		// Start pt.
	int16_t		sDstY1,		// Start pt.
	int16_t		sDstZ1,		// Start pt.
	int16_t		sDstX2,		// End pt.
	int16_t		sDstY2,		// End pt.
	int16_t		sDstZ2,		// End pt.
	CHood*	phood,		// Da hood, homey.
	RRect*	prcDstClip)	// Dst clip rect.
	{
	int16_t	s2dX1, s2dY1;
	phood->Map3Dto2D(sDstX1, sDstY1, sDstZ1, &s2dX1, &s2dY1);
	int16_t s2dX2, s2dY2;
	phood->Map3Dto2D(sDstX2, sDstY2, sDstZ2, &s2dX2, &s2dY2);

	DrawLine2d(u8Color, pimDst, s2dX1, s2dY1, s2dX2, s2dY2, prcDstClip);
	}

////////////////////////////////////////////////////////////////////////////////
// Draw a 2D line.
////////////////////////////////////////////////////////////////////////////////
void CScene::Line2D(					// Returns nothing.
	RImage*			pimDst,			// Destination image.
	int16_t				sDstX,			// Destination 2D x coord.
	int16_t				sDstY,			// Destination 2D y coord.
	CSpriteLine2d*	psl2,				// Tree of sprites to render.
	RRect*			prcDstClip)		// Dst clip rect.
	{
	DrawLine2d(
		psl2->m_u8Color,
		pimDst,
		psl2->m_sX2 + sDstX,   
		psl2->m_sY2 + sDstY,
		psl2->m_sX2End + sDstX,
		psl2->m_sY2End + sDstY,
		prcDstClip);
	}

////////////////////////////////////////////////////////////////////////////////
// Draw a 3D (as if there were another kind) cylinder.
// Actually, it draws a box from a cylinder description.
////////////////////////////////////////////////////////////////////////////////
void CScene::RenderCylinder3D(			// Returns nothing.
	RImage*					pimDst,			// Destination image.
	int16_t						sDstX,			// Destination 2D x coord.
	int16_t						sDstY,			// Destination 2D y coord.
	CSpriteCylinder3d*	psc3,				// Cylinder sprite.
	CHood*					phood,			// Da hood, homey.
	RRect*					prcDstClip)		// Dst clip rect.
	{
	int16_t sX	= sDstX + psc3->m_sX2;
	int16_t	sZ	= sDstY + psc3->m_sY2;

	// Determine points.
	int16_t	sX1	= sX - psc3->m_sRadius;
	int16_t	sX2	= sX + psc3->m_sRadius;
	int16_t	sY1	= 0;
	int16_t	sY2	= psc3->m_sHeight;
	int16_t	sZ1	= sZ - psc3->m_sRadius;
	int16_t	sZ2	= sZ + psc3->m_sRadius;

	// Base box.
	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX,
		sY1,
		sZ1,
		sX2,
		sY1,
		sZ,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX2,
		sY1,
		sZ,
		sX,
		sY1,
		sZ2,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX,
		sY1,
		sZ2,
		sX1,
		sY1,
		sZ,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX1,
		sY1,
		sZ,
		sX,
		sY1,
		sZ1,
		phood,
		prcDstClip);

	// Top box.
	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX,
		sY2,
		sZ1,
		sX2,
		sY2,
		sZ,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX2,
		sY2,
		sZ,
		sX,
		sY2,
		sZ2,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX,
		sY2,
		sZ2,
		sX1,
		sY2,
		sZ,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX1,
		sY2,
		sZ,
		sX,
		sY2,
		sZ1,
		phood,
		prcDstClip);

	// Height edges.
	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX,
		sY1,
		sZ1,
		sX,
		sY2,
		sZ1,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX2,
		sY1,
		sZ,
		sX2,
		sY2,
		sZ,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX,
		sY1,
		sZ2,
		sX,
		sY2,
		sZ2,
		phood,
		prcDstClip);

	DrawLine3d(
		psc3->m_u8Color,
		pimDst,
		sX1,
		sY1,
		sZ,
		sX1,
		sY2,
		sZ,
		phood,
		prcDstClip);
	}

////////////////////////////////////////////////////////////////////////////////
// Render a 2D sprite.
////////////////////////////////////////////////////////////////////////////////
void CScene::Render2D(		// Returns nothing.
	RImage*		pimDst,		// Destination image.
	int16_t			sDstX,		// Destination 2D x coord.
	int16_t			sDstY,		// Destination 2D y coord.
	CSprite2*	ps2Cur,		// Tree of sprites to render.
	CHood*		phood,		// Da hood, homey.
	RRect*		prcDstClip,	// Dst clip rect.
	CSprite*		psprXRayee)	// XRayee, if not NULL.
	{
	// Make sure the stuff we need is there.
	ASSERT(ps2Cur->m_pImage != NULL);

	ASSERT(ps2Cur->m_sAlphaLevel >= 0);
	ASSERT(ps2Cur->m_sAlphaLevel <= 255);

	if (ps2Cur->m_pimAlpha != NULL && g_GameSettings.m_sAlphaBlend != FALSE)
		{
		// If the alpha level is not opaque . . .
		if (ps2Cur->m_sAlphaLevel < 255)
			{
			// Do the Alpha Mask with adaptable alpha level blit.
			rspGeneralAlphaBlit(
				ps2Cur->m_sAlphaLevel,		// Adaptable alpha level.
				phood->m_pmaTransparency,	// Levels multialpha table.
				ps2Cur->m_pimAlpha,			// Alpha mask.
				ps2Cur->m_pImage,				// Src.
				pimDst,							// Dst.
				ps2Cur->m_sX2 + sDstX,		// 2D Dst coord.
				ps2Cur->m_sY2 + sDstY,		// 2D Dst coord.
				*prcDstClip);					// Dst clip.
			}
		else
			{
			// Do the Alpha Mask blit.
			rspGeneralAlphaBlit(
				phood->m_pmaTransparency,	// Levels multialpha table.
				ps2Cur->m_pimAlpha,			// Alpha mask.
				ps2Cur->m_pImage,				// Src.
				pimDst,							// Dst.
				ps2Cur->m_sX2 + sDstX,		// 2D Dst coord.
				ps2Cur->m_sY2 + sDstY,		// 2D Dst coord.
				*prcDstClip);					// Dst clip.
			}
		}
	else
		{
		// If the alpha level is not opaque . . .
		if (ps2Cur->m_sAlphaLevel < 255 && g_GameSettings.m_sAlphaBlend != FALSE)
			{
			// Do Homogeneous Alpha blit.
			rspAlphaBlitT(
				ps2Cur->m_sAlphaLevel,		// Homogeneous alpha level.
				phood->m_pmaTransparency,	// Levels multialpha table.
				ps2Cur->m_pImage,				// Src.
				pimDst,							// Dst.
				ps2Cur->m_sX2 + sDstX,		// 2D Dst coord.
				ps2Cur->m_sY2 + sDstY,		// 2D Dst coord.
				prcDstClip);					// Dst clip.
			}
		else
			{
			// Determine which BLiT to use based on image type
			if (ps2Cur->m_pImage->m_type == RImage::FSPR8)
				{
				// If this item is alpha OR this item is an opaque and we are
				// xraying opaques AND there is an xrayee AND the xray effect is enabled, 
				// we need to see if the xray should be used via collision detection.
				int16_t sNormalBlit = 1;
#if 0			// The math equiv which appears to be slower than the confusing but
				// similar compares.  Also, the math allows none of the short circuiting 
				// that the C++ compares do.
				short	sUseXRay		
					=	(	(ps2Cur->m_sInFlags & CSprite::InAlpha) 
						+	(	(ps2Cur->m_sInFlags & CSprite::InOpaque)
							*	m_bXRayAll
							)
						)
					*	(long)psprXRayee
					*	(	g_GameSettings.m_sXRayEffect
						+	m_bXRayAll
						)
					;
						

				if (sUseXRay)
#else
				if (	(		(ps2Cur->m_sInFlags & CSprite::InAlpha) 
							|| ( (ps2Cur->m_sInFlags & CSprite::InOpaque) 
								&& m_bXRayAll == true
								)
						)
						&& psprXRayee 
						&& 
						(		g_GameSettings.m_sXRayEffect != FALSE
							||	m_bXRayAll == true)
					)
#endif
					{
					// Check if this sprite collides with the xrayee sprite.  If so, 
					// we need to do draw this sprite with the xray effect so the 
					// xrayee(s) can be seen through it.
					int16_t	sXRayeeW	= phood->m_pimXRayMask->m_sWidth;
					int16_t	sXRayeeH	= phood->m_pimXRayMask->m_sHeight;
					int16_t	sXRayeeX	= psprXRayee->m_sX2;	// Default to 2D blit location.
					int16_t	sXRayeeY	= psprXRayee->m_sY2;	// Default to 2D blit location.
					switch (psprXRayee->m_type)
						{
						case CSprite::Standard2d:
							// Use default sXRayeeX, sXRayeeY.
							break;
						case CSprite::Standard3d:
							{
							// For ease of access.
							CSprite3*	ps3XRayee	= (CSprite3*)psprXRayee;
							sXRayeeX	= ps3XRayee->m_sCenX - sXRayeeW / 2;
							sXRayeeY	= ps3XRayee->m_sCenY - sXRayeeH / 2;
							break;
							}
						}
					
					// Do an exteremely cheesy collision detecting by clipping the two rects!!!!
					RRect rect1(sXRayeeX, sXRayeeY, sXRayeeW, sXRayeeH);
					RRect rect2(ps2Cur->m_sX2, ps2Cur->m_sY2, ps2Cur->m_pImage->m_sWidth, ps2Cur->m_pImage->m_sHeight);
					if (rect2.ClipTo(&rect1) != -1)
						{
						// Clear flag so he doesn't get drawn by normal blit
						sNormalBlit = 0;

						// Get coordinate in BUILDING coordinates (i.e., coords inside/realtive-to the building)
						// for the alpha effect.
						int16_t	sAlphaX	= (sXRayeeX + sXRayeeW / 2 - phood->m_pimXRayMask->m_sWidth / 2) - ps2Cur->m_sX2;
						int16_t	sAlphaY	= (sXRayeeY + sXRayeeH / 2 - phood->m_pimXRayMask->m_sHeight / 2) - ps2Cur->m_sY2;

						// Set position of alpha effect.
						g_alphaBlit(
								ps2Cur->m_pImage,				// Source image.
								pimDst,							// Destination image.
								phood->m_pimXRayMask,		// Mask of alphable area.
								phood->m_pmaTransparency,	// Table of alphas or something.
								sAlphaX,							// Source coordinate in pimSrc to start blit.
								sAlphaY,							// Source coordinate in pimSrc to start blit.
								ps2Cur->m_sX2 + sDstX,		// Destination coordinate in pimDst for pimSrc(0,0).
								ps2Cur->m_sY2 + sDstY,		// Destination coordinate in pimDst for pimSrc(0,0).
								*prcDstClip);					// Rectangle to clip Dst to.
						}
					}
				if (sNormalBlit)
					{
					// Transparent BLiT
					rspBlit(
						ps2Cur->m_pImage,					// src
						pimDst,								// dst
						ps2Cur->m_sX2 + sDstX,			// map x to dst image
						ps2Cur->m_sY2 + sDstY,			// map y to dst image
						prcDstClip);						// dst clip
					}
				}
			else
				{
				// Non-alpha BLiT
				rspBlitT(
					0,										// Src.
					ps2Cur->m_pImage,					// src
					pimDst,								// dst
					0,										// src x
					0,										// src y
					ps2Cur->m_sX2 + sDstX,			// dst x
					ps2Cur->m_sY2 + sDstY,			// dst y
					ps2Cur->m_pImage->m_sWidth,	// width
					ps2Cur->m_pImage->m_sHeight,	// height
					prcDstClip,							// dst clip
					NULL);								// Src clip.
				}
			}
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Render a sprite tree.
////////////////////////////////////////////////////////////////////////////////
void CScene::Render(			// Returns nothing.
	RImage*		pimDst,		// Destination image.
	int16_t			sDstX,		// Destination 2D x coord.
	int16_t			sDstY,		// Destination 2D y coord.
	CSprite*		pSprite,		// Tree of sprites to render.
	CHood*		phood,		// Da hood, homey.
	RRect*		prcDstClip,	// Dst clip rect.
	CSprite*		psprXRayee)	// XRayee, if not NULL.
	{
	while (pSprite != NULL)
		{
		// Make sure sprite isn't hidden (if it is, skip it)
		if (!(pSprite->m_sInFlags & CSprite::InHidden))
			{
			// Set flag to indicate sprite was rendered.  As of right now, the sprite WILL
			// be rendered if we get this far, but if the logic changes, the setting of
			// this flag might need to be moved to somewhere else.
			pSprite->m_sOutFlags |= CSprite::OutRendered;

			// Determine whether this is a 2d or 3d sprite
			switch (pSprite->m_type)
				{
				case CSprite::Standard2d:
					{
		// ****TEMP****
		if (g_bSceneDontBlit == false)
		{
		// ****END TEMP****
					Render2D(					// Returns nothing.
						pimDst,					// Destination image.
						sDstX,					// Destination 2D x coord.
						sDstY,					// Destination 2D y coord.
						(CSprite2*)pSprite,	// Tree of sprites to render.
						phood,					// Da hood, homey.
						prcDstClip,				// Dst clip rect.
						psprXRayee);			// XRayee, if not NULL.
		// ****TEMP****
		}
		// ****END TEMP****

					break;	// CSprite::Standard2d.
					}

				case CSprite::Standard3d:	// *** g_bSceneDontBlit is checked inside Render3D() 10/03/99 ***
					Render3D(					// Returns happy thoughts.
						pimDst,					// Destination image.
						sDstX,					// Destination 2D x coord.
						sDstY,					// Destination 2D y coord.
						(CSprite3*)pSprite,	// Tree of 3D sprites to render.
						phood,					// Da hood, homey.
						prcDstClip);			// Dst clip rect.

					break;	// CSprite::Standard3d.

				case CSprite::Line2d:
					{
		// ****TEMP****
		if (g_bSceneDontBlit == false)
		{
		// ****END TEMP****
					Line2D(								// Returns nothing.
						pimDst,							// Destination image.
						sDstX,							// Destination 2D x coord.
						sDstY,							// Destination 2D y coord.
						(CSpriteLine2d*)pSprite,	// Tree of sprites to render.
						prcDstClip);					// Dst clip rect.

		// ****TEMP****
		}
		// ****END TEMP****

					break;
					}

				case CSprite::Cylinder3d:
					{
		// ****TEMP****
		if (g_bSceneDontBlit == false)
		{
		// ****END TEMP****
					RenderCylinder3D(						// Returns nothing.
						pimDst,								// Destination image.
						sDstX,								// Destination 2D x coord.
						sDstY,								// Destination 2D y coord.
						(CSpriteCylinder3d*)pSprite,	// Cylinder sprite.
						phood,								// Da hood, homey.
						prcDstClip);						// Dst clip rect.

		// ****TEMP****
		}
		// ****END TEMP****

					break;
					}

				}

			// ****TEMP****
			if (g_bSceneDontBlit == false)
			{
			// ****END TEMP****
			// If there's some text . . .
			if (pSprite->m_pszText != NULL)
				{


				// Bind it.
				int16_t	sX	= pSprite->m_sX2 + sDstX;
				int16_t	sY	= pSprite->m_sY2 + sDstY;
				int16_t	sW	= prcDstClip->sX + prcDstClip->sW - sX;
				int16_t	sH	= prcDstClip->sY + prcDstClip->sH - sY;

				if (sW > 0 && sH > 0)
					{
					ms_print.SetColumn(
						sX, 
						sY,
						sW,
						sH);

					// Print it.
					ms_print.print(
						sX, sY,
						"%s",
						pSprite->m_pszText);
					}
				}
			// ****TEMP****
			}
			// ****END TEMP****

			// If this sprite has any children . . .
			if (pSprite->m_psprHeadChild != NULL)
				{
				Render(								// Returns nothing.
					pimDst,							// Destination image.
					sDstX + pSprite->m_sX2,		// Destination 2D x coord.
					sDstY + pSprite->m_sY2,		// Destination 2D y coord.
					pSprite->m_psprHeadChild,	// Tree of sprites to render.
					phood,							// Da hood, homey.
					prcDstClip,						// Dst clip rect.
					psprXRayee);					// XRayee, if not NULL.
				}
			}

		// Get sibling.
		pSprite	= pSprite->m_psprNext;
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Render specified area of scene into specified image
///////////////////////////////////////////////////////////////////////////////
void CScene::Render(
	int16_t sSrcX,											// In:  Source (scene) x coord
	int16_t sSrcY,											// In:  Source (scene) y coord
	int16_t sW,												// In:  Width
	int16_t sH,												// In:  Height
	RImage* pimDst,										// In:  Destination image
	int16_t sDstX,											// In:  Destination (image) x coord
	int16_t sDstY,											// In:  Destination (image) y coord
	CHood* phood)											// In:  The hood involved.
	{
	// Init dst clipping rect
	RRect rDstClip(sDstX, sDstY, sW, sH);

	// Clip the dst clipping rect to the image size (in case image is smaller)
	RRect rImageSize(0, 0, pimDst->m_sWidth, pimDst->m_sHeight);
	rDstClip.ClipTo(&rImageSize);

	ms_print.SetDestination(pimDst, &rDstClip);

	// Calculate mapping from scene to image coords
	int16_t sMapX = sSrcX - sDstX;
	int16_t sMapY = sSrcY - sDstY;

	CSprite*	psprXRayee	= NULL;	// XRayee when not NULL.

	// Go through all the layers, back to front
	for (int16_t sLayer = 0; sLayer < m_sNumLayers; sLayer++)
		{
		// Get pointer to layer (more readable)
		Layer* pLayer = &m_pLayers[sLayer];

		// Make sure layer isn't hidden (if it is, skip it)
		if (!(pLayer->m_bHidden))
			{

			// Go through all the sprites stored in this layer
			for (msetSprites::iterator iSprite = pLayer->m_sprites.begin(); iSprite != pLayer->m_sprites.end(); )
				{
				// Get pointer to sprite (more readable than iterator dereference and may optimize better)
				CSprite* pSprite = *iSprite;

				Render(				// Returns nothing.
					pimDst,			// Destination image.
					-sMapX,			// Destination 2D x coord.
					-sMapY,			// Destination 2D y coord.
					pSprite,			// Tree of sprites to render.
					phood,			// Da hood, homey.
					&rDstClip,		// Dst clip rect.
					psprXRayee);	// XRayee, if not NULL.

				// Move to next item before deleting this one.
				iSprite++;

				// If this sprite wanted to be deleted after use . . .
				if (pSprite->m_sInFlags & CSprite::InDeleteOnRender)
					{
					RemoveSprite(pSprite);
					// Be gone, vile weed.
					delete pSprite;
					}
				else
					{
					// If this sprite is an xrayee, add it to the container
					if (pSprite->m_sInFlags & CSprite::InXrayee)
						{
						psprXRayee	= pSprite;
						}
					}
				}
			}
		}
	}


////////////////////////////////////////////////////////////////////////////////
// Setup render pipeline.  Use this function to setup or alter the pipeline.
// This function DOES a Make1() and then multiplies by the supplied transform,
// if any.  Any transforms that need to be applied after this setup can be
// done following a call to this function.
// The only sux is you cannot insert yourself into the middle of this function.
// If that's what you need, you should probably just do the entire setup for
// the pipe yourself or add a similar function to this one.
////////////////////////////////////////////////////////////////////////////////
void CScene::SetupPipeline(						// Returns nothing.
	RTransform* ptrans /*= NULL*/,				// Transform to apply before doing defaults.
	RTransform* ptransScene2Realm /*= NULL*/,	// Transform to convert from scene to realm.
	double		dScale3d /*= 0.0*/)				// In:  New scaling to apply to pipeline (see
															// m_dScale3d declaration).
	{
	/////////////////////////////////////////////////////////////////////////////
	// This portion was previously done in UpdatePipeline().
	/////////////////////////////////////////////////////////////////////////////
	// Adjust the current pipeline to account for the case when we've
	// loaded a new hood which may have a different 3d scaling for
	// the 3d characters.  This effects both the pipeline and the
	// smashatorium!
	/////////////////////////////////////////////////////////////////////////////
	// Some extra safety
	if (dScale3d <= c_dMinScale) dScale3d = c_dMinScale;
	if (dScale3d > c_dMaxScale) dScale3d = c_dMaxScale;

	ASSERT( (dScale3d >= c_dMinScale) && (dScale3d <= c_dMaxScale) );

	// Strap onto the old methods:
	m_dScale3d = dScale3d;

	// Use the built in adjustment features of the pipeline:
	if (m_pipeline.Create(1000, SCREEN_DIAMETER_FOR_3D) != 0)
		TRACE("SetupPipeline(): FONGOOL!  m_pipeline.Create() failed!  No 3D for you!\n");

	/////////////////////////////////////////////////////////////////////////////
	// End previously done in UpdatePipeline().
	/////////////////////////////////////////////////////////////////////////////

	// Re-init (Renit) the pipeline.
	m_pipeline.m_tView.Make1();	// Identity.
	m_pipeline.m_tScreen.Make1();	// Identity.
	m_transNoZView.Make1();			// Identity.
	m_transNoZScreen.Make1();		// Identity.

	// If there is a user transform . . .
	if (ptrans != NULL)
		{
		// Apply now. Ok. Close.
		m_pipeline.m_tView.PreMulBy(ptrans->T);
		}

	// If there is a conversion transform . . .
	if (ptransScene2Realm != NULL)
		{
		m_transScene2Realm	= *ptransScene2Realm;
		}
	else
		{
		m_transScene2Realm.Make1();
		}

	////////////////////////////////////////////////////////
	// Stolen from the famous 3D e-letters by Jeff Diamond.
	// We use this to get from SoftImage to Postal coord
	// system.
	////////////////////////////////////////////////////////

	// OK, this is a bit more professional way to do it:
	int16_t sScreenSize		= SCREEN_DIAMETER_FOR_3D; // diameter in pixels;
//	short	sScreenRadius	= sScreenSize>>1;

	double dModelSize		= MODEL_DIAMETER; // diameter in Randy's coordinates
	double dModelRadius	= 0.5 * dModelSize;

	// Flipping the image in the view was a bad hack, bacause it
	// changes the direction of rotation about the axis:
	//	m_pipe.m_tView.Scale(1.0,-1.0,1.0); // DON'T DO!

	// Just put the Randy origin in the center of the cube:
	m_pipeline.m_tView.Trans(dModelRadius, dModelRadius, 0.0);
	// Mimic this in transform that contains no Z scaling.
	m_transNoZView.Trans(dModelRadius, dModelRadius, 0.0);

	// Then, to project to the screen, need to FIRST scale it,
	// THEN flip it about y=0, and FINALLY slide it back down sp
	// we can see it.  (Luckily, when you're down it's still a single
	// matrix, so it's no slower than anything else you might do.

	// Also, stretch out the z-coordinate to take full advantage of the 
	// 16-bit z-buffer
	//
	m_pipeline.m_tScreen.Scale(
		double(sScreenSize) / dModelSize,
		double(sScreenSize) / dModelSize,
		REAL(65535.0 / MODEL_ZSPAN));	 // Use MODEL_ZSPAN for consistent fog thickness
	// Mimic this in transform that contains no Z scaling.
	m_transNoZScreen.Scale(
		double(sScreenSize) / dModelSize,
		double(sScreenSize) / dModelSize,
		double(sScreenSize) / dModelSize);

	// Now offset the image and flip the y-coordinate:
	m_pipeline.m_tScreen.Scale(1.0,-1.0,1.0); // mirror vertically about (y=0)
	// Mimic this in transform that contains no Z scaling.
	m_transNoZScreen.Scale(1.0, -1.0, 1.0);

	m_pipeline.m_tScreen.Trans(0.0,sScreenSize,0.0); // slide down the screen
	// Mimic this in transform that contains no Z scaling.
	m_transNoZScreen.Trans(0.0,sScreenSize,0.0);

	////////////////////////////////////////////////////////
	// More transforms can be applied to either of the 
	// pipeline's transforms after this function exits, if
	// so desired.
	////////////////////////////////////////////////////////
	}

////////////////////////////////////////////////////////////////////////////////
// Transform the given points through the CScene's pipeline with the
// supplied transform.
////////////////////////////////////////////////////////////////////////////////
void CScene::TransformPts(	// Returns nothing.
	RTransform*	ptrans,		// In:  Transformation to apply to CScene's before
									// transforming pts.
	RP3d*		p3dPtsSrc,		// In:  Ptr to group of pts to transform from.
	RP3d*		p3dPtsDst,		// Out: Ptr to group of pts to transform into.
	int16_t		sNum)				// In:  The number of pts in p3dPtsSrc to transform.
	{
	// Let's try to be nice and let the user call us all the time.
	// That is, check if there's anything to do before wasting any time . . .
	if (sNum > 0)
		{
		RTransform	transApplied;	// Auto-identitied.

		// If there is a user transformation . . .
		if (ptrans != NULL)
			{
			// Create scene-user transform:
			// Apply user to view.
			transApplied.Mul(m_transNoZView.T, ptrans->T);
			// Apply user-view to screen (store in user-view).
			transApplied.PreMulBy(m_transNoZScreen.T);
			}
		else
			{
			// Create plain scene transform:
			// Apply view to screen (store in scene transform).
			transApplied.Mul(m_transNoZScreen.T, m_transNoZView.T);
			}

		// Translate points
		int16_t	sCur;
		// Note that this could save a fraction of a miniscule(sp?) moment by being
		// a do { } while since we know sNum > 0, but fongooey.
		for (sCur = 0; sCur < sNum; sCur++)
			{
			transApplied.TransformInto(*p3dPtsSrc++, *p3dPtsDst++);
			}
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Transform the given points through the CScene's pipeline with the
// supplied transform and then map them to Realm 3D coordinates.
////////////////////////////////////////////////////////////////////////////////
void CScene::TransformPtsToRealm(	// Returns nothing.
	RTransform*	ptrans,		// In:  Transformation to apply to CScene's before
									// transforming pts.
	RP3d*		p3dPtsSrc,		// In:  Ptr to group of pts to transform from.
	RP3d*		p3dPtsDst,		// Out: Ptr to group of pts to transform into.
	int16_t		sNum)				// In:  The number of pts in p3dPtsSrc to transform.
	{
#if 0	// This doesn't work.
	RTransform	trans;
	// If there is a user transform . . .
	if (ptrans != NULL)
		{
		// Combine the caller's with the conversion.
		trans.Mul(m_transScene2Realm.T, ptrans->T);
		// Use new transform.
		ptrans	= &trans;
		}
	else
		{
		// Use the conversion transform.
		ptrans	= &m_transScene2Realm;
		}
#endif

	// Map to scene.
	TransformPts(ptrans, p3dPtsSrc, p3dPtsDst, sNum);

	// Translate to Postal Realm coords.
	int16_t	i;
	for (i = 0; i < sNum; i++)
		{
		p3dPtsDst->x		-= SCREEN_DIAMETER_FOR_3D / 2;
		p3dPtsDst->y		= -(p3dPtsDst->y - SCREEN_DIAMETER_FOR_3D / 2);
		// No Z mapping.
//		p3dPtsDst->z		= p3dPtsDst->z;

		p3dPtsDst++;
		}

	}

////////////////////////////////////////////////////////////////////////////////
// Render a 3D sprite tree into the specified image.
// Ignores non 3D sprites.
////////////////////////////////////////////////////////////////////////////////
void CScene::DeadRender3D(					// Returns nothing.
	RImage*		pimDst,						// Destination image.
	CSprite3*	ps3,							// Tree of 3D sprites to render.
	CHood*		phood,						// Da hood, homey.
	int16_t			sDstX /*= 0*/,				// Destination 2D x coord.
	int16_t			sDstY /*= 0*/,				// Destination 2D y coord.
	RRect*		prcDstClip /*= NULL*/)	// Dst clip rect.
	{
	// Make sure sprite isn't hidden (if it is, skip it)
	if (!(ps3->m_sInFlags & CSprite::InHidden))
		{
		ASSERT(pimDst	!= NULL);
		ASSERT(ps3		!= NULL);
		ASSERT(phood	!= NULL);

		RRect	rcClip;
		// If no clipping rect specified . . .
		if (prcDstClip == NULL)
			{
			prcDstClip	= &rcClip;

			rcClip.sX	= 0;
			rcClip.sY	= 0;
			rcClip.sW	= pimDst->m_sWidth;
			rcClip.sH	= pimDst->m_sHeight;
			}

#if 1
		// Render.
		Render3D(
			pimDst,			// Destination image.
			sDstX,			// Destination 2D x coord.
			sDstY,			// Destination 2D y coord.
			ps3,				// Tree of 3D sprites to render.
			phood,			// Da hood, homey.
			prcDstClip);	// Dst clip rect.

		// Call for all children . . .
		CSprite*	psprite	= ps3->m_psprHeadChild;
		while (psprite)
			{
			// If 3D child and visible . . .
			if (psprite->m_type == CSprite::Standard3d)
				{
				// Render 'em.
				DeadRender3D(
					pimDst, 
					(CSprite3*)psprite, 
					phood, 
					sDstX + ps3->m_sX2,
					sDstY + ps3->m_sY2,
					prcDstClip);
				}

			// Next, please.
			psprite	= psprite->m_psprNext;
			}
#else
		Render(				// Returns nothing.
			pimDst,			// Destination image.
			0,					// Destination 2D x coord.
			0,					// Destination 2D y coord.
			ps3,				// Tree of sprites to render.
			phood,			// Da hood, homey.
			prcDstClip,		// Dst clip rect.
			NULL);			// XRayee, if not NULL.
#endif
		}
	}


////////////////////////////////////////////////////////////////////////////////
// Set all 'alpha' _and_ 'opaque' layers to xray.
////////////////////////////////////////////////////////////////////////////////
void CScene::SetXRayAll(	// You see a door to the north.  Returns nothing.       
	bool bXRayAll)				// In:  true to X Ray all 'alpha' _and_ 'opaque' layers.
	{
	m_bXRayAll	= bXRayAll;
	}

////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////
