/*!
	@project Rune
	@file RuHelper_Entity.cpp

	Copyright (c) 2004-2005 All rights reserved

	@author John Tang
	@date 2005/04/13
*/

#include "../Helper/RuHelper_Entity.h"

#include "../Scene/Controller/RuController_Entity.h"
#include "../Scene/Controller/RuController_GhostTrail.h"
#include "../Scene/Controller/RuController_ParticleSystem.h"

#include "../Symphony/RuSymphony_Base.h"

//#include "../../../Interface/chat/ChatFrame.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

/*!
	Anchors an entity to the specified anchor object. Anchoring is the process where a
	character is affixed to a walking surface. This is done along the Y axis only. This
	function will take entity's current location and cast rays downward to locate the
	entity's anchored Y position.

	Assumption is made that the entity is the root frame. That is, the entity's local
	transform is identical to its world transform.

	@param entity Entity to be anchored.
	@param anchorObject Entity to serve as the anchoring surface.
	@param entityMassOffset Entity's center of mass offset.
	@param testIncrement Increment by which to raise the test ray origin at each iteration.
	@param testRange Maximum test height.

	@return TRUE if the entity was successfully anchored, FALSE otherwise.
*/
BOOL RuEntity_Anchor(CRuEntity *entity, IRuObject *anchorObject, REAL entityMassOffset, REAL testIncrement, REAL testRange)
{
	CRuCollisionQuery_Sphere_Anchor anchorQuery(entity->GetTranslation(), testRange, 1.0f);
	anchorQuery.m_exclusionMask = 0x00200000;

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&anchorQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&anchorQuery);
	}

	if(anchorQuery.m_anchorable)
	{
		// Build position vector
		CRuVector3 entityPosition = entity->GetTranslation();
		entityPosition.y = anchorQuery.m_anchorPoint;

		// Reset location
		entity->SetTranslation(entityPosition);

		return TRUE;
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Anchors an entity to the specified anchor object. Anchoring is the process where a
	character is affixed to a walking surface. This is done along the Y axis only. This
	function will take entity's current location and cast rays downward to locate the
	entity's anchored Y position.

	The entity's forward orientation is also adjusted during the anchoring process.
	Its default forward vector will be rotated to match the current forward vector.
	
	Assumption is made that the entity is the root frame. That is, the entity's local
	transform is identical to its world transform.

	@param entity Entity to be anchored.
	@param anchorObject Entity to serve as the anchoring surface.
	@param defaultForward Default forward orientation of the entity to be anchored.
	@param entityForward Current forward orientation of the entity to be anchored.
	@param entityMassOffset Entity's center of mass offset.
	@param testIncrement Increment by which to raise the test ray origin at each iteration.
	@param testRange Maximum test height.

	@return TRUE if the entity was successfully anchored, FALSE otherwise.
*/
BOOL RuEntity_Anchor(CRuEntity *entity, IRuObject *anchorObject, const CRuVector3 &defaultForward, const CRuVector3 &entityForward, REAL entityMassOffset, REAL testIncrement, REAL testRange)
{
	CRuCollisionQuery_Sphere_Anchor anchorQuery(entity->GetTranslation(), testRange, 1.0f);
	anchorQuery.m_exclusionMask = 0x00200000;

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&anchorQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&anchorQuery);
	}

	if(anchorQuery.m_anchorable)
	{
		// Build position vector
		CRuVector3 entityPosition = entity->GetTranslation();
		entityPosition.y = anchorQuery.m_anchorPoint;

		// Rebuild orientation
		entity->ResetLocalTransform();
		RuEntity_Orient(entity, defaultForward, entityForward, 1.0f);
		entity->Translate(entityPosition);

		return TRUE;
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Finds the anchor point for an entity on the specified anchor object. Anchoring is the
	process where a character is affixed to a walking surface. This is done along the Y axis
	only. This function will take entity's current location and cast rays downward to locate
	the entity's anchored Y position.

	Assumption is made that the entity is the root frame. That is, the entity's local
	transform is identical to its world transform.

	@param entity Position of the entity to be anchored.
	@param anchorObject Entity to serve as the anchoring surface.
	@param entityMassOffset Entity's center of mass offset.
	@param testIncrement Increment by which to raise the test ray origin at each iteration.
	@param testRange Maximum test height.

	@return Y position of the anchoring point, or -FLT_MAX if no anchor point was found.
*/
REAL RuEntity_FindAnchorPoint(const CRuVector3 &entityPosition, IRuObject *anchorObject, REAL entityMassOffset, REAL testIncrement, REAL testRange)
{
	CRuCollisionQuery_Sphere_Anchor anchorQuery(entityPosition, testRange, 1.0f);
	anchorQuery.m_exclusionMask = 0x00A00000;		// Exclude water and foliage surfaces

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&anchorQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&anchorQuery);
	}

	if(anchorQuery.m_anchorable)
	{
		return anchorQuery.m_anchorPoint;
	}

	return -FLT_MAX;
}

// ************************************************************************************************************************************************************

/*!
	Calculates the movement vector for the specified entity given its forward vector by the specified delta.

	@param entity Entity to be moved.
	@param defaultForward Default forward orientation of the entity to be moved.
	@param forwardDelta Forward movement delta.
	@param rightDelta Right movement delta.
	@param moveVectorOut Output vector.

	@return TRUE if the entity was successfully moved, FALSE otherwise.
*/
BOOL RuEntity_CalculateMoveVector(CRuEntity *entity, const CRuVector3 &defaultForward, REAL forwardDelta, REAL rightDelta, CRuVector3 *moveVectorOut)
{
	// Calculate entity forward vector
	CRuVector3 entityForward = RuEntity_CalculateForwardVector(entity, defaultForward);

	// Forward/backward movement
	*moveVectorOut = entityForward * forwardDelta;

	// Right/left movement
	*moveVectorOut = *moveVectorOut + CrossProduct(CRuVector3(0.0f, 1.0f, 0.0f), entityForward) * rightDelta;

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Moves the specified entity along its forward and right vectors by the specified delta.

	@param entity Entity to be moved.
	@param defaultForward Default forward orientation of the entity to be moved.
	@param forwardDelta Forward movement delta.
	@param rightDelta Right movement delta.

	@return TRUE if the entity was successfully moved, FALSE otherwise.
*/
BOOL RuEntity_Move(CRuEntity *entity, const CRuVector3 &defaultForward, REAL forwardDelta, REAL rightDelta)
{
	// Calculate entity forward vector
	CRuVector3 entityForward = RuEntity_CalculateForwardVector(entity, defaultForward);

	// Forward/backward movement
	CRuVector3 moveVec = entityForward * forwardDelta;

	// Right/left movement
	moveVec = moveVec + CrossProduct(CRuVector3(0.0f, 1.0f, 0.0f), entityForward) * rightDelta;

	entity->Translate(moveVec);

	return TRUE;
}

// ************************************************************************************************************************************************************

BOOL RuEntity_CalculateCollideOffsetVector(const CRuSphere &collideSphere, IRuObject *anchorObject, CRuVector3 *offsetVectorOut)
{
	// Query collision against glide sphere
	CRuCollisionQuery_Sphere_SlideTest sphereQuery;
	sphereQuery.m_exclusionMask = 0x00200000;
	sphereQuery.m_colSphere = collideSphere;

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&sphereQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&sphereQuery);
	}
