www.pudn.com > RCApp-src.zip > Entity.cpp


/* 
	RedEye Project (http://members.ozemail.com.au/~ndmcevoy/) 
	Copyright (C) 2003  Nick McEvoy 
 
	This library is free software; you can redistribute it and/or 
	modify it under the terms of the GNU Library General Public 
	License as published by the Free Software Foundation; either 
	version 2 of the License, or (at your option) any later version. 
 
	This library 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 
	Library General Public License for more details. 
 
	You should have received a copy of the GNU Library General Public 
	License along with this library; if not, write to the Free 
	Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 
	----------------------------------------------------------------- 
 
		Commented for use with Doxygen (http://www.doxygen.org) 
 
	----------------------------------------------------------------- 
*/ 
 
/*! \file Entity.cpp 
 *	\brief Game entity. 
 * 
 *		This file contains the game entity. 
 */ 
 
// GLUT includes 
#include  
 
// ODE includes 
#include "ode/common.h" 
#include "ode/contact.h" 
#include "ode/objects.h" 
#include "ode/geom.h" 
#include "ode/rotation.h" 
 
// RedEye includes 
#include "Entity.h" 
#include "Engine.h" 
#include "MaterialManager.h" 
#include "ModelManager.h" 
#include "Collision.h" 
#include "RCDefs.h" 
 
static sgVec4 black             = { 0.0f, 0.0f, 0.0f, 1.0f } ; 
static sgVec4 white             = { 1.0f, 1.0f, 1.0f, 1.0f } ; 
static sgVec4 translucent_white = { 1.0f, 1.0f, 1.0f, 0.8f } ; 
 
void 
reUpdateValue(float& fValue, 
			  bool bIncFactor1, bool bDecFactor2, 
			  float fIncFactor1, float fDecFactor2, 
			  float fDampFactor, float fMaxValue, 
			  float fMinValue, float fDeltaSec); 
 
//********************************************************************** 
// 
// reShadow 
// 
//********************************************************************** 
reShadow::reShadow(float x1, float x2, float y1, float y2) 
{ 
	setName("Shadow"); 
 
	sgVec4* scolors = new sgVec4[1]; 
	sgVec3* scoords = new sgVec3[4]; 
	sgVec2* tcoords = new sgVec2[4]; 
	sgVec3* snorms  = new sgVec3[1]; 
 
	sgSetVec4(scolors[0], 0.0f, 0.0f, 0.0f, 1.0f); 
	sgSetVec3(snorms [0], 0.0f, 0.0f, 1.0f); 
 
	sgSetVec3(scoords[0], x1, y1, 0.01); 
	sgSetVec3(scoords[1], x2, y1, 0.01); 
	sgSetVec3(scoords[2], x1, y2, 0.01); 
	sgSetVec3(scoords[3], x2, y2, 0.01); 
 
	sgSetVec2(tcoords[0], 0.0, 0.0); 
	sgSetVec2(tcoords[1], 1.0, 0.0); 
	sgSetVec2(tcoords[2], 0.0, 1.0); 
	sgSetVec2(tcoords[3], 1.0, 1.0); 
 
	clrTraversalMaskBits(SSGTRAV_ISECT|SSGTRAV_HOT); 
 
	ssgVTable* gs = new ssgVTable( 
		GL_TRIANGLE_STRIP, 4, scoords, 1, snorms, 4, tcoords, 1, scolors); 
 
	gs->clrTraversalMaskBits(SSGTRAV_ISECT|SSGTRAV_HOT); 
 
	ssgSimpleState* pFuzzyState = 
		reGetMaterialManager()->GetSimpleState("textures/fuzzy.inta"); 
 
	gs->setState(pFuzzyState); 
 
	mpTransform = new ssgTransform; 
	addKid(mpTransform); 
	mpTransform->addKid(gs); 
} 
 
 
 
 
void 
reShadow::Update(float x, float y) 
{ 
	sgCoord Position; 
	sgSetCoord(&Position, x, y, 0, 0, 0, 0); 
 
	ssgHit* pHit; 
	sgVec3 vNormal; 
	float fHOT = reGetHeightAndNormal(reGetWorld(), Position.xyz, vNormal, &pHit); 
	Position.xyz[SG_Z] = fHOT; 
 
	pr_from_normal(Position.hpr, vNormal); 
 
	mpTransform->setTransform(&Position); 
} 
 
