/*
* Copyright (C) 2006-2010 - Frictional Games
*
* This file is part of Penumbra Overture.
*
* Penumbra Overture is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Penumbra Overture is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Penumbra Overture. If not, see .
*/
#include "StdAfx.h"
#include "CharacterMove.h"
#include "GameEntity.h"
#include "EffectHandler.h"
//////////////////////////////////////////////////////////////////////////
// A STAR CALLBHCK
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cCharacterAStarCallback::cCharacterAStarCallback(cCharacterMove *apMove)
{
mbCheckDynamic = false;
mpMove = apMove;
}
//-----------------------------------------------------------------------
bool cCharacterAStarCallback::CanAddNode(cAINode *apParentNode,cAINode *apChildNode)
{
if(mbCheckDynamic==false && mpMove->GetMaxDoorToughness()==-1) return true;
bool bRet = mpMove->GetNodeContainer()->FreePath(apParentNode->GetPosition(),apChildNode->GetPosition(),
1,0,this);
//Log("Checking %s -> %s, ret: %d \n",apParentNode->GetName().c_str(),
// apChildNode->GetName().c_str(),bRet?1:0);
return bRet;
}
//-----------------------------------------------------------------------
bool cCharacterAStarCallback::Intersects(iPhysicsBody *pBody,cPhysicsRayParams *apParams)
{
//if(pBody->GetCollide()==false) return false;
if(pBody->IsCharacter()) return false;
if(pBody->GetCollideCharacter()==false) return false;
if(pBody->GetMass() == 0) return false;
iGameEntity *pEntity = static_cast(pBody->GetUserData());
if(pEntity && pEntity->GetType() == eGameEntityType_SwingDoor)
{
if(pEntity->GetToughness() >= mpMove->GetMaxDoorToughness()
&& mpMove->GetMaxDoorToughness() >=0)
{
//Log("Door was too strong!\n T: %d Max: %d\n",pEntity->GetToughness(),mpMove->GetMaxDoorToughness());
return true;
}
return false;
}
else
{
if(pBody->GetMass() < mpMove->mfMaxPushMass) return false;
}
return true;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// RAY CALLBHCK
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cMoverRayCallback::Reset()
{
mbIntersection = false;
}
bool cMoverRayCallback::BeforeIntersect(iPhysicsBody *pBody)
{
if( pBody->IsCharacter() || pBody->GetCollide()==false || pBody->GetCollideCharacter()==false ||
pBody->GetMass()>0)
{
return false;
}
return true;
}
bool cMoverRayCallback::OnIntersect(iPhysicsBody *pBody,cPhysicsRayParams *apParams)
{
if( pBody->IsCharacter() || pBody->GetCollide()==false || pBody->GetCollideCharacter()==false ||
pBody->GetMass()>0)
{
return true;
}
mbIntersection = true;
return false;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cCharacterMove::cCharacterMove(cInit *apInit)
{
mpAStar = NULL;
mpCharBody = NULL;
mpContainer = NULL;
mpInit = apInit;
mfMaxTurnSpeed = 9999.0f; //The maximum speed the enemy will turn in.
mfAngleDistTurnMul = 1.0f; //Multiplied with angle distance to determine turn speed.
mfMinBreakAngle = cMath::ToRad(20.0f); //Minimum angle distance at which break start.
mfBreakAngleMul = 1.0f; //Multiplied with angle distance to determine break force.
mbMoving = false;
mbTurning = false;
mfTurnSpeed =0;
mfStuckLimit = 0.3f;
mfStuckCounter =0;
mfMaxPushMass = 5;
mbMoveToNewNode = false;
mpAStarCallback = hplNew( cCharacterAStarCallback, (this) );
mvTempStart = 0;
mvTempEnd =0;
mlMaxDoorToughness = -1;
mlMaxNodeDistances = 150;
mfNodeDistAvg =0;
}
//-----------------------------------------------------------------------
cCharacterMove::~cCharacterMove()
{
if(mpAStarCallback) hplDelete( mpAStarCallback );
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cCharacterMove::Update(float afTimeStep)
{
if(mpCharBody->IsActive()==false) return;
////////////////////////////////////
//Update Movement
if(mbMoving)
{
cAINode *pCurrentNode = NULL;
cVector3f vGoal;
cVector3f vPos = mpCharBody->GetPosition();
/////////////////////////////////////////
//Get the postion to move towards and current node if there is any.
if(mlstNodes.empty())
{
vGoal = mvGoalPos;
}
else{
pCurrentNode = mlstNodes.back();
vGoal = pCurrentNode->GetPosition();
}
/////////////////////////////////////////
//Check if the newer node is reachable
if(mbMoveToNewNode)
{
mbMoveToNewNode = false;
//if(mpContainer->FreePath(mpCharBody->GetFeetPosition()+cVector3f(0,0.1f,0),
// vGoal,1,0,mpAStarCallback)==false)
/*if(mpContainer->FreePath(mpCharBody->GetFeetPosition()+cVector3f(0,0.1f,0),
vGoal,-1,0,mpAStarCallback)==false)
{
//Do nothing here right now, might wanna remove the path from the two connected
//Nodes, but that might turn out pretty hard, leave for now, this takes too much cpu.
//------------------------
//mpInit->mpEffectHandler->GetSubTitle()->Add("Path was blocked!\n",2.0f,true);
mbMoving = false;
mlstNodes.clear();
//Log("-- Begin Dynamic path find !\n");
mpAStarCallback->mbCheckDynamic = true;
MoveToPos(mvGoalPos);
mpAStarCallback->mbCheckDynamic = false;
//Log("-- End Dynamic path find !\n");
}*/
}
float fGoalAngle = -cMath::GetAngleFromPoints2D(cVector2f(vPos.x, vPos.z), cVector2f(vGoal.x, vGoal.z));
TurnToAngle(fGoalAngle);
mpCharBody->SetPitch(0);
mpCharBody->Move(eCharDir_Forward,1.0f, afTimeStep);
////////////////////////////////
//Update bounding volume
mBoundingVolume.SetPosition(vPos);
mBoundingVolume.UpdateSize();
bool bStuckAtNode = false;
float fNodeDist = cMath::Vector3DistSqr(vGoal,mpCharBody->GetFeetPosition());
mlstNodeDistances.push_back(fNodeDist);
if((int)mlstNodeDistances.size() > mlMaxNodeDistances)
{
mlstNodeDistances.pop_front();
mfNodeDistAvg=0;
std::list::iterator it = mlstNodeDistances.begin();
float fPreviousDistance = *it;
++it;
for(; it != mlstNodeDistances.end();++it)
{
float fChange = *it - fPreviousDistance;
mfNodeDistAvg += fChange;
}
if( mfNodeDistAvg > 0 &&
fNodeDist < mpCharBody->GetSize().x * 1.5f)
{
bStuckAtNode = true;
}
}
////////////////////////////////
//Check if node is reached:
if(bStuckAtNode || cMath::PointBVCollision(vGoal, mBoundingVolume))
{
if(mlstNodes.empty())
{
mbMoving = false;
}
else
{
mlstNodes.pop_back(); //Go to next node next update.
}
mbMoveToNewNode = true;
mlstNodeDistances.clear();
}
}
////////////////////////////////////
//Update turning
if(mbTurning)
{
float fAngleDist = cMath::GetAngleDistanceRad(mpCharBody->GetYaw(), mfGoalAngle);
/////////////////
//Rotate the body
if(std::abs(fAngleDist) < 0.001f){
mbTurning = false;
mfTurnSpeed =0;
}
if(mbTurning)
{
mfTurnSpeed = cMath::Min(mfAngleDistTurnMul * std::abs(fAngleDist), mfMaxTurnSpeed);
if(fAngleDist < 0) mpCharBody->AddYaw(-mfTurnSpeed * afTimeStep);
else mpCharBody->AddYaw(mfTurnSpeed * afTimeStep);
//////////////////////
//Break when making short turns
if(std::abs(fAngleDist) >= mfMinBreakAngle && mpCharBody->GetMoveSpeed(eCharDir_Forward)>0.15f)
{
float fBreakAcc = -mfBreakAngleMul * std::abs(fAngleDist);
mpCharBody->Move(eCharDir_Forward,fBreakAcc,afTimeStep);
mfCurrentBreak = fBreakAcc;
}
else
{
mfCurrentBreak =0;
}
}
}
//////////////////////////////////////
/// Update stuck counter
float fWantedSpeed = mpCharBody->GetMoveSpeed(eCharDir_Forward);
float fRealSpeed = cMath::Vector3Dist(mpCharBody->GetPosition(), mpCharBody->GetLastPosition());
fRealSpeed = fRealSpeed / afTimeStep;
cVector3f vWantedDir = mpCharBody->GetForward();
cVector3f vRealDir = mpCharBody->GetPosition() - mpCharBody->GetLastPosition();
vRealDir.Normalise();
float fCos = cMath::Vector3Dot(vWantedDir,vRealDir);
if( fRealSpeed/fWantedSpeed < mfStuckLimit ||
(std::abs(fCos) < 0.3f && fWantedSpeed > 0.001f))
{
mfStuckCounter += afTimeStep;
//mpInit->mpEffectHandler->GetSubTitle()->Add(_W("ADD!\n"),1.0f/60.0f,false);
}
else
{
//mpInit->mpEffectHandler->GetSubTitle()->Add(_W("NEG!\n"),1.0f/60.0f,false);
mfStuckCounter -= afTimeStep;
if(mfStuckCounter<0)mfStuckCounter =0;
}
}
//-----------------------------------------------------------------------
bool cCharacterMove::MoveToPos(const cVector3f &avPos)
{
if(mpAStar==NULL) return false;
//Log(" Moving to %s\n",avPos.ToString().c_str());
//mpInit->mpEffectHandler->GetSubTitle()->Add("Get newer path!\n",2.0f,true);
//Get the start and goal position
cVector3f vStartPos = mpCharBody->GetPosition();
cVector3f vGoalPos = avPos;
if(mpContainer->GetNodeIsAtCenter()==false)
{
vStartPos -= cVector3f(0,mpCharBody->GetSize().y/2.0f,0);
}
//Get the nodes to be following
mlstNodes.clear();
//Log(" Getting path!\n");
bool bRet = mpAStar->GetPath(vStartPos,vGoalPos,&mlstNodes);
if(bRet==false)
{
//Log("Did NOT find path\n");
//mpInit->mpEffectHandler->GetSubTitle()->Add(_W("Did not find path!\n"),2,false);
}
mvGoalPos = vGoalPos;
mbMoving = true;
mlstNodeDistances.clear();
return bRet;
}
//-----------------------------------------------------------------------
void cCharacterMove::MoveDirectToPos(const cVector3f &avFeetPos, float afTimeStep)
{
TurnToPos(avFeetPos);
GetCharBody()->Move(eCharDir_Forward,1.0f,afTimeStep);
}
//-----------------------------------------------------------------------
void cCharacterMove::TurnToAngle(float afAngle)
{
mbTurning = true;
mfGoalAngle = afAngle;
}
void cCharacterMove::TurnToPos(const cVector3f &avPos)
{
cVector3f vStartPos = mpCharBody->GetPosition();
float fGoalAngle = -cMath::GetAngleFromPoints2D(cVector2f(vStartPos.x, vStartPos.z),
cVector2f(avPos.x, avPos.z));
TurnToAngle(fGoalAngle);
}
//-----------------------------------------------------------------------
void cCharacterMove::Stop()
{
mbMoving = false;
mlstNodes.clear();
mlstNodeDistances.clear();
}
//-----------------------------------------------------------------------
bool cCharacterMove::FreeDirectPathToChar(iCharacterBody *apBody)
{
float fHeight = fabs(GetCharBody()->GetFeetPosition().y - apBody->GetFeetPosition().y);
if(fHeight > 0.8f) return false;
return mpContainer->FreePath( GetCharBody()->GetFeetPosition()+cVector3f(0,0.05f,0),
apBody->GetFeetPosition()+cVector3f(0,0.05f,0),
-1,eAIFreePathFlag_SkipDynamic);
}
//-----------------------------------------------------------------------
float cCharacterMove::DistanceToChar(iCharacterBody *apBody)
{
return cMath::Vector3Dist(mpCharBody->GetFeetPosition(), apBody->GetFeetPosition());
}
float cCharacterMove::DistanceToChar2D(iCharacterBody *apBody)
{
cVector3f vStart = mpCharBody->GetFeetPosition();
cVector3f vEnd = apBody->GetFeetPosition();
vStart.y =0;
vEnd.y =0;
return cMath::Vector3Dist(vStart,vEnd);
}
//-----------------------------------------------------------------------
cAINode* cCharacterMove::GetAINodeInRange(float afMinDistance, float afMaxDistance)
{
float fMaxDistSqr = afMaxDistance * afMaxDistance;
float fMinDistSqr = afMinDistance * afMinDistance;
int i = cMath::RandRectl(0, mpContainer->GetNodeNum()-1);
int lCount=0;
//Log("StartNode i: %d\n",i);
while(lCount< mpContainer->GetNodeNum())
{
cAINode* pNode = mpContainer->GetNode(i);
//Log("Testing node: %d '%s'\n",i,pNode->GetName().c_str());
float fDistSqr = cMath::Vector3DistSqr(pNode->GetPosition(), mpCharBody->GetFeetPosition());
if(fDistSqr <= fMaxDistSqr && fDistSqr >= fMinDistSqr)
{
return pNode;
}
++lCount;
++i;
if(i >= mpContainer->GetNodeNum()) i=0;
}
return NULL;
/*for(int i=0; iGetNodeNum(); ++i)
{
cAINode* pNode = mpContainer->GetNode(i);
MoveToPos(pNode->GetPosition());
}
return NULL;*/
}
//-----------------------------------------------------------------------
cAINode* cCharacterMove::GetAINodeAtPosInRange(const cVector3f &avPos,float afMinDistance,float afMaxDistance,
bool abRayCheck, float afEndOffset)
{
float fMaxDistSqr = afMaxDistance * afMaxDistance;
float fMinDistSqr = afMinDistance * afMinDistance;
int i = cMath::RandRectl(0, mpContainer->GetNodeNum()-1);
int lCount=0;
iPhysicsWorld* pPhysicsWorld = mpInit->mpGame->GetScene()->GetWorld3D()->GetPhysicsWorld();
while(lCount< mpContainer->GetNodeNum())
{
cAINode* pNode = mpContainer->GetNode(i);
float fDistSqr = cMath::Vector3DistSqr(pNode->GetPosition(), avPos);
if(fDistSqr <= fMaxDistSqr && fDistSqr >= fMinDistSqr)
{
if(abRayCheck)
{
mRayCallback.Reset();
cVector3f vStart = pNode->GetPosition();
//Calculate a postion offset closer to the node.
float fDist = cMath::Vector3Dist(vStart,avPos);
fDist = fDist - afEndOffset;
if(fDist< 0)fDist =0;
cVector3f vDir = cMath::Vector3Normalize(avPos - vStart);
cVector3f vEnd = vStart + vDir * fDist;
pPhysicsWorld->CastRay(&mRayCallback,vStart,vEnd,false,false,false);
if(mRayCallback.mbIntersection == false)
{
mvTempStart =vStart;
mvTempEnd = vEnd;
return pNode;
}
}
else
{
return pNode;
}
}
++lCount;
++i;
if(i >= mpContainer->GetNodeNum()) i=0;
}
return NULL;
}
//-----------------------------------------------------------------------
void cCharacterMove::SetCharBody(iCharacterBody *apCharBody)
{
mpCharBody = apCharBody;
cVector3f vSize = mpCharBody->GetSize();
vSize.y *= 2.5f;
mBoundingVolume.SetSize(vSize);
}
void cCharacterMove::SetNodeContainer(cAINodeContainer* apContainer)
{
mpContainer = apContainer;
}
void cCharacterMove::SetAStar(cAStarHandler *apAStar)
{
mpAStar = apAStar;
mpAStar->SetCallback(mpAStarCallback);
}
//-----------------------------------------------------------------------
void cCharacterMove::OnDraw(cInit *apInit)
{
//return;
//apInit->mpDefaultFont->Draw(cVector3f(5,64,100),14,cColor(1,1,1,1),eFontAlign_Left,
// "StuckCount %f",mfStuckCounter);
/*apInit->mpDefaultFont->Draw(cVector3f(5,64,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Speed: %f",mpCharBody->GetMoveSpeed(eCharDir_Forward));
apInit->mpDefaultFont->Draw(cVector3f(5,79,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Break: %f",mfCurrentBreak);*/
if(mbMoving)
apInit->mpDefaultFont->Draw(cVector3f(5,79,100),14,cColor(1,1,1,1),eFontAlign_Left,
_W("NodeDistAvg: %f"),mfNodeDistAvg);
/*apInit->mpDefaultFont->Draw(cVector3f(5,64,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Yaw: %f Pitch %f",cMath::ToDeg(mpCharBody->GetYaw()), cMath::ToDeg(mpCharBody->GetPitch()));
apInit->mpDefaultFont->Draw(cVector3f(5,79,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Speed: %f",mpCharBody->GetMoveSpeed(eCharDir_Forward));
apInit->mpDefaultFont->Draw(cVector3f(5,94,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Fwd: %s",mpCharBody->GetForward().ToString().c_str());
apInit->mpDefaultFont->Draw(cVector3f(5,110,100),14,cColor(1,1,1,1),eFontAlign_Left,
"Moving: %d",mbMoving?1:0);*/
}
//-----------------------------------------------------------------------
void cCharacterMove::OnPostSceneDraw(iLowLevelGraphics *apLowLevelGfx)
{
apLowLevelGfx->SetDepthTestActive(true);
for(int i=0; iGetNodeNum(); ++i)
{
cAINode *pNode = mpContainer->GetNode(i);
apLowLevelGfx->DrawSphere(pNode->GetPosition(),0.15f,cColor(0.6f,0.6f,0.6f,1));
for(int j=0; j < pNode->GetEdgeNum(); ++j)
{
cAINodeEdge *pEdge = pNode->GetEdge(j);
apLowLevelGfx->DrawLine(pNode->GetPosition(),pEdge->mpNode->GetPosition(),cColor(0.4f,0.4f,0.4f,1));
}
}
mpCharBody->GetBody()->RenderDebugGeometry(apLowLevelGfx,cColor(1,1,1,1));
//return;
if(mbMoving==false) return;
//////////////////////////////
//GoalPos
cVector3f vGoalPos = mvGoalPos;
if(mpContainer->GetNodeIsAtCenter()==false){
vGoalPos += cVector3f(0,mpContainer->GetCollideSize().y/2, 0);
}
apLowLevelGfx->DrawSphere(vGoalPos,0.2f, cColor(1,0,1));
cVector3f vLastVec = vGoalPos;
//////////////////////////////
//Nodes
tAINodeListIt it = mlstNodes.begin();
for(; it != mlstNodes.end(); ++it)
{
cAINode *pNode = *it;
cVector3f vNodePos = pNode->GetPosition();
if(mpContainer->GetNodeIsAtCenter()==false){
vNodePos += cVector3f(0,mpContainer->GetCollideSize().y/2, 0);
}
apLowLevelGfx->DrawSphere(vNodePos,0.2f, cColor(1,0,1));
apLowLevelGfx->DrawLine(vLastVec, vNodePos,cColor(1,0,1));
vLastVec = vNodePos;
}
//////////////////////////////
//Start pos
cVector3f vStartPos = mpCharBody->GetPosition();
//if(mpContainer->GetNodeIsAtCenter()==false){
//vStartPos += cVector3f(0,mpContainer->GetCollideSize().y/2, 0);
//}
apLowLevelGfx->DrawSphere(vStartPos,0.2f,cColor(1,0,1));
apLowLevelGfx->DrawLine(vLastVec, vStartPos,cColor(1,0,1));
apLowLevelGfx->DrawSphere(mvTempStart,0.2f,cColor(0,1,1));
apLowLevelGfx->DrawSphere(mvTempEnd,0.2f,cColor(0,1,1));
apLowLevelGfx->DrawLine(mvTempStart, mvTempEnd,cColor(0,1,1));
}
//-----------------------------------------------------------------------
///////////////////////////////////////////////////////////////////////////
// SAVE OBJECT STUFF
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
//-----------------------------------------------------------------------