/*
	if(sphereQuery.m_positiveHit)
	{
		// Save offset vector
		CRuVector3 offsetVector = sphereQuery.m_colNormal;
		if(offsetVector.SquaredMagnitude() > ruEPSILON)
			offsetVector.Normalize();

		// Output offset vector
		*offsetVectorOut = offsetVector;

		return TRUE;
	}
*/
	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Entity movement sample.

	Movement Procedure:

	1. Abort if both of the movement deltas are zero.
	2. Save entity position as the entity's previous position.
	3. Check collision against the glide sphere. If collision occurs, push entity back along
	   the collision normal. If no collision occurs, do nothing.
	4. Move entity along movement deltas.
	5. Check for collision. If there is no collision, save new location as the entity's previous position.
	6. Anchor entity.
	7. Check for collision. If there is no collision, accept new location. If there is location,
	   restore location to the entity's previous position.

	@param entity Entity to be moved.
	@param anchorObject Entity to serve as the anchoring surface.
	@param defaultForward Default forward orientation of the entity to be moved.
	@param forwardDelta Forward movement delta.
	@param rightDelta Right movement delta.
	@param moveSpeed Movement speed of the entity in distance per second.
	@param elapsedTime Elapsed time since last entity move.

	@return TRUE if the entity was successfully moved, FALSE otherwise.
*/
BOOL RuEntity_MoveEx(CRuEntity *entity, IRuObject *anchorObject, const CRuVector3 &defaultForward, const CRuVector3 &entityForward, const CRuVector3 &moveDelta, REAL moveSpeed, REAL elapsedTime)
{
//	REAL absForwardDelta = fabs(forwardDelta);
//	REAL absRightDelta = fabs(rightDelta);

	CRuVector3 normalizedEntityForward = entityForward;
	normalizedEntityForward.y = 0;
	normalizedEntityForward.Normalize();

	// Save entity position
	CRuVector3 entityPosition = entity->GetTranslation();
	CRuVector3 oldEntityPosition = entity->GetTranslation();
/*
	// Check offset
	CRuVector3 offsetVector;
	if(RuEntity_CalculateCollideOffsetVector(CRuSphere(entity->GetTranslation() + CRuVector3(0.0f, 10.0f, 0.0f), 5.0f), anchorObject, &offsetVector))
	{
		REAL moveDistance = moveSpeed * elapsedTime;
		offsetVector = offsetVector * moveDistance;

		// Move entity in direction of offset
		entity->Translate(offsetVector);
	}
*/
	// Get entity movement FSM state
	INT32 movementFSMState = reinterpret_cast<INT32>(entity->GetTag(ruENTITYTAG_FSMSTATE_MOVEMENT));

	switch(movementFSMState)
	{
		case 0:		// Surface movement
			{
				// Save old entity position
				oldEntityPosition = entity->GetTranslation();

				// Move the entity
				RuEntity_Move(entity, defaultForward, moveDelta.m_z, moveDelta.m_x);

				// Translate by vertical movement
				entity->Translate(CRuVector3(0.0f, moveDelta.m_y, 0.0f));

				// Anchor entity
				entityPosition = entity->GetTranslation();

				REAL anchorPoint = RuEntity_FindAnchorPoint(entityPosition, anchorObject);
				REAL anchorOffset = (anchorPoint - entity->GetTranslation().y);

				if(anchorOffset > 0.0f)
				{
					anchorOffset = min(anchorOffset, moveSpeed * elapsedTime);
				}
				else
				{
					anchorOffset = max(anchorOffset, -moveSpeed * elapsedTime);
				}

				// Adjust entity vertical positioning
				entityPosition.y += anchorOffset;

				CRuVector3 velocity = entityPosition - oldEntityPosition;

				if(velocity.Magnitude() < 0.001f)
				{
					entityPosition = oldEntityPosition;
				}
				else
				{
					CRuVector3 adjustedPosition;

					CRuFusion_Sojourner slider(anchorObject);
					slider.Slide(oldEntityPosition + CRuVector3(0.0f, 9.0f, 0.0f), velocity, CRuVector3(4.0f, 8.5f, 4.0f), adjustedPosition);
					
					entityPosition = adjustedPosition - CRuVector3(0.0f, 9.0f, 0.0f);
				}

				entity->SetTranslation(entityPosition);

				// Orient entity
				RuEntity_Orient(entity, defaultForward, normalizedEntityForward, 1.0f);	// hack

				// Test to see if we are in water
				CRuCollisionQuery_Segment_Nearest waterQuery;
				waterQuery.m_colSegment = CRuSegment3(CRuVector3(entityPosition.x, entityPosition.y + 15.0f, entityPosition.z), CRuVector3(entityPosition.x, entityPosition.y + 17.0f, entityPosition.z));
				waterQuery.m_ignoreBackface = FALSE;

				if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
				{
					static_cast<CRuEntity *>(anchorObject)->QueryCollision(&waterQuery);
				}
				else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
				{
					static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&waterQuery);
				}

				if(waterQuery.m_positiveHit && (waterQuery.m_collisionFlag & 0x00200000))
				{
					entity->SetTag(ruENTITYTAG_FSMSTATE_MOVEMENT, reinterpret_cast<POINTER>(1));
				}
			}

			break;

		case 1:		// Water surface movement
			{
				// Forward/backward movement
				CRuVector3 moveVec = entityForward * moveDelta.m_z;

				if(fabs(entityForward.y) < 0.5f)
				{
					moveVec = normalizedEntityForward * moveDelta.m_z;
				}

				// Right/left movement
				moveVec = moveVec + CrossProduct(CRuVector3(0.0f, 1.0f, 0.0f), entityForward) * moveDelta.m_x;

				entity->Translate(moveVec);

				// Orient the entity
				RuEntity_Orient(entity, defaultForward, normalizedEntityForward, 1.0f);	// hack

				// Test to see if we are diving
				{
					CRuCollisionQuery_Segment_Nearest waterQuery;
					waterQuery.m_colSegment = CRuSegment3(CRuVector3(entityPosition.x, entityPosition.y + 17.0f, entityPosition.z), CRuVector3(entityPosition.x, entityPosition.y + 20.0f, entityPosition.z));
					waterQuery.m_ignoreBackface = FALSE;

					if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
					{
						static_cast<CRuEntity *>(anchorObject)->QueryCollision(&waterQuery);
					}
					else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
					{
						static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&waterQuery);
					}

					if(waterQuery.m_positiveHit && (waterQuery.m_collisionFlag & 0x00200000))
					{
						entity->SetTag(ruENTITYTAG_FSMSTATE_MOVEMENT, reinterpret_cast<POINTER>(2));
					}
				}

				// Test to see if we are exiting water
				{
					CRuCollisionQuery_Segment_Nearest waterQuery;
					waterQuery.m_colSegment = CRuSegment3(CRuVector3(entityPosition.x, entityPosition.y + 10.0f, entityPosition.z), CRuVector3(entityPosition.x, entityPosition.y + 15.0f, entityPosition.z));
					waterQuery.m_ignoreBackface = FALSE;

					if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
					{
						static_cast<CRuEntity *>(anchorObject)->QueryCollision(&waterQuery);
					}
					else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
					{
						static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&waterQuery);
					}

					if(waterQuery.m_positiveHit && (waterQuery.m_collisionFlag & 0x00200000))
					{
						entity->SetTag(ruENTITYTAG_FSMSTATE_MOVEMENT, reinterpret_cast<POINTER>(0));
					}
				}
			}

			break;

		case 2:		// Diving
			{
				// Forward/backward movement
				CRuVector3 moveVec = entityForward * moveDelta.m_z;

				// Right/left movement
				moveVec = moveVec + CrossProduct(CRuVector3(0.0f, 1.0f, 0.0f), entityForward) * moveDelta.m_x;

				entity->Translate(moveVec);

				// Orient the entity
				RuEntity_Orient(entity, defaultForward, normalizedEntityForward, 1.0f);	// hack

				// Test to see if we are surfacing
				{
					CRuCollisionQuery_Segment_Nearest waterQuery;
					waterQuery.m_colSegment = CRuSegment3(CRuVector3(entityPosition.x, entityPosition.y + 15.0f, entityPosition.z), CRuVector3(entityPosition.x, entityPosition.y + 17.0f, entityPosition.z));
					waterQuery.m_ignoreBackface = FALSE;

					if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
					{
						static_cast<CRuEntity *>(anchorObject)->QueryCollision(&waterQuery);
					}
					else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
					{
						static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&waterQuery);
					}

					if(waterQuery.m_positiveHit && (waterQuery.m_collisionFlag & 0x00200000))
					{
						entity->SetTag(ruENTITYTAG_FSMSTATE_MOVEMENT, reinterpret_cast<POINTER>(1));
					}
				}
			}

			break;
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Orients an entity to the specified forward direction. +Y is always assumed to be the
	upward orientation.

	@param entity Entity to be oriented
	@param defaultForward Default forward orientation of the entity to be anchored.
	@param entityForward Current forward orientation of the entity to be anchored.

	@return TRUE if the entity was successfully oriented, FALSE otherwise.