//********************************************************************** 
// 
// reGameEntity 
// 
//********************************************************************** 
reGameEntity::reGameEntity() 
{ 
	mpTransform = NULL; 
	sgZeroVec3(mVDampFactor); 
	sgZeroVec3(mAVDampFactor); 
	mpShadow = NULL; 
	mODEBody = NULL; 
	mODEGeom = NULL; 
} 
 
 
 
 
reGameEntity::~reGameEntity() 
{ 
	if (mODEGeom) 
	{ 
		dGeomDestroy(mODEGeom); 
		mODEGeom = NULL; 
	} 
 
	if (mODEBody) 
	{ 
		dBodyDestroy(mODEBody); 
		mODEBody = NULL; 
	} 
 
	if (mpShadow) 
	{ 
		reGetWorld()->removeKid(mpShadow); 
		ssgDeRefDelete(mpShadow); 
	} 
} 
 
 
 
 
reGameEntity* 
reGameEntity::Create(const char* psFile) 
{ 
	char sFilepath[RE_STRING_MAX]; 
	sprintf(sFilepath, "%s/%s", reGetGameEngine()->GetLevelPath(), psFile); 
 
	TiXmlDocument Config; 
	if (!Config.LoadFile(sFilepath)) 
	{ 
		ulSetError(UL_WARNING,  
		     "reGameEntity::Create() Could not read %s.", sFilepath); 
		return NULL; 
	} 
 
	TiXmlNode* pNode = Config.FirstChild("Entity"); 
 
	return Create(pNode); 
} 
 
 
 
 
reGameEntity* 
reGameEntity::Create(TiXmlNode* pNode) 
{ 
	reGameEntity* pEntity = NULL; 
 
	const char* psType = reGetXMLAttribute(pNode, "type", ""); 
	const char* psSubType = reGetXMLAttribute(pNode, "sub_type", ""); 
 
	if (strcmp(psType, "rePlatformEntity") == 0) 
	{ 
		if (strcmp(psSubType, "reBoatEntity") == 0) 
			pEntity = new reBoatEntity(); 
		else if (strcmp(psSubType, "reBillyCartEntity") == 0) 
			pEntity = new reBillyCartEntity(); 
		else if (strcmp(psSubType, "reSpaceShipEntity") == 0) 
			pEntity = new reSpaceShipEntity(); 
	} 
	else if (strcmp(psType, "reBuildingEntity") == 0) 
	{ 
	} 
	else if (strcmp(psType, "reEquipmentEntity") == 0) 
	{ 
	} 
	else if (strcmp(psType, "reSceneryEntity") == 0) 
	{ 
		if (strcmp(psSubType, "reTerrainEntity") == 0) 
			pEntity = new reTerrainEntity(); 
		else if (strcmp(psSubType, "reOceanEntity") == 0) 
			pEntity = new reOceanEntity(); 
		else if (strcmp(psSubType, "reSkyEntity") == 0) 
			pEntity = new reSkyEntity(); 
	} 
	else if (strcmp(psType, "reParticleEntity") == 0) 
	{ 
		if (strcmp(psSubType, "reParticleEntity") == 0) 
			pEntity = new reParticleEntity(); 
	} 
 
	if (pEntity) 
	{ 
		pEntity->Init(pNode); 
		pEntity->ref(); 
	} 
	else 
	{ 
		ulSetError(UL_WARNING,  
			 "reGameEntity::Create() Invalid platform type %s sub_type %s.", psType, psSubType); 
	} 
 
	return pEntity; 
} 
 
 
 
 
bool 
reGameEntity::Init(TiXmlNode* pConfig) 
{ 
	mpTransform = new ssgTransform; 
	addKid(mpTransform); 
 
	if (pConfig) 
	{ 
		// Name 
		//setName("todo"); 
 
		// Geometry 
		TiXmlNode* pGeometry = pConfig->FirstChild("Geometry"); 
		const char* psGeometry = reGetXMLAttribute(pGeometry, "type", ""); 
		if (strcmp("box", psGeometry)==0) 
		{ 
			sgVec4 Geom = {0}; // 0=Width,1=Length,2=Height,3=Mass 
			reGetXMLChildAttribVec4(pGeometry, "Param", Geom); 
 
			dMass m; 
			mODEBody = dBodyCreate(reGetODEWorldId()); 
			dBodySetPosition(mODEBody, 0, 0, 0); 
			dMassSetBox(&m, 1, Geom[0], Geom[1], Geom[2]); 
			dMassAdjust(&m, Geom[3]); 
			dBodySetMass(mODEBody, &m); 
			mODEGeom = dCreateBox(0,Geom[0], Geom[1], Geom[2]); 
			dGeomSetBody(mODEGeom, mODEBody); 
		} 
		else if (strcmp("sphere", psGeometry)==0) 
		{ 
			sgVec2 Geom = {0}; // 0=Radius,1=Mass 
			reGetXMLChildAttribVec2(pGeometry, "Param", Geom); 
 
			dMass m; 
			mODEBody = dBodyCreate(reGetODEWorldId()); 
			dBodySetPosition(mODEBody, 0, 0, 0); 
			dMassSetSphere(&m, 1, Geom[0]); 
			dMassAdjust(&m, Geom[1]); 
			dBodySetMass(mODEBody, &m); 
			mODEGeom = dCreateSphere(0, Geom[0]); 
			dGeomSetBody(mODEGeom, mODEBody); 
		} 
		else // none 
		{ 
			mODEBody = NULL; 
			mODEGeom = NULL; 
		} 
 
		// Shadow 
		TiXmlNode* pShadow = pConfig->FirstChild("Shadow"); 
		bool bShadowOn = reGetXMLAttribute(pShadow, "enabled", false); 
 
		if (bShadowOn) 
		{ 
			sgVec4 Shadow = {0}; 
			reGetXMLChildAttribVec4(pShadow, "Param", Shadow); 
 
			mpShadow = new reShadow( 
				Shadow[0], Shadow[1], Shadow[2], Shadow[3]); 
			mpShadow->ref(); 
			reGetWorld()->addKid(mpShadow); 
		} 
 
		reGetXMLChildAttribVec3(pConfig, "VelocityDampFactor", mVDampFactor); 
		reGetXMLChildAttribVec3(pConfig, "AngularVelocityDampFactor", mAVDampFactor); 
	} 
 
	return true; 
} 
 
 
 
 
void 
reGameEntity::Update(float fDeltaSec) 
{ 
	if (mODEBody) 
	{ 
		// Get position 
		sgCoord p; 
		GetPosition(&p); 
 
		// Setup rotation matricies 
		// - R1 useful for conversion between body-to-world coords 
		// - R2 useful for conversion between world-to-body coords 
		sgMat4 R1, R2; 
		sgMakeRotMat4(R1, p.hpr); 
		sgCopyMat4(R2, R1); 
		sgInvertMat4(R2); 
 
		// Apply a damping force (based on current velocity) 
		// - convert velocity from world-to-body coordinates 
		// - calculate damping in the xz direction (assume body facing +y direction) 
		// - convert damping force from body-to-world coordinates 
		// - apply damping 
		sgVec3 BodyVelocity, DampingForce; 
		const dReal* pV = dBodyGetLinearVel(mODEBody); 
		sgSetVec3(BodyVelocity, pV[0], pV[1], pV[2]); 
		sgXformVec3(BodyVelocity, R2); 
		sgSetVec3(DampingForce, 
			-BodyVelocity[0]*fabs(BodyVelocity[0])*mVDampFactor[0], 
			-BodyVelocity[1]*fabs(BodyVelocity[1])*mVDampFactor[1], 
			-BodyVelocity[2]*fabs(BodyVelocity[2])*mVDampFactor[2]); 
		sgXformVec3(DampingForce, R1); 
		dBodyAddForce(mODEBody, 
			DampingForce[0], 
			DampingForce[1], 
			DampingForce[2]); 
		//printf("BodyVelocity <%0.2f:%0.2f:%0.2f>\n", BodyVelocity[0], BodyVelocity[1], BodyVelocity[2]); 
		//printf("DampingForce <%0.2f:%0.2f:%0.2f>\n", DampingForce[0], DampingForce[1], DampingForce[2]); 
 
		// Apply a damping torque (based on current angular velocity) 
		// - convert angular velocity from world-to-body coordinates 
		// - calculate damping torque 
		// - convert damping torque from body-to-world coordinates 
		// - apply damping torque 
		sgVec3 BodyAngularVelocity, DampingTorque; 
		const dReal* pAV = dBodyGetAngularVel(mODEBody); 
		sgSetVec3(BodyAngularVelocity, pAV[0], pAV[1], pAV[2]); 
		sgXformVec3(BodyAngularVelocity, R2); 
		sgSetVec3(DampingTorque, 
			-BodyAngularVelocity[0]*fabs(BodyAngularVelocity[0])*mAVDampFactor[0], 
			-BodyAngularVelocity[1]*fabs(BodyAngularVelocity[1])*mAVDampFactor[1], 
			-BodyAngularVelocity[2]*fabs(BodyAngularVelocity[2])*mAVDampFactor[2]); 
		sgXformVec3(DampingTorque, R1); 
		dBodyAddTorque(mODEBody, 
			DampingTorque[0], 
			DampingTorque[1], 
			DampingTorque[2]); 
		//printf("BodyAngularVelocity <%0.2f:%0.2f:%0.2f>\n", BodyAngularVelocity[0], BodyAngularVelocity[1], BodyAngularVelocity[2]); 
		//printf("DampingTorque <%0.2f:%0.2f:%0.2f>\n", DampingTorque[0], DampingTorque[1], DampingTorque[2]); 
 
		// Get the ODE position and set entity position 
		reGetBodyTransform(mpTransform, mODEBody); 
	} 
 
	// Update shadow 
	if (mpShadow) 
	{ 
		sgCoord Pos; 
		GetPosition(&Pos); 
		mpShadow->Update(Pos.xyz[SG_X], Pos.xyz[SG_Y]); 
	} 
} 
 
 
 
 
void 
reGameEntity::SetPosition(sgCoord* p) 
{ 
	if (mODEBody) 
	{ 
		/*dBodySetPosition(mODEBody, p->xyz[SG_X], p->xyz[SG_Y], p->xyz[SG_Z]); 
		dMatrix3 ROT; 
		dRFromEulerAngles(ROT, 
			p->hpr[0]*SG_DEGREES_TO_RADIANS, 
			p->hpr[1]*SG_DEGREES_TO_RADIANS, 
			p->hpr[2]*SG_DEGREES_TO_RADIANS); 
 
		// todo: set rotation 
		//dBodySetRotation(mODEBody, ROT);*/ 
 
		dBodySetPosition(mODEBody, p->xyz[SG_X], p->xyz[SG_Y], p->xyz[SG_Z]); 
	} 
	else 
	{ 
		mpTransform->setTransform(p); 
	} 
} 
 
 
 
 
void 
reGameEntity::GetPosition(sgCoord* p) 
{ 
	sgMat4 Xform; 
	mpTransform->getTransform(Xform); 
	sgSetCoord(p, Xform); 
} 
 
 
 
 
void 
reGameEntity::GetTransform(sgMat4 Xform) 
{ 
	mpTransform->getTransform(Xform); 
} 
 
 
 
 
void 
reGameEntity::GetVelocity(sgVec3& Velocity) 
{ 
	reGetBodyVelocity(Velocity, mODEBody); 
} 
 
 
 
 
int 
reGameEntity::GetHits(ssgHit** pHitList) 
{ 
	sgMat4 InvMat; 
	sgMakeIdentMat4(InvMat); 
 
	// GetBSphere() results are slightly larger and off center than expected 
	// ... is this a PLIB bug ? ... sigh ... no time to investigate now. :-( 
	sgCoord Pos; 
	sgSphere Sphere; 
	GetPosition(&Pos); 
	Sphere.setCenter(Pos.xyz[SG_X], Pos.xyz[SG_Y], Pos.xyz[SG_Z]); 
	Sphere.setRadius(getBSphere()->getRadius()); 
 
	// Using our bounding sphere ... check for hits 
	int iNumHits = ssgIsect(reGetWorld(), &Sphere, InvMat, pHitList); 
 
	return iNumHits; 
} 
 
//********************************************************************** 
// 
// rePlatformEntity 
// 
//********************************************************************** 
rePlatformEntity::rePlatformEntity() 
{ 
//	meEntityType = ePlatform; 
 
	mfPower = 0.0f; 
	sgZeroVec3(mDirChange); 
	mfMaxFuel = 0; 
	mfBurnRate = 0; 
	sgZeroVec2(mMaxThrust); 
	sgZeroVec3(mMaxAngularThrust); 
	sgZeroVec3(mPowerFactor); 
	sgZeroVec3(mPowerDampFactor); 
	sgZeroVec3(mAngularPowerFactor); 
	sgZeroVec3(mAngularDampFactor); 
	mbForward = false; 
	mbReverse = false; 
	mbBreaking = false; 
	mbHeadingLeft = false; 
	mbHeadingRight = false; 
	mbPitchUp = false; 
	mbPitchDown = false; 
	mbRollLeft = false; 
	mbRollRight = false; 
} 
 
 
 
 
rePlatformEntity::~rePlatformEntity() 
{ 
} 
 
 
 
 
bool 
rePlatformEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reGameEntity::Init(pConfig)) 
		return false; 
 
	if (pConfig) 
	{ 
		TiXmlNode* pFuel = pConfig->FirstChild("Fuel"); 
		mfMaxFuel = reGetXMLAttribute(pFuel, "max", 0); 
		mfBurnRate = reGetXMLAttribute(pFuel, "burn_rate", 0); 
 
		reGetXMLChildAttribVec3(pConfig, "MaxThrust", mMaxThrust); 
		reGetXMLChildAttribVec3(pConfig, "MaxAngularThrust", mMaxAngularThrust); 
		reGetXMLChildAttribVec3(pConfig, "PowerFactor", mPowerFactor); 
		reGetXMLChildAttribVec3(pConfig, "PowerDampingFactor", mPowerDampFactor); 
		reGetXMLChildAttribVec3(pConfig, "AngularPowerFactor", mAngularPowerFactor); 
		reGetXMLChildAttribVec3(pConfig, "AngularDampingFactor", mAngularDampFactor); 
	} 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reLandCraftEntity 
// 
//********************************************************************** 
bool 
reLandCraftEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!rePlatformEntity::Init(pConfig)) 
		return false; 
 
	// Read config 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reWaterCraftEntity 
// 
//********************************************************************** 
bool 
reWaterCraftEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!rePlatformEntity::Init(pConfig)) 
		return false; 
 
	// Read config 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reAirCraftEntity 
// 
//********************************************************************** 
bool 
reAirCraftEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!rePlatformEntity::Init(pConfig)) 
		return false; 
 
	// Read config 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reSpaceCraftEntity 
// 
//********************************************************************** 
bool 
reSpaceCraftEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!rePlatformEntity::Init(pConfig)) 
		return false; 
 
	// Read config 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reWeaponEntity 
// 
//********************************************************************** 
bool 
reWeaponEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!rePlatformEntity::Init(pConfig)) 
		return false; 
 
	// Read config 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reSceneryEntity 
// 
//********************************************************************** 
bool 
reSceneryEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reGameEntity::Init(pConfig)) 
		return false; 
 
	// Read config 
 
	return true; 
} 
 
//********************************************************************** 
// 
// reParticleSystem 
// 
//********************************************************************** 
static void ParticleCreateCallback(ssgaParticleSystem* ps, int, ssgaParticle* p) 
{ 
	reParticleSystem* reps = (reParticleSystem*)ps; 
 
	// Set colour, position, velocity, gravity & time to live 
	//float c = ((float)(rand()%100)/100.0f) * (256.0f-163.0f)/255.0f; 
	//sgSetVec4(p->col, 96.0f/255.0f+c, 147.0f/255.0f+c, 163.0f/255.0f+c, 0.5); 
	sgSetVec4(p->col, 
		reGenerateRandom(reps->mColMin[0], reps->mColMax[0]), 
		reGenerateRandom(reps->mColMin[1], reps->mColMax[1]), 
		reGenerateRandom(reps->mColMin[2], reps->mColMax[2]), 
		0.5); 
	sgSetVec3(p->pos, 
		reGenerateRandom(reps->mPosMin[0], reps->mPosMax[0]), 
		reGenerateRandom(reps->mPosMin[1], reps->mPosMax[1]), 
		reGenerateRandom(reps->mPosMin[2], reps->mPosMax[2])); 
	sgSetVec3(p->vel, 
		reGenerateRandom(reps->mVelMin[0], reps->mVelMax[0]), 
		reGenerateRandom(reps->mVelMin[1], reps->mVelMax[1]), 
		reGenerateRandom(reps->mVelMin[2], reps->mVelMax[2])); 
	sgSetVec3(p->acc, 
		reGenerateRandom(reps->mAccelMin[0], reps->mAccelMax[0]), 
		reGenerateRandom(reps->mAccelMin[1], reps->mAccelMax[1]), 
		reGenerateRandom(reps->mAccelMin[2], reps->mAccelMax[2])); 
	p->time_to_live = reGenerateRandom(reps->mfLiveTimeMin, reps->mfLiveTimeMax); 
} 
 
 
 
 
reParticleSystem::reParticleSystem(TiXmlNode* pConfig) 
:	ssgaParticleSystem(reGetXMLAttribute(pConfig, "num", 0),				// Max Number of particles, 
					   reGetXMLAttribute(pConfig, "initial_num", 0),		// Number to launch initially 
					   reGetXMLAttribute(pConfig, "create_rate", 0.0f),		// Max number to create per sec 
					   reGetXMLAttribute(pConfig, "turn_to_face", true),	// Turn to face? 
					   reGetXMLAttribute(pConfig, "size", 0.0f),			// Size of particles 
					   reGetXMLAttribute(pConfig, "bsphere_size", 0.0f),	// Size of bounding sphere 
					   ParticleCreateCallback, NULL, NULL) 
{ 
	sgZeroVec3(mColMin); 
	sgZeroVec3(mColMax); 
	sgZeroVec3(mPosMin); 
	sgZeroVec3(mPosMax); 
	sgZeroVec3(mVelMin); 
	sgZeroVec3(mVelMax); 
	sgZeroVec3(mAccelMin); 
	sgZeroVec3(mAccelMax); 
	mfLiveTimeMin = 0; 
	mfLiveTimeMax = 0; 
 
	TiXmlNode* pCol = pConfig->FirstChild("Colour"); 
	reGetXMLChildAttribVec3(pCol, "Min", mColMin); 
	reGetXMLChildAttribVec3(pCol, "Max", mColMax); 
 
	TiXmlNode* pPos = pConfig->FirstChild("Position"); 
	reGetXMLChildAttribVec3(pPos, "Min", mPosMin); 
	reGetXMLChildAttribVec3(pPos, "Max", mPosMax); 
 
	TiXmlNode* pVel = pConfig->FirstChild("Velocity"); 
	reGetXMLChildAttribVec3(pVel, "Min", mVelMin); 
	reGetXMLChildAttribVec3(pVel, "Max", mVelMax); 
 
	TiXmlNode* pAccel = pConfig->FirstChild("Acceleration"); 
	reGetXMLChildAttribVec3(pAccel, "Min", mAccelMin); 
	reGetXMLChildAttribVec3(pAccel, "Max", mAccelMax); 
 
	TiXmlNode* pLive = pConfig->FirstChild("TimeToLive"); 
	mfLiveTimeMin = reGetXMLChildValue(pLive, "Min", 0.0f); 
	mfLiveTimeMax = reGetXMLChildValue(pLive, "Max", 0.0f); 
} 
 
 
 
 
//********************************************************************** 
// 
// reParticleEntity 
// 
//********************************************************************** 
reParticleEntity::reParticleEntity() 
{ 
	mpParticle = NULL; 
} 
 
 
 
 
reParticleEntity::~reParticleEntity() 
{ 
	ssgDeRefDelete(mpParticle); 
} 
 
 
 
 
bool 
reParticleEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reGameEntity::Init(pConfig)) 
		return false; 
 
	TiXmlNode* pNode = pConfig->FirstChild("reParticleEntity"); 
	if (!pNode) 
		return false; 
 
	// todo: remove hardcoded values & read SimpleState from config 
	TiXmlNode* pState = pNode->FirstChild("SimpleState"); 
	ssgSimpleState* splash_state = new ssgSimpleState () ; 
	splash_state -> setTexture        ( "textures/Droplet.rgb"); 
	splash_state -> setTranslucent    () ; 
	splash_state -> enable            ( GL_TEXTURE_2D); 
	splash_state -> setShadeModel     ( GL_SMOOTH); 
	splash_state -> enable            ( GL_CULL_FACE); 
	splash_state -> enable            ( GL_BLEND); 
	splash_state -> enable            ( GL_LIGHTING); 
	splash_state -> setColourMaterial ( GL_EMISSION); 
	splash_state -> setMaterial       ( GL_AMBIENT, 0, 0, 0, 1); 
	splash_state -> setMaterial       ( GL_DIFFUSE, 0, 0, 0, 1); 
	splash_state -> setMaterial       ( GL_SPECULAR, 0, 0, 0, 1); 
	splash_state -> setShininess      (  0); 
/* 
		 
			textures/Droplet.rgb 
			true 
			true 
			GL_EMISSION 
			 
			 
			 
			 
			0 
		 
*/ 
 
	mpParticle = new reParticleSystem(pNode->FirstChild("Particle")); 
	mpParticle->ref(); 
	mpParticle->setState(splash_state); 
	mpTransform->addKid(mpParticle); 
	clrTraversalMaskBits(SSGTRAV_ISECT|SSGTRAV_HOT); 
 
	return true; 
} 
 
 
 
 
void 
reParticleEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reGameEntity::Update(fDeltaSec); 
 
	// Update particle 
	mpParticle->update(fDeltaSec); 
} 
 
 
 
 
void 
reParticleEntity::SetCreationRate(float fRate) 
{ 
	mpParticle->setCreationRate(fRate); 
} 
 
//********************************************************************** 
// 
// reTreeEntity 
// 
//********************************************************************** 
reTreeEntity::reTreeEntity() 
{ 
} 
 
 
 
 
bool 
reTreeEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reSceneryEntity::Init(pConfig)) 
		return false; 
 
	// todo: remove hardcoded values & read from config 
 
	// Get model 
	ssgEntity* pTreeObj = reGetModelManager()->GetModel("tree"); 
	mpTransform->addKid(pTreeObj); 
 
	return true; 
} 
 
 
 
 
void 
reTreeEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reSceneryEntity::Update(fDeltaSec); 
 
	// Update tree 
	// todo 
} 
 