*/
BOOL RuEntity_Orient(CRuEntity *entity, const CRuVector3 &defaultForward, const CRuVector3 &entityForward, REAL entityScale)
{
	CRuVector3 defaultRight;
	CRuVector3 defaultUp(0.0f, 1.0f, 0.0f);

	CRuVector3 entityRight;

	CRuMatrix4x4 scaleTransform;
	CRuMatrix4x4 objToCanonicalTransform;
	CRuMatrix4x4 canonicalToOrientationTransform;
	CRuMatrix4x4 basisChangeTransform;

	// Do nothing if either the entity or the default forward direction is invalid
	if(entityForward.SquaredMagnitude() < ruEPSILON || defaultForward.SquaredMagnitude() < ruEPSILON)
	{
		return FALSE;
	}

	// Build the scale transform
	scaleTransform.SetToScaling(entityScale, entityScale, entityScale);

	// Build the basis to go from entity's object space to canonical space (forward = +X, up = +Y)
	defaultRight = CrossProduct(defaultUp, defaultForward);
	objToCanonicalTransform.SetToInverseBasis(defaultRight, defaultUp, defaultForward);

	// Build the basis to go from canonical space to the orientation space
	entityRight = CrossProduct(defaultUp, entityForward);
	canonicalToOrientationTransform.SetToBasis(entityRight, defaultUp, entityForward);

	// Combine basis transforms
	basisChangeTransform = scaleTransform * objToCanonicalTransform * canonicalToOrientationTransform;

	// Orient entity
	entity->SetLocal3x3Transform(basisChangeTransform);

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Calculates the forward vector for the specified entity.

	@param entity Entity to calculate forward vector for.
	@param defaultForward Default forward orientation of the entity.

	@return The entity's forward vector.
*/
CRuVector3 RuEntity_CalculateForwardVector(CRuEntity *entity, const CRuVector3 &defaultForward)
{
	// Calculate entity forward vector
	CRuVector3 entityForward;
	entity->GetLocalTransform().TransformVector(defaultForward, entityForward);

	return entityForward;
}

// ************************************************************************************************************************************************************

BOOL RuEntity_Unstuck(CRuEntity *entity, IRuObject *anchorObject, REAL moveSpeed, REAL elapsedTime)
{
	CRuCollisionQuery_Sphere_SlideTest sphereQuery;
	sphereQuery.m_exclusionMask = 0x00200000;		// Exclude water surfaces
	sphereQuery.m_colSphere = CRuSphere(entity->GetTranslation() + CRuVector3(0.0f, 10.0f, 0.0f), 4.0f);

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&sphereQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&sphereQuery);
	}
/*
	if(sphereQuery.m_positiveHit)
	{
		REAL moveDistance = moveSpeed * elapsedTime;
		CRuVector3 offsetVector = sphereQuery.m_colNormal;
		offsetVector.y = 0.0f;
		if(offsetVector.SquaredMagnitude() > ruEPSILON)
			offsetVector.Normalize();
		offsetVector = offsetVector * min(moveDistance, sphereQuery.m_colDistance);

		entity->Translate(offsetVector);
	}
*/
	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Calculates the highest morph target count among all of the entity's children.

	@param entity Entity to examine.

	@return The highest morph target count.