//********************************************************************** 
// 
// reTerrainEntity 
// 
//********************************************************************** 
reTerrainEntity::reTerrainEntity() 
: mEntityList(false) 
{ 
} 
 
 
 
 
reTerrainEntity::~reTerrainEntity() 
{ 
	LISTPOSITION pos = mEntityList.GetHeadPosition(); 
	while (pos) 
	{ 
		reGameEntity* pGameEntity = mEntityList.GetNext(pos); 
		mpTransform->removeKid(pGameEntity); 
		ssgDeRefDelete(pGameEntity); 
	} 
	mEntityList.RemoveAll(); 
} 
 
 
 
 
bool 
reTerrainEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reSceneryEntity::Init(pConfig)) 
		return false; 
 
	const char* psModelName = reGetXMLChildValue(pConfig, "ModelName", ""); 
 
	TiXmlNode* pTrees = pConfig->FirstChild("Trees"); 
	bool bTreesOn = reGetXMLAttribute(pTrees, "enabled", false); 
	const char* psTreesFile = reGetXMLAttribute(pTrees, "file", ""); 
 
	TiXmlNode* pOcean = pConfig->FirstChild("Ocean"); 
	bool bOceanOn = reGetXMLAttribute(pOcean, "enabled", false); 
	const char* psOceanFile = reGetXMLAttribute(pOcean, "file", ""); 
 
	// Get model 
	ssgEntity* pTerrainObj = reGetModelManager()->GetModel(psModelName); 
	mpTransform->addKid(pTerrainObj); 
 
	/*// Create trees 
	if (iTreesCount > 0) 
	{ 
		// todo: use range selector for lod 
		FILE* pFile = reOpenLevelFile(sTreesFile); 
		float fTRadius = getBSphere()->getRadius(); 
		sgCoord TreePos; 
		for (int i = 0; i < iTreesCount; i++) 
		{ 
			// todo: fix this cause this is never going to happen cause 
			//       the terrain is not yet added to the model !!! 
			float fX, fY; 
			float fZ = DEEPEST_HELL; 
			fX = reGenerateRandom(-fTRadius, fTRadius); 
			fY = reGenerateRandom(-fTRadius, fTRadius); 
			sgSetCoord(&TreePos, fX, fY, HIGHEST_HEAVEN, 0.0f, 0.0f, 0.0f); 
			fZ = reGetHeight(reGetWorld(), TreePos.xyz); 
 
			// Only add trees above zero elevation 
			if (fZ > 0) 
			{ 
				sgSetCoord(&TreePos, fX, fY, fZ, 0.0f, 0.0f, 0.0f); 
 
				reTreeEntity* pTree = new reTreeEntity; 
				pTree->Init(pFile); 
				pTree->ref(); 
				pTree->SetPosition(&TreePos); 
				mpTransform->addKid(pTree); 
				mEntityList.AddTail(pTree); 
			} 
		} 
	}*/ 
 
	// Create ocean 
	if (bOceanOn) 
	{ 
		reOceanEntity* pOcean = (reOceanEntity*)reGameEntity::Create(psOceanFile); 
		mpTransform->addKid(pOcean); 
		mEntityList.AddTail(pOcean); 
	} 
 
	return true; 
} 
 
 
 
 
void 
reTerrainEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reSceneryEntity::Update(fDeltaSec); 
 
	// Update terrain features 
	LISTPOSITION pos = mEntityList.GetHeadPosition(); 
	while (pos) 
	{ 
		reGameEntity* pGameEntity = mEntityList.GetNext(pos); 
		pGameEntity->Update(fDeltaSec); 
	} 
} 
 
//********************************************************************** 
// 
// reOceanEntity 
// 
//********************************************************************** 
static float reOceanDepth(float x, float y) 
{ 
	// todo: calc depth based on terrain ... too cpu expensive !!! 
	return 0.5f; 
} 
 
 
 
 
reOceanEntity::reOceanEntity() 
{ 
	mfTime = 0; 
	mpOcean = NULL; 
} 
 
 
 
 
bool 
reOceanEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reSceneryEntity::Init(pConfig)) 
		return false; 
 
	sgVec3 Center = {0}; 
	sgVec4 Colour = {0}; 
 
	TiXmlNode* pNode = pConfig->FirstChild("reOceanEntity"); 
	reGetXMLChildAttribVec4(pNode, "OceanColor", Colour); 
 
	TiXmlNode* pOcean = pNode->FirstChild("Ocean"); 
	const char* psOceanTex = reGetXMLAttribute(pOcean, "tex_path", ""); 
	if (strlen(psOceanTex) == 0) psOceanTex = NULL; 
	ssgSimpleState* pSeaState = reGetMaterialManager()->GetSimpleState((char*)psOceanTex); 
 
	mpOcean = new ssgaWaveSystem(reGetXMLAttribute(pOcean, "triangles", 1000)); 
	mpOcean->setColour    (Colour); 
	mpOcean->setSize      (reGetXMLAttribute(pOcean, "size", 1000)); 
	mpOcean->setTexScale  (reGetXMLAttribute(pOcean, "tex_u", 1), 
						   reGetXMLAttribute(pOcean, "tex_v", 1)); 
	mpOcean->setCenter    (Center); 
	// todo: calc depth based on terrain ... too cpu expensive !!! 
	//mpOcean->setDepthCallback (reOceanDepth); 
	mpOcean->setKidState  (pSeaState); 
	mpOcean->setWindSpeed (reGetXMLAttribute(pOcean, "wind_speed", 0)); 
 
	int i = 0; 
	TiXmlNode* pTrains = pNode->FirstChild("WaveTrains"); 
	TiXmlNode* pTrain = pTrains->FirstChild("WaveTrain"); 
	TiXmlNode* pElement = pTrain->ToElement(); 
	while (pElement && i < SSGA_MAX_WAVETRAIN) 
	{ 
		ssgaWaveTrain* pWaveTrain = new ssgaWaveTrain; 
 
		pWaveTrain->setSpeed      (reGetXMLAttribute(pElement, "speed", 0.0f)); 
		pWaveTrain->setLength     (reGetXMLAttribute(pElement, "length", 0.0f)); 
		pWaveTrain->setLambda     (reGetXMLAttribute(pElement, "lambda", 0.0f)); 
		pWaveTrain->setHeading    (reGetXMLAttribute(pElement, "heading", 0.0f)); 
		pWaveTrain->setWaveHeight (reGetXMLAttribute(pElement, "height", 0.0f)); 
 
		mWaveTrainList.AddTail(pWaveTrain); 
		mpOcean->setWaveTrain (i, pWaveTrain); 
 
		pElement = pElement->NextSiblingElement(); 
		i++; 
	} 
 
	mpTransform->addKid(mpOcean); 
 
	return true; 
} 
 
 
 
 
void 
reOceanEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reSceneryEntity::Update(fDeltaSec); 
 
	// Update ocean 
	mfTime += fDeltaSec; 
	mpOcean->updateAnimation(mfTime); 
} 
 
//********************************************************************** 
// 
// reSkyEntity 
// 
//********************************************************************** 
reSkyEntity::reSkyEntity() 
:	mpSky(NULL), 
	mpSunRef(NULL) 
{ 
} 
 
 
 
 
reSkyEntity::~reSkyEntity() 
{ 
	mpSunRef = NULL; 
	if (mpSky) 
	{ 
		delete mpSky; 
		mpSky = NULL; 
	} 
} 
 
 
 
 
bool 
reSkyEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reSceneryEntity::Init(pConfig)) 
		return false; 
 
	TiXmlNode* pNode = pConfig->FirstChild("reSkyEntity"); 
 
	/* 
	*/ 
 
	TiXmlNode* pDome = pNode->FirstChild("Dome"); 
	mpSky = new ssgaSky; 
	mpSky->build(reGetXMLAttribute(pDome, "h_radius", 80000), 
				 reGetXMLAttribute(pDome, "v_radius", 80000), 
				 0,		// todo: nplanets 
				 NULL, 
				 0,		// todo: nstars 
				 NULL); 
 
	TiXmlNode* pBodies = pNode->FirstChild("Bodies"); 
	TiXmlNode* pBody = pBodies->FirstChild("Body"); 
	TiXmlNode* pElement = pBody->ToElement(); 
	while (pElement) 
	{ 
		const char* psBodyTex = reGetXMLAttribute(pElement, "body_tex_path", ""); 
		if (strlen(psBodyTex) == 0) psBodyTex = NULL; 
		const char* psHaloTex = reGetXMLAttribute(pElement, "halo_tex_path", ""); 
		if (strlen(psHaloTex) == 0) psHaloTex = NULL; 
 
		ssgaCelestialBody* cb = mpSky->addBody( 
			psBodyTex, 
			psHaloTex, 
			reGetXMLAttribute(pElement, "size", 5000), 
			reGetXMLAttribute(pElement, "distance", 80000), 
			reGetXMLAttribute(pElement, "is_sun", false)); 
 
		if (reGetXMLAttribute(pElement, "is_sun", false)) 
			mpSunRef = cb; 
 
		cb->setRightAscension(reGetXMLAttribute(pElement, "right_ascension", 0)*SGD_DEGREES_TO_RADIANS); 
		cb->setDeclination(reGetXMLAttribute(pElement, "declination", 0)*SGD_DEGREES_TO_RADIANS); 
 
		pElement = pElement->NextSiblingElement(); 
	} 
 
	TiXmlNode* pClouds = pNode->FirstChild("Clouds"); 
	TiXmlNode* pCloud = pClouds->FirstChild("Cloud"); 
	pElement = pCloud->ToElement(); 
	while (pElement) 
	{ 
		const char* psCloudTex = reGetXMLAttribute(pElement, "cloud_tex_path", ""); 
		if (strlen(psCloudTex) == 0) psCloudTex = NULL; 
 
		ssgaCloudLayer* cl = mpSky->addCloud( 
			psCloudTex, 
			reGetXMLAttribute(pElement, "span", 80000), 
			reGetXMLAttribute(pElement, "elevation", 2000), 
			reGetXMLAttribute(pElement, "thickness", 100), 
			reGetXMLAttribute(pElement, "transition", 100)); 
 
		cl->setSpeed(reGetXMLAttribute(pElement, "speed", 0)); 
		cl->setDirection(reGetXMLAttribute(pElement, "direction", 0)); 
 
		pElement = pElement->NextSiblingElement(); 
	} 
 
	return true; 
} 
 
 
// todo: tidy-up 
static sgVec4 base_sky_color    = { 0.39, 0.5, 0.74, 1.0 } ; 
static sgVec4 base_fog_color    = { 0.84, 0.87, 1.0, 1.0 } ; 
 