*/
INT32 RuEntity_GetMaxMorphTargetCount(CRuEntity *entity)
{
	INT32 maxMTCount = 0;

	if(entity->GetType().IsSubClassOf(IRuEntity_Renderable::Type))
	{
		// Iterate through all meshes
		for(INT32 i = 0, numMeshes = static_cast<IRuEntity_Renderable *>(entity)->GetNumMeshes(); i < numMeshes; ++i)
		{
			IRuMesh *mesh;
			IRuMaterial *material;
			
			if(static_cast<IRuEntity_Renderable *>(entity)->GetMesh(i, &mesh, &material))
			{
				maxMTCount = max(maxMTCount, mesh->GetNumMorphTargets());
			}
		}

		return maxMTCount;
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		maxMTCount = max(maxMTCount, RuEntity_GetMaxMorphTargetCount(curChild));

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return maxMTCount;
}

// ************************************************************************************************************************************************************

BOOL RuEntity_PickTest(CRuEntity *entity, CRuCamera *camera, IRuSwapChain *swapChain, INT32 x, INT32 y, REAL *pickDistanceOut)
{
	// Get the target position for the pick ray in world coordinates
	CRuVector3 homPt, viewPt, worldPt;
	camera->Coord_ScreenToHomogeneous(x, y, swapChain->GetWidth(), swapChain->GetHeight(), homPt);
	camera->Coord_HomogeneousToView(homPt, viewPt);
	camera->Coord_ViewToWorld(viewPt, worldPt);

	// Build collision query
	CRuCollisionQuery_Ray_Pick rayQuery;
	rayQuery.m_colRay = CRuRay3(camera->GetCameraPosition(), worldPt - camera->GetCameraPosition(), TRUE);

	// Make collision query
	entity->QueryCollision(&rayQuery);

	if(pickDistanceOut)
	{
		*pickDistanceOut = rayQuery.m_colDistance;
	}

	return rayQuery.m_positiveHit;
}

// ************************************************************************************************************************************************************

/*!
	Attaches the child entity to the parent entity at the first link matching linkName.
	This function will search the parent entity for available frame hierarchies, and
	use the first link it finds.

	@param parentEntity Entity to attach to.
	@param childEntity Entity to be attached.
	@param linkName Name of the link to attach to.

	@return TRUE if the entity was successfully attached, FALSE otherwise.
*/
BOOL RuEntity_Attach(CRuEntity *parentEntity, CRuEntity *childEntity, const char *linkName)
{
	if(parentEntity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
	{
		CRuEntity *linkFrame = static_cast<CRuFrameHierarchy *>(parentEntity)->GetLinkFrameByName(linkName);

		// If a matching link was found, attach the child entity to it
		if(linkFrame)
		{
			linkFrame->AddChild(childEntity);

			return TRUE;
		}
	}

	// If the current object is matches the search specification, use it
	if(parentEntity->GetType().IsSubClassOf(CRuLinkFrame::Type))
	{
		if(stricmp(static_cast<CRuLinkFrame *>(parentEntity)->GetLinkName(), linkName) == 0)
		{
			static_cast<CRuLinkFrame *>(parentEntity)->AddEntity(childEntity);

			return TRUE;
		}
	}

	CRuEntity *curChild = parentEntity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuEntity_Attach(curChild, childEntity, linkName))
		{
			return TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Detaches the child entity from the parent entity at the first link matching linkName.
	This function will search the parent entity for available frame hierarchies, and
	use the first link it finds.

	@param parentEntity Entity to detach from.
	@param childEntity Entity to be detached.
	@param linkName Name of the link to detach from.

	@return TRUE if the entity was successfully detached, FALSE otherwise.
*/
BOOL RuEntity_Detach(CRuEntity *parentEntity, CRuEntity *childEntity, const char *linkName)
{
	if(parentEntity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
	{
		CRuEntity *linkFrame = static_cast<CRuFrameHierarchy *>(parentEntity)->GetLinkFrameByName(linkName);

		// If a matching link was found, look for the specified child entity
		if(linkFrame)
		{
			if(linkFrame->RemoveChild(childEntity))
			{
				return TRUE;
			}
		}
	}

	// If the current object is matches the search specification, use it
	if(parentEntity->GetType().IsSubClassOf(CRuLinkFrame::Type))
	{
		if(stricmp(static_cast<CRuLinkFrame *>(parentEntity)->GetLinkName(), linkName) == 0)
		{
			for(INT32 i = 0, numEntities = static_cast<CRuLinkFrame *>(parentEntity)->GetNumEntities(); i < numEntities; ++i)
			{
				if(static_cast<CRuLinkFrame *>(parentEntity)->GetEntity(i) == childEntity)
				{
					static_cast<CRuLinkFrame *>(parentEntity)->RemoveEntityAt(i);

					return TRUE;
				}
			}
		}
	}

	CRuEntity *curChild = parentEntity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuEntity_Detach(curChild, childEntity, linkName))
		{
			return TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Detaches all child entities at the link name of the parent entity.
	This function will search the parent entity for available frame hierarchies, and
	use the first link it finds.

	@param parentEntity Entity that holds the link.
	@param linkName Name of the link to detach from.

	@return TRUE if the link was successfully cleared, FALSE otherwise.
*/
BOOL RuEntity_DetachAll(CRuEntity *parentEntity, const char *linkName)
{
	if(parentEntity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
	{
		CRuEntity *linkFrame = static_cast<CRuFrameHierarchy *>(parentEntity)->GetLinkFrameByName(linkName);

		// If a matching link was found, detach all child entities linked to it
		if(linkFrame)
		{
			return linkFrame->RemoveAllChildren();
		}
	}

	// If the current object is matches the search specification, use it
	if(parentEntity->GetType().IsSubClassOf(CRuLinkFrame::Type))
	{
		if(stricmp(static_cast<CRuLinkFrame *>(parentEntity)->GetLinkName(), linkName) == 0)
		{
			return static_cast<CRuLinkFrame *>(parentEntity)->ClearEntities();
		}
	}

	CRuEntity *curChild = parentEntity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuEntity_DetachAll(curChild, linkName))
		{
			return TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Finds the specified link frame attached to the entity.

	@param entity Entity that holds the link.
	@param linkName Name of the link to find.

	@return Pointer to link frame if it was found. NULL otherwise.
*/
CRuEntity *RuEntity_FindLinkFrame(CRuEntity *entity, const char *linkName)
{
	if(entity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
	{
		CRuEntity *linkFrame = static_cast<CRuFrameHierarchy *>(entity)->GetLinkFrameByName(linkName);

		// If a matching link was found, return it
		if(linkFrame)
		{
			return linkFrame;
		}
	}

	// If the current object is matches the search specification, return it
	if(entity->GetType().IsSubClassOf(CRuLinkFrame::Type))
	{
		if(stricmp(static_cast<CRuLinkFrame *>(entity)->GetLinkName(), linkName) == 0)
		{
			return entity;
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		CRuEntity *linkFrame = RuEntity_FindLinkFrame(curChild, linkName);

		if(linkFrame)
		{
			return linkFrame;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return NULL;
}

// ************************************************************************************************************************************************************

/*!
	Enumerates all meshes exposed by the entity and its descendents using pre-order traversal.

	@param entity Entity to enumerate
	@param meshListOut Output mesh list.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuEntity_EnumerateMeshes(CRuEntity *entity, CRuArrayList<IRuMesh *> &meshListOut)
{
	if(entity->GetType().IsSubClassOf(IRuEntity_Renderable::Type))
	{
		IRuEntity_Renderable *renderable = static_cast<IRuEntity_Renderable *>(entity);

		for(INT32 i = 0; i < renderable->GetNumMeshes(); ++i)
		{
			IRuMesh *mesh;
			IRuMaterial *material;

			if(renderable->GetMesh(i, &mesh, &material))
			{
				meshListOut.Add(mesh);
			}
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuEntity_EnumerateMeshes(curChild, meshListOut);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Enumerates all materials used by the entity and its descendents using pre-order traversal.

	@param entity Entity to enumerate
	@param materialListOut Output material list.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuEntity_EnumerateMaterials(CRuEntity *entity, CRuArrayList<IRuMaterial *> &materialListOut)
{
	if(entity->GetType().IsSubClassOf(IRuEntity_Renderable::Type))
	{
		IRuEntity_Renderable *renderable = static_cast<IRuEntity_Renderable *>(entity);

		for(INT32 i = 0; i < renderable->GetNumMeshes(); ++i)
		{
			IRuMesh *mesh;
			IRuMaterial *material;
			
			if(renderable->GetMesh(i, &mesh, &material))
			{
				materialListOut.Add(material);
			}
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuEntity_EnumerateMaterials(curChild, materialListOut);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

BOOL RuEntity_SetOption(CRuEntity *entity, RuEntityOption option, INT32 value)
{
	// Debug assertion
	assert(entity);

	// Set specified option on the entity
	entity->SetOption(option, value);

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuEntity_SetOption(curChild, option, value);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

CRuModel *RuEntity_FindFirstModel(CRuEntity *entity)
{
	// Debug assertion
	assert(entity);

	if(entity->GetType().IsSubClassOf(CRuModel::Type))
	{
		return static_cast<CRuModel *>(entity);
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		CRuModel *retVal = RuEntity_FindFirstModel(curChild);
		if(retVal)
		{
			return retVal;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return NULL;
}

// ************************************************************************************************************************************************************

CRuFrameHierarchy *RuEntity_FindFirstFrameHierarchy(CRuEntity *entity)
{
	// Debug assertion
	assert(entity);

	if(entity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
	{
		return static_cast<CRuFrameHierarchy *>(entity);
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		CRuFrameHierarchy *retVal = RuEntity_FindFirstFrameHierarchy(curChild);
		if(retVal)
		{
			return retVal;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return NULL;
}

// ************************************************************************************************************************************************************

CRuACTEntity *RuEntity_FindFirstACTEntity(CRuEntity *entity)
{
	// Debug assertion
	assert(entity);

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		return static_cast<CRuACTEntity *>(entity);
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		CRuACTEntity *retVal = RuEntity_FindFirstACTEntity(curChild);
		if(retVal)
		{
			return retVal;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return NULL;
}

// ************************************************************************************************************************************************************

CRuPaperdoll *RuEntity_FindFirstPaperdoll(CRuEntity *entity)
{
	// Debug assertion
	assert(entity);

	if(entity->GetType().IsSubClassOf(CRuPaperdoll::Type))
	{
		return static_cast<CRuPaperdoll *>(entity);
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		CRuPaperdoll *retVal = RuEntity_FindFirstPaperdoll(curChild);
		if(retVal)
		{
			return retVal;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	// Debug assertion
	assert(entity);

	return NULL;
}

// ************************************************************************************************************************************************************

/*!
	Transforms the specified vector from world space into the entity's local space.

	@param entity Entity to transform to.
	@param v0 Vector to transform.
	@param v0Out Output vector.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuEntity_TransformVector_WorldToLocal(CRuEntity *entity, const CRuVector3 &v0, CRuVector3 &v0Out)
{
	// Debug assertion
	assert(entity);

	CRuMatrix4x4 inverseWorldTransform = entity->GetWorldTransform().GetInverse();

	inverseWorldTransform.TransformVector(v0, v0Out);

	return TRUE;
}

// ************************************************************************************************************************************************************

CRuMatrix4x4 &RuHierarchy_GetSubNodeTransform(CRuFrameHierarchy *hierarchy, CRuAnimationInterpolator *interpolator, INT32 nodeIndex, CRuMatrix4x4 &transformOut)
{
	// Clear output transform
	transformOut.SetToIdentity();

	// Traverse up the node tree
	CRuMatrix4x4 localTransform;

	while(nodeIndex >= 0)
	{
		// Build the local transform
		CRuAnimKeyFrame &localKeyFrame = interpolator->GetKeyFrames()[nodeIndex];
		localTransform.SetToBoneSRT(localKeyFrame.m_scale, localKeyFrame.m_rotation, localKeyFrame.m_translation);

		// Concatenate into output transform
		transformOut = transformOut * localTransform;

		// Move up to the parent node
		nodeIndex = hierarchy->GetSubNodeParentIndex(nodeIndex);
	}

	return transformOut;
}

// ************************************************************************************************************************************************************

CRuMatrix4x4 &RuHierarchy_GetSubNodeParentTransform(CRuFrameHierarchy *hierarchy, CRuAnimationInterpolator *interpolator, INT32 nodeIndex, CRuMatrix4x4 &transformOut)
{
	// Clear output transform
	transformOut.SetToIdentity();

	// Traverse up the node tree
	CRuMatrix4x4 localTransform;

	// Move up to the parent node
	nodeIndex = hierarchy->GetSubNodeParentIndex(nodeIndex);

	while(nodeIndex >= 0)
	{
		// Build the local transform
		CRuAnimKeyFrame &localKeyFrame = interpolator->GetKeyFrames()[nodeIndex];
		localTransform.SetToBoneSRT(localKeyFrame.m_scale, localKeyFrame.m_rotation, localKeyFrame.m_translation);

		// Concatenate into output transform
		transformOut = transformOut * localTransform;

		// Move up to the parent node
		nodeIndex = hierarchy->GetSubNodeParentIndex(nodeIndex);
	}

	return transformOut;
}

// ************************************************************************************************************************************************************

/*!
	Performs a collision test between the AABB and the anchor object.

	@param testSphere The test collision AABB.
	@param anchorObject The anchoring object.

	@return TRUE if there is a collision, FALSE otherwise.
*/
BOOL RuCollision_TestAABB(const CRuAABB &aabb, IRuObject *anchorObject)
{
	CRuCollisionQuery_AABB_HitTest boxQuery;
	boxQuery.m_exclusionMask = 0x00200000;		// Exclude water surfaces
	boxQuery.m_colAABB = aabb;

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&boxQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&boxQuery);
	}

	if(boxQuery.m_positiveHit)
	{
		return TRUE;
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Performs a collision test between the sphere and the anchor object.

	@param testSphere The test collision sphere.
	@param anchorObject The anchoring object.

	@return TRUE if there is a collision, FALSE otherwise.
*/
BOOL RuCollision_TestSphere(const CRuSphere &testSphere, IRuObject *anchorObject)
{
	CRuCollisionQuery_Sphere_HitTest sphereQuery;
	sphereQuery.m_exclusionMask = 0x00200000;		// Exclude water surfaces
	sphereQuery.m_colSphere = testSphere;

	if(anchorObject->GetType().IsSubClassOf(CRuEntity::Type))
	{
		static_cast<CRuEntity *>(anchorObject)->QueryCollision(&sphereQuery);
	}
	else if(anchorObject->GetType().IsSubClassOf(IRuCollisionHierarchy::Type))
	{
		static_cast<IRuCollisionHierarchy *>(anchorObject)->QueryCollision(&sphereQuery);
	}

	if(sphereQuery.m_positiveHit)
	{
		return TRUE;
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

/*!
	Determines whether the specified ACT entity has the requested motion available. If firstACTOnly
	is true, this function will stop checking as soon as it encounters the first ACT entity. Otherwise,
	this function will return TRUE if any child entities has the requested motion available.

	@param entity Entity to act upon.
	@param motionName Motion name.
	@param firstACTOnly Check first ACT entity only?

	@return TRUE if the entity has the requested motion, FALSE otherwise.
*/
BOOL RuACT_HasMotion(CRuEntity *entity, const char *motionName, BOOL firstACTOnly)
{
	BOOL hasMotion = FALSE;

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		INT32 motionID = static_cast<CRuACTEntity *>(entity)->GetMotionIDByName(motionName);

		if(motionID >= 0)
		{
			hasMotion = TRUE;
		}

		if(firstACTOnly)
		{
			return hasMotion;
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuACT_HasMotion(curChild, motionName, firstACTOnly) == TRUE)
		{
			hasMotion = TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return hasMotion;
}

// ************************************************************************************************************************************************************

/*!
	Determines whether the specified entity supports the specified motion channel. This function
	works by finding all frame hierarchies and checking them to make sure they have bones with
	acceptable group ID bitfields.

	@param entity Entity to be act upon.
	@param channel Channel to check for.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuACT_SupportsMotionChannel(CRuEntity *entity, UINT32 channel)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuACT_SupportsMotionChannel(curChild, channel) == FALSE)
		{
			return FALSE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuFrameHierarchy::Type))
	{
		if(static_cast<CRuFrameHierarchy *>(entity)->SupportsMotionChannel(channel) == FALSE)
		{
			return FALSE;
		}
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

BOOL RuACT_PauseChannel(CRuEntity *entity, UINT32 channel)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_PauseChannel(curChild, channel);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		static_cast<CRuACTEntity *>(entity)->PauseChannel(channel);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

BOOL RuACT_ResumeChannel(CRuEntity *entity, UINT32 channel)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_ResumeChannel(curChild, channel);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		static_cast<CRuACTEntity *>(entity)->ResumeChannel(channel);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

BOOL RuACT_PlayPersistentMotion(CRuEntity *entity, BOOL forceReset)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_PlayPersistentMotion(curChild, forceReset);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		static_cast<CRuACTEntity *>(entity)->PlayPersistentMotion(forceReset);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Plays the specified motion on the entity. If forceReset is true, then the motion
	is restarted even if it is already playing.

	@param entity Entity to be act upon.
	@param motionName Name of the motion to play.
	@param blendTime Time to spend blending into the new motion
	@param forceReset Restart motion play if the specified motion ID is already playing?

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuACT_PlayMotion(CRuEntity *entity, const char *motionName, REAL blendTime, BOOL forceReset)
{
	// NOTE: If the container is an ACT, invoking play motion on the child entities first has the effect
	//       that any higher-level motion controllers owned by the parent ACT container has a chance
	//       to override any motions played by its control target.
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_PlayMotion(curChild, motionName, blendTime, forceReset);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		INT32 motionID = static_cast<CRuACTEntity *>(entity)->GetMotionIDByName(motionName);

		static_cast<CRuACTEntity *>(entity)->PlayMotion(0xFFFFFFFF, motionID, blendTime, forceReset);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Plays the specified motion on the entity. If forceReset is true, then the motion
	is restarted even if it is already playing.

	@param entity Entity to be act upon.
	@param motionID ID of the motion to play.
	@param blendTime Time to spend blending into the new motion
	@param forceReset Restart motion play if the specified motion ID is already playing?

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuACT_PlayMotion(CRuEntity *entity, INT32 motionID, REAL blendTime, BOOL forceReset)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_PlayMotion(curChild, motionID, blendTime, forceReset);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		static_cast<CRuACTEntity *>(entity)->PlayMotion(0xFFFFFFFF, motionID, blendTime, forceReset);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Plays the specified motion on the entity. If forceReset is true, then the motion
	is restarted even if it is already playing.

	@param entity Entity to be act upon.
	@param channel Channel to play the motion on.
	@param motionName Name of the motion to play.
	@param blendTime Time to spend blending into the new motion
	@param forceReset Restart motion play if the specified motion ID is already playing?
	@param channelWeight Blending weight of the channel.
	@param leadIn Lead-in time during which the motion is blended in
	@param leadOut Lead-out time during which the motion is blended out

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuACT_PlayMotionEx(CRuEntity *entity, UINT32 channel, const char *motionName, REAL blendTime, BOOL forceReset, REAL channelWeight, REAL leadIn, REAL leadOut, REAL expectedMotionScale)
{
	// NOTE: If the container is an ACT, invoking play motion on the child entities first has the effect
	//       that any higher-level motion controllers owned by the parent ACT container has a chance
	//       to override any motions played by its control target.
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_PlayMotionEx(curChild, channel, motionName, blendTime, forceReset, channelWeight, leadIn, leadOut, expectedMotionScale);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		INT32 motionID = static_cast<CRuACTEntity *>(entity)->GetMotionIDByName(motionName);

		static_cast<CRuACTEntity *>(entity)->PlayMotion(channel, motionID, blendTime, forceReset, channelWeight, leadIn, leadOut, expectedMotionScale);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Swaps motions playing on the two specified channels.

	@param entity Entity to be act upon.
	@param srcChannel First channel to swap.
	@param destChannel Second channel to swap.

	@return TRUE if motion has been successfully swapped, FALSE otherwise.
*/
BOOL RuACT_SwapMotion(CRuEntity *entity, UINT32 srcChannel, UINT32 destChannel)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_SwapMotion(curChild, srcChannel, destChannel);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		static_cast<CRuACTEntity *>(entity)->SwapMotion(srcChannel, destChannel);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Clears any motions playing on the specified group mask.

	@param entity Entity to be act upon.
	@param channel channel to act upon.
	@param fadeDuration Duration of the motion fade. Motion fade is the period
	                    where there is no motion playing but the old motion is blended slowly to zero.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuACT_ClearMotion(CRuEntity *entity, UINT32 channel, REAL fadeDuration)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuACT_ClearMotion(curChild, channel, fadeDuration);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		static_cast<CRuACTEntity *>(entity)->ClearMotion(channel, fadeDuration);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Determines whether the specified channel has completed its motion playback. This
	function will recursively iterate through all children, and return TRUE only if every
	child has completed its motion playback. If any of the motions being played back are
	looping, then this function will never return TRUE.

	@param entity Entity to be act upon.
	@param channel Channel to determine playback status for.

	@return TRUE if motion has finished played, FALSE otherwise.
*/
BOOL RuACT_IsMotionComplete(CRuEntity *entity, UINT32 channel)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuACT_IsMotionComplete(curChild, channel) == FALSE)
		{
			return FALSE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		return static_cast<CRuACTEntity *>(entity)->IsMotionComplete(channel);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Gets the active motion name for the specified channel. This function
	will return the active motion name for the first ACT entity it finds.

	@param entity Entity to act upon.
	@param channel Channel to get motion name from.

	@return Active motion name, or NULL if there is no active motion on the specified channel.
*/
const char *RuACT_GetActiveMotionName(CRuEntity *entity, UINT32 channel)
{
	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		return static_cast<CRuACTEntity *>(entity)->GetActiveMotionName(channel);
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		const char *name = RuACT_GetActiveMotionName(curChild, channel);

		if(name)
		{
			return name;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return NULL;
}

// ************************************************************************************************************************************************************

/*!
	Gets the active motion time for the specified channel. This function
	will return the active motion time for the first ACT entity it finds.

	@param entity Entity to act upon.
	@param channel Channel to get motion time from.

	@return Active motion time, or a negative value if there is no active motion on the specified channel.
*/
REAL RuACT_GetActiveMotionTime(CRuEntity *entity, UINT32 channel)
{
	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		return static_cast<CRuACTEntity *>(entity)->GetActiveMotionTime(channel);
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		REAL motionTime = RuACT_GetActiveMotionTime(curChild, channel);

		if(motionTime >= 0.0f)
		{
			return motionTime;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return -1.0f;
}

// ************************************************************************************************************************************************************

/*!
	Determines whether the ACT entity has the requested controller instanced in the currently playing
	motion on the specified channel. If firstACTOnly is true, this function will stop checking as
	soon as it encounters the first ACT entity. Otherwise, this function will return TRUE if any child
	entities has the requested controller instanced.

	@param entity Entity to act upon.
	@param channel Channel mask.
	@param controllerType Run-time type of the controller to check for.
	@param firstACTOnly Check first ACT entity only?

	@return TRUE if the entity has the requested controller, FALSE otherwise.
*/
BOOL RuACT_CheckForController(CRuEntity *entity, UINT32 channel, const CRuRTTI &controllerType, BOOL firstACTOnly)
{
	BOOL hasController = FALSE;

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		hasController = static_cast<CRuACTEntity *>(entity)->CheckForController(channel, controllerType);

		if(firstACTOnly)
		{
			return hasController;
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuACT_CheckForController(curChild, channel, controllerType, firstACTOnly) == TRUE)
		{
			hasController = TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return hasController;
}

// ************************************************************************************************************************************************************

/*!
	Determines whether the ACT entity has the requested event instanced in the currently playing
	motion on the specified channel. If firstACTOnly is true, this function will stop checking as
	soon as it encounters the first ACT entity. Otherwise, this function will return TRUE if any child
	entities has the requested event instanced.

	@param entity Entity to act upon.
	@param channel Channel mask.
	@param eventType Event to check for.
	@param startTimeOut [OUT] Start time of the event.
	@param firstACTOnly Check first ACT entity only?

	@return TRUE if the entity has the requested event, FALSE otherwise.
*/
BOOL RuACT_CheckForEvent(CRuEntity *entity, UINT32 channel, RuEntityEvent eventType, REAL *startTimeOut, BOOL firstACTOnly)
{
	BOOL hasEvent = FALSE;

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		hasEvent = static_cast<CRuACTEntity *>(entity)->CheckForEvent(channel, eventType, startTimeOut);

		if(firstACTOnly)
		{
			return hasEvent;
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuACT_CheckForEvent(curChild, channel, eventType, startTimeOut, firstACTOnly) == TRUE)
		{
			hasEvent = TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return hasEvent;
}

// ************************************************************************************************************************************************************

/*!
	Scans the specified entity for missing motions, and plugs the missing motions with
	replacement motion IDs. Motion list is specified by the command stream. The command
	stream is a simple list as follows:

		[motion to scan for] [replacement motion 1] [replacement motion 2] ... \n\r

	@param entity Entity to act upon.
	@param commandStream The command stream.
	@param firstACTOnly Check first ACT entity only?

	@return TRUE if the entity was plugged, FALSE otherwise.
*/
BOOL RuACT_PlugMissingMotions(CRuEntity *entity, const char *commandStream, BOOL firstACTOnly)
{
	BOOL returnValue = FALSE;

	if(entity->GetType().IsSubClassOf(CRuACTEntity::Type))
	{
		// Parse command stream into multiple commands
		CRuString commandString = commandStream;
		CRuString **commands = commandString.Tokenize("\n\r");

		// Scan through commands
		for(INT32 i = 0; TRUE; ++i)
		{
			if(commands[i] == NULL)
			{
				break;
			}

			// Tokenize command
			CRuString **commandTokens = commands[i]->Tokenize(" \t");

			if(commandTokens[0])
			{
				if(static_cast<CRuACTEntity *>(entity)->GetMotionIDByName(static_cast<const char *>(*commandTokens[0])) < 0)
				{
					for(INT32 j = 1; TRUE; ++j)
					{
						if(commandTokens[j] == NULL)
						{
							break;
						}

						// Attempt to find the current replacement motion
						INT32 replacementMotionID = static_cast<CRuACTEntity *>(entity)->GetMotionIDByName(static_cast<const char *>(*commandTokens[j]));

						if(replacementMotionID >= 0)
						{
							// Create new entry in the ACT template
							static_cast<CRuACTEntity *>(entity)->GetTemplate()->AddMotion(replacementMotionID, static_cast<const char *>(*commandTokens[0]));
							break;
						}
					}
				}
			}

			// Delete tokens
			for(INT32 j = 0; TRUE; ++j)
			{
				if(commandTokens[j] == NULL)
				{
					break;
				}

				delete commandTokens[j];
			}
			// Delete token array
			delete[] commandTokens;

			// Delete command
			delete commands[i];
		}

		// Delete command array
		delete[] commands;

		returnValue = TRUE;

		// If specified to scan only the first ACT, return immediately
		if(firstACTOnly)
		{
			return returnValue;
		}
	}

	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		if(RuACT_PlugMissingMotions(curChild, commandStream, firstACTOnly) == TRUE)
		{
			returnValue = TRUE;
		}

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	return returnValue;
}

// ************************************************************************************************************************************************************

/*!
	Sets the specified color codes into any paperdolls contained by the entity. Generally,
	color code 0 is skin color and color code 1 is hair color.

	@param entity Entity to act upon.
	@param color0Code Color code 0.
	@param color1Code Color code 1.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuPaperdoll_SetColorCodes(CRuEntity *entity, INT32 color0Code, INT32 color1Code)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuPaperdoll_SetColorCodes(curChild, color0Code, color1Code);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuPaperdoll::Type))
	{
		// Relay call to paperdoll
		static_cast<CRuPaperdoll *>(entity)->SetColorCodes(color0Code, color1Code);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Sets the specified component into the part for all paperdolls contained by the entity.

	@param entity Entity to act upon.
	@param partName Part name.
	@param componentName Component name.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuPaperdoll_SetComponent(CRuEntity *entity, const char *partName, const char *componentName)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuPaperdoll_SetComponent(curChild, partName, componentName);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuPaperdoll::Type))
	{
		// Relay call to paperdoll
		return static_cast<CRuPaperdoll *>(entity)->SetComponent(partName, componentName);
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

/*!
	Builds all paperdolls contained by the specified entity.

	@param entity Entity to act upon.

	@return TRUE if successful, FALSE otherwise.
*/
BOOL RuPaperdoll_Build(CRuEntity *entity)
{
	CRuEntity *curChild = entity->GetFirstChild();
	while(curChild)
	{
		// Recurse to child entities
		RuPaperdoll_Build(curChild);

		// Advance to next sibling
		curChild = curChild->GetNextSibling();
	}

	if(entity->GetType().IsSubClassOf(CRuPaperdoll::Type))
	{
		// Relay call to paperdoll
		return static_cast<CRuPaperdoll *>(entity)->BuildPaperdoll();
	}

	return TRUE;
}

// ************************************************************************************************************************************************************

const char *RuEnumConverter_ToString(RuTextureChannel texChannel)
{
	switch(texChannel)
	{
		case ruTEXCHANNEL_DIFFUSEMAP:		return "Diffuse Map";
		case ruTEXCHANNEL_DETAILMAP:		return "Detail Map";
		case ruTEXCHANNEL_LIGHTMAP:			return "Light Map";
		case ruTEXCHANNEL_DIFFUSECUBEMAP:	return "Diffuse Cube Map";
		case ruTEXCHANNEL_ENVIRONMENTMAP:	return "Environment Map";
		case ruTEXCHANNEL_CAUSTICMAP:		return "Caustic Map";
		case ruTEXCHANNEL_BUMPMAP:			return "Bump Map";
		case ruTEXCHANNEL_NORMALMAP:		return "Normal Map";
		case ruTEXCHANNEL_GLOSSMAP:			return "Gloss Map";
	}

	return "Unknown";
}

// ************************************************************************************************************************************************************

CRuGlobalEventManager_Sample::CRuGlobalEventManager_Sample()
{
	m_controllerFrame = ruNEW CRuEntity_Container_Impl();
}

CRuGlobalEventManager_Sample::~CRuGlobalEventManager_Sample()
{
	ruSAFE_RELEASE(m_controllerFrame);
}

void CRuGlobalEventManager_Sample::Update(REAL elapsedTime)
{
	// Chain update call to the controller container
	m_controllerFrame->Update(elapsedTime);
}

BOOL CRuGlobalEventManager_Sample::HandleEvent(CRuEntity *entity, INT32 eventID, INT32 eventParameter0, INT32 eventParameter1, const char *eventString, REAL eventDuration, void *userData)
{
	switch(eventID)
	{
		case ruENTITYEVENT_HIT:
			{
				CRuController_Entity *entityController = ruNEW CRuController_Entity();
				entityController->SetSpawnedEntityName("model\\fx\\hit_fx\\act_hit_fx.ros");
				entityController->SetSpawnedMotionName("idle");

				entityController->SetTarget(entity);

				entityController->SetStartTime(0.0f);
				entityController->SetDuration(eventDuration);
				entityController->SetLoopTime(0.0f);
				entityController->SetSelfDestructTimer(eventDuration + 2.0f);

				// Add controller to the container frame
				m_controllerFrame->AddEntity(entityController);
			}

			return TRUE;

		case ruENTITYEVENT_FOOTSTEP:
			{
				// Build a particle system or some such
				CRuController_ParticleSystem *psTest = ruNEW CRuController_ParticleSystem();
				psTest->SetParticleSystem(g_ruResourceManager->LoadEntity("model\\fx\\fx_fire.ros"));
				psTest->SetTarget(entity);

				psTest->SetStartTime(0.0f);
				psTest->SetDuration(eventDuration);
				psTest->SetLoopTime(0.0f);
				psTest->SetSelfDestructTimer(eventDuration + 2.0f);

				// Add controller to the container frame
				m_controllerFrame->AddEntity(psTest);
			}

			return TRUE;

		case ruENTITYEVENT_GHOSTTRAIL:
			{
				CRuController_GhostTrail *gtController = ruNEW CRuController_GhostTrail();
				gtController->SetInterval(0.001f);
				gtController->SetMaxAge(eventDuration);
				gtController->SetFadeCoefficients(0.0f, 0.0f, 1.0f);
				gtController->SetMaxSegments(128);
				gtController->SetTrailAnchors("effect01", "effect02");
				gtController->SetTrailTextureName("texture\\fx\\weapon_trail\\blade_flash");
				gtController->SetTarget(entity);

				gtController->SetActiveDuration(eventDuration);
				gtController->SetFadeOutDuration(0.5f);

				gtController->SetTrailFrameCount(1);
				gtController->SetTrailFrameRate(30.0f);

				gtController->SetStartTime(0.0f);
				gtController->SetDuration(eventDuration + 0.5f);
				gtController->SetLoopTime(0.0f);
				gtController->SetSelfDestructTimer(eventDuration + 2.0f);

				// Add controller to the container frame
				m_controllerFrame->AddEntity(gtController);
			}

			return TRUE;

		case ruENTITYEVENT_AUDIO_DIRECT:
			{
				if(eventString)
				{
					g_ruSymphony->PlaySoundByPath(eventString);
				}
			}

			return TRUE;

		case ruENTITYEVENT_AUDIO_3D_DIRECT:
			{
				if(eventString)
				{
					g_ruSymphony->Play3DSoundByPath(eventString, 0.0f, 0.0f, 0.0f);
				}
			}

			return TRUE;
	}

	return FALSE;
}

// ************************************************************************************************************************************************************

char *g_ruACT_DefaultMotionChart = "\
	unarmed_idle		stand_idle																	\n\
	unarmed_attack01	idle02																		\n\
	unarmed_attack02	unarmed_attack01															\n\
	unarmed_attack03	unarmed_attack01															\n\
																									\n\
	1h_idle				unarmed_idle																\n\
	1h_parry			unarmed_parry																\n\
	1h_attack01			unarmed_attack01															\n\
	1h_attack02			1h_attack01			unarmed_attack02										\n\
	1h_attack03			1h_attack02			unarmed_attack03										\n\
																									\n\
	2h_idle				1h_idle																		\n\
	1h_parry			1h_parry																	\n\
	2h_attack01			1h_attack01																	\n\
	2h_attack02			1h_attack02																	\n\
	2h_attack03			1h_attack03																	\n\
																									\n\
	polearm_idle		unarmed_idle																\n\
	polearm_parry		unarmed_parry																\n\
	polearm_attack01	1h_attack01																	\n\
	polearm_attack02	1h_attack02																	\n\
	polearm_attack03	1h_attack03																	\n\
																									\n\
	dw_idle				1h_idle																		\n\
	dw_parry			1h_parry																	\n\
	dw_attack01			1h_attack01																	\n\
	dw_attack02			1h_attack02																	\n\
	dw_attack03			1h_attack03																	\n\
																									\n\
	bow_idle			gun_idle			unarmed_idle											\n\
	bow_parry			gun_parry			unarmed_parry											\n\
	bow_attack01		gun_attack01		unarmed_attack01										\n\
	bow_attack02		bow_attack01																\n\
	bow_attack03		bow_attack02																\n\
																									\n\
	gun_idle			bow_idle																	\n\
	gun_parry			bow_parry																	\n\
	gun_attack01		bow_attack01																\n\
	gun_attack02		bow_attack02																\n\
	gun_attack03		bow_attack03																\n\
																									\n\
	casting02			casting01																	\n\
	casting03			casting02			casting01												\n\
																									\n\
	idle03				idle02																		\n\
	idle04				idle03				idle02													\n\
	";

// ************************************************************************************************************************************************************

#pragma managed