static sgVec4 base_ambient      = { 0.6, 0.6, 0.6, 1.0 } ; 
static sgVec4 base_diffuse      = { 1.0, 1.0, 1.0, 1.0 } ; 
static sgVec4 base_specular     = { 1.0, 1.0, 1.0, 1.0 } ; 
 
static sgVec4 sky_color ; 
static sgVec4 fog_color ; 
static sgVec4 cloud_color ; 
 
static sgVec4 scene_ambient ; 
static sgVec4 scene_diffuse ; 
static sgVec4 scene_specular ; 
void 
reSkyEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reSceneryEntity::Update(fDeltaSec); 
 
	if (!mpSky) 
		return; 
 
	sgCoord CameraPos = reGetGameEngine()->GetCamera()->GetCameraPos(); 
 
	mpSky->repositionFlat(CameraPos.xyz, 0, fDeltaSec); 
	mpSky->modifyVisibility(CameraPos.xyz[SG_Z], fDeltaSec); 
 
	double sun_angle = 0; 
	double sky_brightness = 1; 
	double scene_brightness = 1; 
	if (mpSunRef) 
	{ 
		sun_angle = mpSunRef->getAngle(); 
		sky_brightness = (1.0 + cos(sun_angle))/2.0; // 0.0 - 1.0 
		scene_brightness = pow(sky_brightness,0.5); 
	} 
 
	/* set sky color */ 
	sky_color[0] = base_sky_color[0] * sky_brightness; 
	sky_color[1] = base_sky_color[1] * sky_brightness; 
	sky_color[2] = base_sky_color[2] * sky_brightness; 
	sky_color[3] = base_sky_color[3]; 
 
	/* set cloud and fog color */ 
	cloud_color[0] = fog_color[0] = base_fog_color[0] * sky_brightness; 
	cloud_color[1] = fog_color[1] = base_fog_color[1] * sky_brightness; 
	cloud_color[2] = fog_color[2] = base_fog_color[2] * sky_brightness; 
	cloud_color[3] = fog_color[3] = base_fog_color[3]; 
 
	/* repaint the sky */ 
	mpSky->repaint(sky_color, fog_color, cloud_color, sun_angle, 0, NULL, 0, NULL); 
 
	if (mpSunRef) 
	{ 
		/* set light source */ 
		sgCoord sunpos; 
		mpSunRef->getPosition(&sunpos); 
		ssgGetLight(0)->setPosition(sunpos.xyz); 
 
		scene_ambient[0] = base_ambient[0] * scene_brightness; 
		scene_ambient[1] = base_ambient[1] * scene_brightness; 
		scene_ambient[2] = base_ambient[2] * scene_brightness; 
		scene_ambient[3] = 1.0; 
 
		scene_diffuse[0] = base_diffuse[0] * scene_brightness; 
		scene_diffuse[1] = base_diffuse[1] * scene_brightness; 
		scene_diffuse[2] = base_diffuse[2] * scene_brightness; 
		scene_diffuse[3] = 1.0; 
 
		scene_specular[0] = base_specular[0] * scene_brightness; 
		scene_specular[1] = base_specular[1] * scene_brightness; 
		scene_specular[2] = base_specular[2] * scene_brightness; 
		scene_specular[3] = 1.0; 
		// GL_LIGHT_MODEL_AMBIENT has a default non-zero value so if 
		// we only update GL_AMBIENT for our lights we will never get 
		// a completely dark scene.  So, we set GL_LIGHT_MODEL_AMBIENT 
		// explicitely to black. 
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, black); 
		ssgGetLight(0)->setColour(GL_AMBIENT, scene_ambient); 
		ssgGetLight(0)->setColour(GL_DIFFUSE, scene_diffuse); 
		ssgGetLight(0)->setColour(GL_SPECULAR, scene_specular); 
	} 
} 
 
 
 
 
void 
reSkyEntity::PreDraw() 
{ 
	// todo: Adjust fog based on visibility (when in clouds) 
	//static const double m_log01 = -log( 0.01 ); 
	//static const double sqrt_m_log01 = sqrt( m_log01 ); 
	//GLfloat fog_exp2_density = sqrt_m_log01 / sky->getVisibility(); 
	//glEnable( GL_FOG ); 
	//glFogf  ( GL_FOG_DENSITY, fog_exp2_density); 
	//glFogi  ( GL_FOG_MODE,    GL_EXP2 ); 
	//glFogfv ( GL_FOG_COLOR,   fog_color ); 
 
	if (mpSky) 
	{ 
		// We need a white diffuse light for the phase of the moon 
		ssgGetLight(0)->setColour(GL_DIFFUSE, white); 
 
		mpSky->preDraw(); 
 
		// Return to the desired diffuse color 
		ssgGetLight(0)->setColour(GL_DIFFUSE, scene_diffuse); 
	} 
} 
 
 
 
 
void 
reSkyEntity::PostDraw() 
{ 
	if (mpSky) 
	{ 
		sgCoord CameraPos = reGetGameEngine()->GetCamera()->GetCameraPos(); 
		mpSky->postDraw(CameraPos.xyz[SG_Z]); // altitude 
	} 
 
} 
 
//********************************************************************** 
// 
// reBillyCartEntity 
// 
//********************************************************************** 
reBillyCartEntity::reBillyCartEntity() 
{ 
	sgZeroVec3(mWOffset); 
	sgZeroVec3(mTOffset); 
	mODEGeomGroupEx = NULL; 
} 
 
 
 
 
reBillyCartEntity::~reBillyCartEntity() 
{ 
	int i; 
 
	for (i = 0; i <= 4; i++) 
	{ 
		dJointDestroy(mODEJointEx[i]); 
	} 
 
	for (i = 0; i <= 4; i++) 
	{ 
		dGeomDestroy(mODEGeomEx[i]); 
		dBodyDestroy(mODEBodyEx[i]); 
	} 
 
	dGeomDestroy(mODEGeomGroupEx); 
} 
 
 
 
 
bool 
reBillyCartEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reLandCraftEntity::Init(pConfig)) 
		return false; 
 
	/*// Read file 
	char sLabel[RE_STRING_MAX]; 
	char sChassisModel[RE_STRING_MAX]; 
	char sWheelModel[RE_STRING_MAX]; 
	float fWRadius, fWMass, fTRadius, fTMass; 
	fscanf(pConfig, "%s\n", sLabel); 
	fscanf(pConfig, "ChassisModelName=%[^\n]\n", sChassisModel); 
	fscanf(pConfig, "WheelModelName=%[^\n]\n", sWheelModel); 
	fscanf(pConfig, "WheelGeomParams=%f,%f\n", &fWRadius, &fWMass); 
	fscanf(pConfig, "WheelFROffset=%f,%f,%f\n", 
		&mWOffset[0], &mWOffset[1], &mWOffset[2]); 
	fscanf(pConfig, "TuxGeomParams=%f,%f\n", &fTRadius, &fTMass); 
	fscanf(pConfig, "TuxOffset=%f,%f,%f\n", 
		&mTOffset[0], &mTOffset[1], &mTOffset[2]); 
 
	// Get models 
	ssgEntity* pBodyObj = reGetModelManager()->GetModel(sChassisModel); 
	ssgEntity* pWheelObj = reGetModelManager()->GetModel(sWheelModel); 
	mpTransform->addKid(pBodyObj); 
 
	int i; 
	dMass m; 
 
	for (i = 0; i <= 3; i++) 
	{ 
		mpWheelTransform[i] = new ssgTransform; 
		mpWheelTransform[i]->addKid(pWheelObj); 
		addKid(mpWheelTransform[i]); 
	} 
 
	// Create wheel body & geom 
	for (i = 0; i <= 3; i++) 
	{ 
		mODEBodyEx[i] = dBodyCreate(reGetODEWorldId()); 
		dQuaternion q; 
		dQFromAxisAndAngle(q, 1, 0, 0, M_PI*0.5); 
		dBodySetQuaternion(mODEBodyEx[i], q); 
		dMassSetSphere(&m, 1, fWRadius); 
		dMassAdjust(&m, fWMass); 
		dBodySetMass(mODEBodyEx[i], &m); 
		mODEGeomEx[i] = dCreateSphere(0, fWRadius); 
		dGeomSetBody(mODEGeomEx[i], mODEBodyEx[i]); 
 
		// Attempt to stop wheels freaking out 
		dBodySetFiniteRotationMode(mODEBodyEx[i], 1); 
	} 
	dBodySetPosition(mODEBodyEx[0],  mWOffset[0],  mWOffset[1], mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[1], -mWOffset[0],  mWOffset[1], mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[2],  mWOffset[0], -mWOffset[1], mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[3], -mWOffset[0], -mWOffset[1], mWOffset[2]); 
 
	// Create tux body & geom 
	mODEBodyEx[4] = dBodyCreate(reGetODEWorldId()); 
	dBodySetPosition(mODEBodyEx[4], mTOffset[0], mTOffset[1], mTOffset[2]); 
	dMassSetSphere(&m, 1, fTRadius); 
	dMassAdjust(&m, fTMass); 
	dBodySetMass(mODEBodyEx[4], &m); 
	mODEGeomEx[4] = dCreateSphere(0, fTRadius); 
	dGeomSetBody(mODEGeomEx[4], mODEBodyEx[4]); 
 
	// Create front and back wheel hinges 
	for (i = 0; i <= 3; i++) 
	{ 
		mODEJointEx[i] = dJointCreateHinge2(reGetODEWorldId(),0); 
		dJointAttach(mODEJointEx[i], mODEBody, mODEBodyEx[i]); 
		const dReal *a = dBodyGetPosition(mODEBodyEx[i]); 
		dJointSetHinge2Anchor(mODEJointEx[i], a[0], a[1], a[2]); 
		dJointSetHinge2Axis1(mODEJointEx[i], 0, 0, 1); 
		dJointSetHinge2Axis2(mODEJointEx[i], 1, 0, 0); 
 
		// Set joint suspension 
		//dJointSetHinge2Param(mODEJointEx[i], dParamSuspensionERP, 0.4f); 
		//dJointSetHinge2Param(mODEJointEx[i], dParamSuspensionCFM, 0.8f); 
		dJointSetHinge2Param(mODEJointEx[i], dParamSuspensionERP, 0.8f); 
		dJointSetHinge2Param(mODEJointEx[i], dParamSuspensionCFM, 0.2f); 
 
		// Lock back 'wheels' along the steering axis 
		if (i >= 2) 
		{ 
			// Set stops to make sure 'wheels' always stay in alignment 
			dJointSetHinge2Param(mODEJointEx[i], dParamLoStop, 0); 
			dJointSetHinge2Param(mODEJointEx[i], dParamHiStop, 0); 
		} 
	} 
 
	// Create tux joint 
	mODEJointEx[4] = dJointCreateFixed(reGetODEWorldId(), 0); 
	dJointAttach(mODEJointEx[4], mODEBody, mODEBodyEx[4]); 
	dJointSetFixed(mODEJointEx[4]); 
 
	// Create geometry group and add it to the space 
	// todo: mODEGeom should really be added to the group too !!! 
	//       need to fix the way body & body extensions are created 
	//       this should all be in reGameEntity 
	mODEGeomGroupEx = dCreateGeomGroup(reGetODESpaceId()); 
	for (i = 0; i <= 4; i++) 
	{ 
		dGeomGroupAdd(mODEGeomGroupEx, mODEGeomEx[i]); 
	}*/ 
 
	return true; 
} 
 
 
 
 
void 
reBillyCartEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reLandCraftEntity::Update(fDeltaSec); 
 
	// Get position 
	sgCoord p; 
	GetPosition(&p); 
 
	// Setup rotation matricies 
	// - R1 useful for conversion between body-to-world coords 
	// - R2 useful for conversion between world-to-body coords 
	sgMat4 R1, R2; 
	sgMakeRotMat4(R1, p.hpr); 
	sgCopyMat4(R2, R1); 
	sgInvertMat4(R2); 
 
	// Update power change 
	reUpdateValue(mfPower, 
		mbForward, mbReverse, 
		mPowerFactor[0], mPowerFactor[1], 
		mPowerDampFactor[0], 1.0f, -1.0f, fDeltaSec); 
 
	// Update heading change 
	reUpdateValue(mDirChange[0], 
		mbHeadingLeft, mbHeadingRight, 
		mAngularPowerFactor[0], mAngularPowerFactor[0], 
		mAngularDampFactor[0], 1.0f, -1.0f, fDeltaSec); 
 
	float speed = -mfPower*100.0f; 
	float steer = -mDirChange[0]; 
 
	// Breaking 
  	/*if (mfPower < 0) 
	{ 
		for (i = 0; i <= 1; i++) 
		{ 
			dJointSetHinge2Param(mODEJointEx[i], dParamVel2, 0); 
			dJointSetHinge2Param(mODEJointEx[i], dParamFMax2, -mfPower*0.9); 
		} 
	} 
	else 
	{ 
		for (i = 0; i <= 1; i++) 
		{ 
			dJointSetHinge2Param(mODEJointEx[0], dParamFMax2, 0); 
		} 
	}*/ 
 
	// Motor 
	int i; 
	for (i = 0; i <= 1; i++) 
	{ 
		dJointSetHinge2Param(mODEJointEx[i], dParamVel2, -speed); 
		dJointSetHinge2Param(mODEJointEx[i], dParamFMax2, 0.1); 
	} 
 
    // Steering 
	dReal v[2]; 
 	for (i = 0; i <= 1; i++) 
	{ 
		v[i] = dJointGetHinge2Angle1(mODEJointEx[i]); 
		if (v[i] > 0.1) v[i] = 0.1; 
		if (v[i] < -0.1) v[i] = -0.1; 
	} 
	dReal vd = fabs(v[0] - v[1]); 
	//printf("b4 %.3f %.3f %.3f\n", v[0], v[1], vd); 
	if (v[0] > v[1]) 
	{ 
		v[0] -= vd/4; 
		v[1] += vd/4; 
	} 
	else if (v[0] < v[1]) 
	{ 
		v[0] += vd/4; 
		v[1] -= vd/4; 
	} 
	//vd = fabs(v[0] - v[1]); 
	//printf("af %.3f %.3f %.3f\n", v[0], v[1], vd); 
 	for (i = 0; i <= 1; i++) 
	{ 
		v[i] = steer - v[i]; 
		v[i] *= 10; 
		dJointSetHinge2Param(mODEJointEx[i], dParamVel, v[i]); 
		dJointSetHinge2Param(mODEJointEx[i], dParamFMax, 0.2); 
		dJointSetHinge2Param(mODEJointEx[i], dParamLoStop, -0.75); 
		dJointSetHinge2Param(mODEJointEx[i], dParamHiStop, 0.75); 
		dJointSetHinge2Param(mODEJointEx[i], dParamFudgeFactor, 0.1); 
	} 
 	/*for (i = 0; i <= 1; i++) 
	{ 
		dReal v = steer - dJointGetHinge2Angle1(mODEJointEx[i]); 
		if (v > 0.1) v = 0.1; 
		if (v < -0.1) v = -0.1; 
		v *= 10.0; 
		dJointSetHinge2Param(mODEJointEx[i], dParamVel, v); 
		dJointSetHinge2Param(mODEJointEx[i], dParamFMax, 0.2); 
		dJointSetHinge2Param(mODEJointEx[i], dParamLoStop, -0.75); 
		dJointSetHinge2Param(mODEJointEx[i], dParamHiStop, 0.75); 
		dJointSetHinge2Param(mODEJointEx[i], dParamFudgeFactor, 0.1); 
	}*/ 
 
	for (i = 2; i <= 3; i++) 
	{ 
		// Attempt to stop wheels freaking out 
		dBodySetFiniteRotationMode(mODEBodyEx[i], 1); 
		dBodySetFiniteRotationAxis(mODEBodyEx[i], 0, 0, 0); 
	} 
 
	// Get the ODE wheel position and set wheel transforms 
 	for (i = 0; i <= 3; i++) 
	{ 
		reGetBodyTransform(mpWheelTransform[i], mODEBodyEx[i]); 
	} 
} 
 
 
 
 
void 
reBillyCartEntity::SpaceCollide() 
{ 
	ssgHit* pHitList; 
	int iNumHits = GetHits(&pHitList); 
 
	// Wheel collision check 
	for (int i = 0; i <= 3; i++) 
	{ 
		reTriangleSphereContact(iNumHits, pHitList, mODEBodyEx[i], mODEGeomEx[i]); 
	} 
 
	// Tux collision check 
	reTriangleSphereContact(iNumHits, pHitList, mODEBodyEx[4], mODEGeomEx[4]); 
 
	// Chassis collision check 
	reTriangleBoxContact( 
		iNumHits, 
		pHitList, 
		mODEBody, 
		mODEGeom); 
} 
 
 
 
 
void 
reBillyCartEntity::Keyboard(unsigned char key, int x, int y, bool bKeyDown) 
{ 
	// Call base class 
	// todo: maybe!? 
	// none ... yet 
} 
 
 
 
 
void 
reBillyCartEntity::KeyboardSpecial(int key, int x, int y, bool bKeyDown) 
{ 
	// Call base class 
	// todo: maybe!? 
 
	switch (key) 
	{ 
	case GLUT_KEY_UP: // forward 
		if (!glutGetModifiers()) 
			SetForward(bKeyDown); 
		else 
			SetForward(false); 
		break; 
 
	case GLUT_KEY_DOWN: // reverse 
		if (!glutGetModifiers()) 
			SetReverse(bKeyDown); 
		else 
			SetReverse(false); 
		break; 
 
	case GLUT_KEY_LEFT: // heading left 
		if (!glutGetModifiers()) 
			SetHeadingLeft(bKeyDown); 
		else 
			SetHeadingLeft(false); 
		break; 
 
	case GLUT_KEY_RIGHT: // heading right 
		if (!glutGetModifiers()) 
			SetHeadingRight(bKeyDown); 
		else 
			SetHeadingRight(false); 
		break; 
 
	default: 
		break; 
	} 
} 
 
 
 
 
void 
reBillyCartEntity::SetPosition(sgCoord* p) 
{ 
	// Call base class 
	reLandCraftEntity::SetPosition(p); 
 
	dBodySetPosition(mODEBodyEx[0], p->xyz[SG_X]+mWOffset[0], p->xyz[SG_Y]+mWOffset[1], p->xyz[SG_Z]+mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[1], p->xyz[SG_X]-mWOffset[0], p->xyz[SG_Y]+mWOffset[1], p->xyz[SG_Z]+mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[2], p->xyz[SG_X]+mWOffset[0], p->xyz[SG_Y]-mWOffset[1], p->xyz[SG_Z]+mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[3], p->xyz[SG_X]-mWOffset[0], p->xyz[SG_Y]-mWOffset[1], p->xyz[SG_Z]+mWOffset[2]); 
	dBodySetPosition(mODEBodyEx[4], p->xyz[SG_X]+mTOffset[0], p->xyz[SG_Y]+mTOffset[1], p->xyz[SG_Z]+mTOffset[2]); 
}; 
 
//********************************************************************** 
// 
// reBoatEntity 
// 
//********************************************************************** 
reBoatEntity::reBoatEntity() 
{ 
	mpSpray = NULL; 
} 
 
 
 
 
reBoatEntity::~reBoatEntity() 
{ 
	int i; 
 
	if (mpSpray) 
	{ 
		reGetWorld()->removeKid(mpSpray); 
		ssgDeRefDelete(mpSpray); 
	} 
 
	for (i = 0; i <= 1; i++) 
	{ 
		dJointDestroy(mODEJointEx[i]); 
	} 
 
	for (i = 0; i <= 1; i++) 
	{ 
		dGeomDestroy(mODEGeomEx[i]); 
		dBodyDestroy(mODEBodyEx[i]); 
	} 
 
	dGeomDestroy(mODEGeomGroupEx); 
} 
 
 
 
 
bool 
reBoatEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reWaterCraftEntity::Init(pConfig)) 
		return false; 
 
	const char* psModelName = reGetXMLChildValue(pConfig, "ModelName", ""); 
	TiXmlNode* pParticle = pConfig->FirstChild("Particle"); 
	bool bParticle = reGetXMLAttribute(pParticle, "enabled", false); 
	const char* psParticleFile = reGetXMLAttribute(pParticle, "file", ""); 
 
	// Get model 
	ssgEntity* pBodyObj = reGetModelManager()->GetModel(psModelName); 
	mpTransform->addKid(pBodyObj); 
 
	// Create boat spray 
	if (bParticle) 
	{ 
		mpSpray = (reParticleEntity*)reGameEntity::Create(psParticleFile); 
		reGetWorld()->addKid(mpSpray); 
	} 
 
	int i; 
	dMass m; 
 
	// Create front/back end sphere 
	for (i = 0; i <= 1; i++) 
	{ 
		mODEBodyEx[i] = dBodyCreate(reGetODEWorldId()); 
		dMassSetSphere(&m, 1, 0.15); 
		dMassAdjust(&m, 0.1); 
		dBodySetMass(mODEBodyEx[i], &m); 
		mODEGeomEx[i] = dCreateSphere(0, 0.15); 
		dGeomSetBody(mODEGeomEx[i], mODEBodyEx[i]); 
	} 
	dBodySetPosition(mODEBodyEx[0], 0,  0.5, 0); 
	dBodySetPosition(mODEBodyEx[1], 0, -0.5, 0); 
 
	// Create joint 
	for (i = 0; i <= 1; i++) 
	{ 
		mODEJointEx[i] = dJointCreateFixed(reGetODEWorldId(), 0); 
		dJointAttach(mODEJointEx[i], mODEBody, mODEBodyEx[i]); 
		dJointSetFixed(mODEJointEx[i]); 
	} 
 
	// Create geometry group and add it to the space 
	// todo: mODEGeom should really be added to the group too !!! 
	//       need to fix the way body & body extensions are created 
	//       this should all be in reGameEntity 
	mODEGeomGroupEx = dCreateGeomGroup(reGetODESpaceId()); 
	for (i = 0; i <= 1; i++) 
	{ 
		dGeomGroupAdd(mODEGeomGroupEx, mODEGeomEx[i]); 
	} 
 
	return true; 
} 
 
 
 
 
void 
reBoatEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reWaterCraftEntity::Update(fDeltaSec); 
 
	// Get position 
	sgCoord p; 
	GetPosition(&p); 
	float fAltitude = reGetAltitude(reGetWorld(), p.xyz); 
 
	// Setup rotation matricies 
	// - R1 useful for conversion between body-to-world coords 
	// - R2 useful for conversion between world-to-body coords 
	sgMat4 R1, R2; 
	sgMakeRotMat4(R1, p.hpr); 
	sgCopyMat4(R2, R1); 
	sgInvertMat4(R2); 
 
	// Update power change 
	reUpdateValue(mfPower, 
		mbForward, mbReverse, 
		mPowerFactor[0], mPowerFactor[1], 
		mPowerDampFactor[0], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Update heading change 
	reUpdateValue(mDirChange[0], 
		mbHeadingLeft, mbHeadingRight, 
		mAngularPowerFactor[0], mAngularPowerFactor[0], 
		mAngularDampFactor[0], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Update pitch change 
	reUpdateValue(mDirChange[1], 
		mbPitchUp, mbPitchDown, 
		mAngularPowerFactor[1], mAngularPowerFactor[1], 
		mAngularDampFactor[1], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Update roll change 
	reUpdateValue(mDirChange[2], 
		mbRollRight, mbRollLeft, 
		mAngularPowerFactor[2], mAngularPowerFactor[2], 
		mAngularDampFactor[2], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Check if touching surface 
	// todo: we actually need to know what surface we are touching 
	float fPower; 
	sgVec3 Torque; 
	if (fAltitude <= 0.2f) 
	{ 
		// Power on, pitch off, roll on, heading on 
		fPower = mfPower; 
		sgSetVec3(Torque, 
			0, 
			mDirChange[2]*mMaxAngularThrust[2], 
			mDirChange[0]*mMaxAngularThrust[0]); 
	} 
	else // not touching surface 
	{ 
		// Power off, pitch on, roll on, heading off 
		fPower = 0; 
		sgSetVec3(Torque, 
			mDirChange[1]*mMaxAngularThrust[1], 
			mDirChange[2]*mMaxAngularThrust[2], 
			0); 
	} 
 
	// Apply a force to move the entity 
	// - calculate force (assume entity is facing in the +y direction) 
	// - transform force from body-to-world coordinates 
	// - apply force 
	sgVec3 Force; 
	if (fPower > 0.0f) 
		sgSetVec3(Force, 0.0f, fPower*mMaxThrust[0], 0.0f); 
	else if (fPower < 0.0f) 
		sgSetVec3(Force, 0.0f, fPower*mMaxThrust[1], 0.0f); 
	else 
		sgZeroVec3(Force); 
	sgXformVec3(Force, R1); 
	dBodyAddForce(mODEBody, Force[SG_X], Force[SG_Y], Force[SG_Z]); 
	//printf("Force <%0.2f>\n", sgLengthVec3(Force)); 
 
	// Apply a torque to rotate the entity 
	// - calculate torque (already done above) 
	// - transform torque from body-to-world coordinates 
	// - apply torque 
	sgXformVec3(Torque, R1); 
	dBodyAddTorque(mODEBody, 
		Torque[0], 
		Torque[1], 
		Torque[2]); 
	//printf("Torque <%0.2f:%0.2f:%0.2f>\n", Torque[0], Torque[1], Torque[2]); 
 
	// Update spray 
	GetPosition(&p); 
 
	if (mpSpray) 
	{ 
		mpSpray->SetPosition(&p); 
		//if (fPower == 0 || fAltitude > 0.2) 
		//	mpSpray->SetCreationRate(0); 
		//else 
			mpSpray->SetCreationRate(fPower*100.0f); 
		mpSpray->Update(fDeltaSec); 
	} 
} 
 
 
 
 
void 
reBoatEntity::SpaceCollide() 
{ 
	ssgHit* pHitList; 
	int iNumHits = GetHits(&pHitList); 
	reTriangleBoxContact(iNumHits, pHitList, mODEBody, mODEGeom); 
 
	// Front/back end collision check 
	for (int i = 0; i <= 1; i++) 
	{ 
		reTriangleSphereContact(iNumHits, pHitList, mODEBodyEx[i], mODEGeomEx[i]); 
	} 
} 
 
 
 
 
void 
reBoatEntity::Keyboard(unsigned char key, int x, int y, bool bKeyDown) 
{ 
	// Call base class 
	// todo: maybe!? 
 
	switch (key) 
	{ 
	case ',': // roll left 
	case '<': 
		SetRollLeft(bKeyDown); 
		break; 
 
	case '.': // roll right 
	case '>': 
		SetRollRight(bKeyDown); 
		break; 
 
	default: 
		break; 
	} 
} 
 
 
 
 
void 
reBoatEntity::KeyboardSpecial(int key, int x, int y, bool bKeyDown) 
{ 
	// Call base class 
	// todo: maybe!? 
 
	switch (key) 
	{ 
	case GLUT_KEY_UP: // forward & pitch down 
		if (!glutGetModifiers()) 
		{ 
			SetForward(bKeyDown); 
			SetPitchDown(bKeyDown); 
		} 
		else 
		{ 
			SetForward(false); 
			SetPitchDown(false); 
		} 
		break; 
 
	case GLUT_KEY_DOWN: // reverse & pitch up 
		if (!glutGetModifiers()) 
		{ 
			SetReverse(bKeyDown); 
			SetPitchUp(bKeyDown); 
		} 
		else 
		{ 
			SetReverse(false); 
			SetPitchUp(false); 
		} 
		break; 
 
	case GLUT_KEY_LEFT: // heading left 
		if (!glutGetModifiers()) 
			SetHeadingLeft(bKeyDown); 
		else 
			SetHeadingLeft(false); 
		break; 
 
	case GLUT_KEY_RIGHT: // heading right 
		if (!glutGetModifiers()) 
			SetHeadingRight(bKeyDown); 
		else 
			SetHeadingRight(false); 
		break; 
 
	default: 
		break; 
	} 
} 
 
 
 
 
void 
reBoatEntity::SetPosition(sgCoord* p) 
{ 
	// Call base class 
	reWaterCraftEntity::SetPosition(p); 
 
	dBodySetPosition(mODEBodyEx[0], p->xyz[SG_X], p->xyz[SG_Y]+0.5, p->xyz[SG_Z]); 
	dBodySetPosition(mODEBodyEx[1], p->xyz[SG_X], p->xyz[SG_Y]-0.5, p->xyz[SG_Z]); 
}; 
 
//********************************************************************** 
// 
// reSpaceShipEntity 
// 
//********************************************************************** 
reSpaceShipEntity::reSpaceShipEntity() 
{ 
} 
 
 
 
 
reSpaceShipEntity::~reSpaceShipEntity() 
{ 
} 
 
 
 
 
bool 
reSpaceShipEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reSpaceCraftEntity::Init(pConfig)) 
		return false; 
 
	const char* psModelName = reGetXMLChildValue(pConfig, "ModelName", ""); 
 
	// Get model 
	ssgEntity* pBodyObj = reGetModelManager()->GetModel(psModelName); 
	mpTransform->addKid(pBodyObj); 
 
	return true; 
} 
 
 
 
 
void 
reSpaceShipEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reSpaceCraftEntity::Update(fDeltaSec); 
 
	// Get position 
	sgCoord p; 
	GetPosition(&p); 
 
	// Setup rotation matricies 
	// - R1 useful for conversion between body-to-world coords 
	// - R2 useful for conversion between world-to-body coords 
	sgMat4 R1, R2; 
	sgMakeRotMat4(R1, p.hpr); 
	sgCopyMat4(R2, R1); 
	sgInvertMat4(R2); 
 
	// Update power change 
	reUpdateValue(mfPower, 
		mbForward, mbReverse, 
		mPowerFactor[0], mPowerFactor[1], 
		mPowerDampFactor[0], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Update heading change 
	reUpdateValue(mDirChange[0], 
		mbHeadingLeft, mbHeadingRight, 
		mAngularPowerFactor[0], mAngularPowerFactor[0], 
		mAngularDampFactor[0], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Update pitch change 
	reUpdateValue(mDirChange[1], 
		mbPitchUp, mbPitchDown, 
		mAngularPowerFactor[1], mAngularPowerFactor[1], 
		mAngularDampFactor[1], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Update roll change 
	reUpdateValue(mDirChange[2], 
		mbRollRight, mbRollLeft, 
		mAngularPowerFactor[2], mAngularPowerFactor[2], 
		mAngularDampFactor[2], 
		1.0f, -1.0f, 
		fDeltaSec); 
 
	// Apply a force to move the entity 
	// - calculate force (assume entity is facing in the +y direction) 
	// - transform force from body-to-world coordinates 
	// - apply force 
	sgVec3 Force; 
	if (mfPower > 0.0f) 
		sgSetVec3(Force, 0.0f, mfPower*mMaxThrust[0], 0.0f); 
	else if (mfPower < 0.0f) 
		sgSetVec3(Force, 0.0f, mfPower*mMaxThrust[1], 0.0f); 
	else 
		sgZeroVec3(Force); 
	sgXformVec3(Force, R1); 
	dBodyAddForce(mODEBody, Force[SG_X], Force[SG_Y], Force[SG_Z]); 
	//printf("Force <%0.2f>\n", sgLengthVec3(Force)); 
 
	// Apply a torque to rotate the entity 
	// - calculate torque 
	// - transform torque from body-to-world coordinates 
	// - apply torque 
	// Power on, pitch off, roll on, heading on 
	sgVec3 Torque; 
	sgSetVec3(Torque, 
		mDirChange[1]*mMaxAngularThrust[1], 
		mDirChange[2]*mMaxAngularThrust[2], 
		mDirChange[0]*mMaxAngularThrust[0]); 
	sgXformVec3(Torque, R1); 
	dBodyAddTorque(mODEBody, 
		Torque[0], 
		Torque[1], 
		Torque[2]); 
	//printf("Torque <%0.2f:%0.2f:%0.2f>\n", Torque[0], Torque[1], Torque[2]); 
} 
 
 
 
 
void 
reSpaceShipEntity::SpaceCollide() 
{ 
	ssgHit* pHitList; 
	int iNumHits = GetHits(&pHitList); 
	reTriangleBoxContact(iNumHits, pHitList, mODEBody, mODEGeom); 
 
	//reTriangleSphereContact(iNumHits, pHitList, mODEBody2, mODEGeom2); 
} 
 
 
 
 
void 
reSpaceShipEntity::Keyboard(unsigned char key, int x, int y, bool bKeyDown) 
{ 
	// Call base class 
	// todo: maybe!? 
 
	switch (key) 
	{ 
	case 'a': // forward 
	case 'A': 
		SetForward(bKeyDown); 
		break; 
 
	case 'z': // reverse 
	case 'Z': 
		SetReverse(bKeyDown); 
		break; 
 
	case ',': // heading left 
	case '<': 
		SetHeadingLeft(bKeyDown); 
		break; 
 
	case '.': // heading right 
	case '>': 
		SetHeadingRight(bKeyDown); 
		break; 
 
	default: 
		break; 
	} 
} 
 
 
 
 
void 
reSpaceShipEntity::KeyboardSpecial(int key, int x, int y, bool bKeyDown) 
{ 
	// Call base class 
	// todo: maybe!? 
 
	switch (key) 
	{ 
	case GLUT_KEY_UP: // pitch down 
		if (!glutGetModifiers()) 
			SetPitchDown(bKeyDown); 
		else 
			SetPitchDown(false); 
		break; 
 
	case GLUT_KEY_DOWN: // pitch up 
		if (!glutGetModifiers()) 
			SetPitchUp(bKeyDown); 
		else 
			SetPitchUp(false); 
		break; 
 
	case GLUT_KEY_LEFT: // roll left 
		if (!glutGetModifiers()) 
			SetRollLeft(bKeyDown); 
		else 
			SetRollLeft(false); 
		break; 
 
	case GLUT_KEY_RIGHT: // roll right 
		if (!glutGetModifiers()) 
			SetRollRight(bKeyDown); 
		else 
			SetRollRight(false); 
		break; 
 
	default: 
		break; 
	} 
} 
 
//********************************************************************** 
// 
// rePlasmaBoltEntity 
// 
//********************************************************************** 
rePlasmaBoltEntity::rePlasmaBoltEntity() 
{ 
} 
 
 
 
 
rePlasmaBoltEntity::~rePlasmaBoltEntity() 
{ 
} 
 
 
 
 
bool 
rePlasmaBoltEntity::Init(TiXmlNode* pConfig) 
{ 
	// Call base class 
	if (!reWeaponEntity::Init(pConfig)) 
		return false; 
 
	const char* psModelName = reGetXMLChildValue(pConfig, "ModelName", ""); 
 
	// Get model 
	ssgEntity* pBodyObj = reGetModelManager()->GetModel(psModelName); 
	mpTransform->addKid(pBodyObj); 
 
	return true; 
} 
 
 
 
 
void 
rePlasmaBoltEntity::Update(float fDeltaSec) 
{ 
	// Call base class 
	reWeaponEntity::Update(fDeltaSec); 
 
	// Update plasma bolt 
	// todo 
} 
 
 
 
 
void 
rePlasmaBoltEntity::SpaceCollide() 
{ 
	ssgHit* pHitList; 
	int iNumHits = GetHits(&pHitList); 
	reTriangleBoxContact(iNumHits, pHitList, mODEBody, mODEGeom); 
} 
 
//********************************************************************** 
// 
// reUpdateValue 
// 
//********************************************************************** 
void 
reUpdateValue(float& fValue, 
			  bool bIncFactor1, 
			  bool bDecFactor2, 
			  float fIncFactor1, 
			  float fDecFactor2, 
			  float fDampFactor, 
			  float fMaxValue, 
			  float fMinValue, 
			  float fDeltaSec) 
{ 
	if (bIncFactor1) 
		fValue += fIncFactor1*fDeltaSec; 
 
	if (bDecFactor2) 
		fValue -= fDecFactor2*fDeltaSec; 
 
	if (!bIncFactor1 && !bDecFactor2 && fDampFactor != 0.0f) 
	{ 
		if (fValue > 0.0f) 
		{ 
			fValue -= fDampFactor*fDeltaSec; 
 
			if (fValue < 0.0f) 
				fValue = 0.0f; 
		} 
		else if (fValue < 0.0f) 
		{ 
			fValue += fDampFactor*fDeltaSec; 
 
			if (fValue > 0.0f) 
				fValue = 0.0f; 
		} 
	} 
 
	if (fValue > fMaxValue) 
	{ 
		fValue = fMaxValue; 
	} 
	else if (fValue < fMinValue) 
	{ 
		fValue = fMinValue; 
	} 
}