/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
/*
* Copyright (C) 2006-2010 - Frictional Games
*
* This file is part of HPL1 Engine.
*/
#include "hpl1/engine/ai/AINodeContainer.h"
#include "hpl1/engine/physics/PhysicsBody.h"
#include "hpl1/engine/scene/World3D.h"
#include "hpl1/engine/system/String.h"
#include "hpl1/engine/system/low_level_system.h"
#include "hpl1/engine/math/Math.h"
#include "hpl1/engine/impl/tinyXML/tinyxml.h"
#include "common/algorithm.h"
namespace hpl {
//////////////////////////////////////////////////////////////////////////
// AI NODE
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAINode::cAINode() {
}
//-----------------------------------------------------------------------
cAINode::~cAINode() {
}
//-----------------------------------------------------------------------
void cAINode::AddEdge(cAINode *pNode) {
cAINodeEdge Edge;
Edge.mpNode = pNode;
Edge.mfDistance = cMath::Vector3Dist(mvPosition, pNode->mvPosition);
Edge.mfSqrDistance = cMath::Vector3DistSqr(mvPosition, pNode->mvPosition);
mvEdges.push_back(Edge);
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// RAY INTERSECT
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cAINodeRayCallback::Reset() {
mbIntersected = false;
mpCallback = NULL;
}
//-----------------------------------------------------------------------
bool cAINodeRayCallback::Intersected() {
return mbIntersected;
}
//-----------------------------------------------------------------------
bool cAINodeRayCallback::BeforeIntersect(iPhysicsBody *pBody) {
if (pBody->GetCollideCharacter() == false)
return false;
if ((mFlags & eAIFreePathFlag_SkipStatic) && pBody->GetMass() == 0)
return false;
if ((mFlags & eAIFreePathFlag_SkipDynamic) &&
(pBody->GetMass() > 0 || pBody->IsCharacter()))
return false;
if ((mFlags & eAIFreePathFlag_SkipVolatile) && pBody->IsVolatile())
return false;
return true;
}
//-----------------------------------------------------------------------
bool cAINodeRayCallback::OnIntersect(iPhysicsBody *pBody, cPhysicsRayParams *apParams) {
if (mpCallback) {
if (mpCallback->Intersects(pBody, apParams)) {
mbIntersected = true;
return false;
} else {
return true;
}
} else {
mbIntersected = true;
return false;
}
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAINodeIterator::cAINodeIterator(cAINodeContainer *apContainer, const cVector3f &avPos, float afRadius) {
mpContainer = apContainer;
mvPosition = avPos;
mfRadius = afRadius;
// Calculate local position
cVector2f vLocalPos(mvPosition.x, mvPosition.z);
vLocalPos -= mpContainer->mvMinGridPos;
cVector2f vLocalStart = vLocalPos - cVector2f(afRadius);
cVector2f vLocalEnd = vLocalPos + cVector2f(afRadius);
mvStartGridPos = mpContainer->GetGridPosFromLocal(vLocalStart);
mvEndGridPos = mpContainer->GetGridPosFromLocal(vLocalEnd);
mvGridPos = mvStartGridPos;
mpNodeList = &mpContainer->GetGrid(mvGridPos)->mlstNodes;
while (mpNodeList->empty()) {
if (IncGridPos()) {
mpNodeList = &mpContainer->GetGrid(mvGridPos)->mlstNodes;
} else {
mpNodeList = NULL;
break;
}
}
if (mpNodeList) {
mNodeIt = mpNodeList->begin();
}
// Log("--------------------------------------\n");
// Log("Iterating (%d %d) -> (%d %d)\n", mvStartGridPos.x,mvStartGridPos.y,
// mvEndGridPos.x,mvEndGridPos.y);
}
//-----------------------------------------------------------------------
bool cAINodeIterator::HasNext() {
if (mpNodeList == NULL || mpNodeList->empty())
return false;
return true;
}
//-----------------------------------------------------------------------
cAINode *cAINodeIterator::Next() {
cAINode *pNode = *mNodeIt;
++mNodeIt;
if (mNodeIt == mpNodeList->end()) {
if (IncGridPos()) {
mpNodeList = &mpContainer->GetGrid(mvGridPos)->mlstNodes;
while (mpNodeList->empty()) {
if (IncGridPos()) {
mpNodeList = &mpContainer->GetGrid(mvGridPos)->mlstNodes;
} else {
mpNodeList = NULL;
break;
}
}
} else {
mpNodeList = NULL;
}
if (mpNodeList)
mNodeIt = mpNodeList->begin();
}
return pNode;
}
//-----------------------------------------------------------------------
bool cAINodeIterator::IncGridPos() {
mvGridPos.x++;
if (mvGridPos.x > mvEndGridPos.x) {
mvGridPos.x = mvStartGridPos.x;
mvGridPos.y++;
if (mvGridPos.y > mvEndGridPos.y) {
return false;
}
}
return true;
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cAINodeContainer::cAINodeContainer(const tString &asName, const tString &asNodeName,
cWorld3D *apWorld, const cVector3f &avCollideSize) {
mpWorld = apWorld;
mvSize = avCollideSize;
msName = asName;
msNodeName = asNodeName;
mpRayCallback = hplNew(cAINodeRayCallback, ());
mlMaxNodeEnds = 5;
mlMinNodeEnds = 2;
mfMaxEndDistance = 3.0f;
mfMaxHeight = 0.1f;
mlNodesPerGrid = 6;
mbNodeIsAtCenter = true;
}
//-----------------------------------------------------------------------
cAINodeContainer::~cAINodeContainer() {
hplDelete(mpRayCallback);
STLDeleteAll(mvNodes);
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void cAINodeContainer::ReserveSpace(size_t alReserveSpace) {
mvNodes.reserve(alReserveSpace);
}
//-----------------------------------------------------------------------
void cAINodeContainer::AddNode(const tString &asName, const cVector3f &avPosition, void *apUserData) {
cAINode *pNode = hplNew(cAINode, ());
pNode->msName = asName;
pNode->mvPosition = avPosition;
pNode->mpUserData = apUserData;
mvNodes.push_back(pNode);
m_mapNodes.insert(tAINodeMap::value_type(asName, pNode));
}
//-----------------------------------------------------------------------
int cAINodeContainer::GetNodeNum() const {
return (int)mvNodes.size();
}
//-----------------------------------------------------------------------
cAINode *cAINodeContainer::GetNodeFromName(const tString &asName) {
tAINodeMapIt it = m_mapNodes.find(asName);
if (it == m_mapNodes.end()) {
return NULL;
}
return it->second;
// return (cAINode*)STLFindByName(mvNodes,asName);
}
//-----------------------------------------------------------------------
class cSortEndNodes {
public:
bool operator()(const cAINodeEdge &aEndA, const cAINodeEdge &aEndB) {
return aEndA.mfDistance < aEndB.mfDistance;
}
};
void cAINodeContainer::Compile() {
BuildNodeGridMap();
tAINodeVecIt CurrentNodeIt = mvNodes.begin();
for (; CurrentNodeIt != mvNodes.end(); ++CurrentNodeIt) {
cAINode *pNode = *CurrentNodeIt;
////////////////////////////////////////
// Add the ends that are connected to the node.
/*Log("Node %s checks: ",pNode->GetName().c_str());
tAINodeVecIt EndNodeIt = mvNodes.begin();
for(; EndNodeIt != mvNodes.end(); ++EndNodeIt)
{
cAINode *pEndNode = *EndNodeIt;
if(pEndNode == pNode) continue;
float fDist = cMath::Vector3Dist(pNode->mvPosition, pEndNode->mvPosition);
if(fDist > mfMaxEndDistance*2) continue;
Log("'%s'(%f) ",pEndNode->GetName().c_str(),fDist);
float fHeight = fabs(pNode->mvPosition.y - pEndNode->mvPosition.y);
if( fHeight <= mfMaxHeight &&
FreePath(pNode->mvPosition, pEndNode->mvPosition,-1,eAIFreePathFlag_SkipDynamic))
{
pNode->AddEdge(pEndNode);
Log("Added!");
}
Log(", ");
}
Log("\n");*/
// Log("Node %s checks: ",pNode->GetName().c_str());
cAINodeIterator nodeIt = GetNodeIterator(pNode->mvPosition, mfMaxEndDistance * 1.5f);
while (nodeIt.HasNext()) {
cAINode *pEndNode = nodeIt.Next();
if (pEndNode == pNode)
continue;
float fDist = cMath::Vector3Dist(pNode->mvPosition, pEndNode->mvPosition);
if (fDist > mfMaxEndDistance * 2)
continue;
// Log("'%s'(%f) ",pEndNode->GetName().c_str(),fDist);
float fHeight = fabs(pNode->mvPosition.y - pEndNode->mvPosition.y);
tAIFreePathFlag flag = eAIFreePathFlag_SkipDynamic | eAIFreePathFlag_SkipVolatile;
if (fHeight <= mfMaxHeight &&
FreePath(pNode->mvPosition, pEndNode->mvPosition, -1, flag)) {
pNode->AddEdge(pEndNode);
// Log("Added!");
}
// Log(", ");
}
// Log("\n");
///////////////////////////////////////
// Sort nodes and remove unwanted ones.
Common::sort(pNode->mvEdges.data(), pNode->mvEdges.data() + pNode->mvEdges.size(), cSortEndNodes());
// Resize if to too large
if (mlMaxNodeEnds > 0 && (int)pNode->mvEdges.size() > mlMaxNodeEnds) {
pNode->mvEdges.resize(mlMaxNodeEnds);
}
// Remove ends to far, but skip if min nodes is not met
for (size_t i = 0; i < pNode->mvEdges.size(); ++i) {
if (pNode->mvEdges[i].mfDistance > mfMaxEndDistance && (int)i >= mlMinNodeEnds) {
pNode->mvEdges.resize(i);
break;
}
}
// Log(" Final edge count: %d\n",pNode->mvEdges.size());
}
}
//-----------------------------------------------------------------------
void cAINodeContainer::BuildNodeGridMap() {
bool bLog = false;
if (bLog)
Log("Nodes: %d\n", mvNodes.size());
////////////////////////////////////
// Calculate min and max
cVector2f vMin(mvNodes[0]->GetPosition().x, mvNodes[0]->GetPosition().z);
cVector2f vMax(mvNodes[0]->GetPosition().x, mvNodes[0]->GetPosition().z);
for (size_t i = 1; i < mvNodes.size(); ++i) {
cAINode *pNode = mvNodes[i];
if (vMin.x > pNode->GetPosition().x)
vMin.x = pNode->GetPosition().x;
if (vMin.y > pNode->GetPosition().z)
vMin.y = pNode->GetPosition().z;
if (vMax.x < pNode->GetPosition().x)
vMax.x = pNode->GetPosition().x;
if (vMax.y < pNode->GetPosition().z)
vMax.y = pNode->GetPosition().z;
}
mvMinGridPos = vMin;
mvMaxGridPos = vMax;
////////////////////////////////////
// Determine size of grids
int lGridNum = (int)(sqrt((float)mvNodes.size() / (float)mlNodesPerGrid) + 0.5f) + 1;
if (bLog)
Log("Grid Num: %d\n", lGridNum);
mvGridMapSize.x = lGridNum;
mvGridMapSize.y = lGridNum;
//+1 to fix so that nodes on the border has a grid)
mvGrids.resize((lGridNum + 1) * (lGridNum + 1));
mvGridSize = (mvMaxGridPos - mvMinGridPos);
mvGridSize.x /= (float)mvGridMapSize.x;
mvGridSize.y /= (float)mvGridMapSize.y;
if (bLog)
Log("GridSize: %f : %f\n", mvGridSize.x, mvGridSize.y);
if (bLog)
Log("MinPos: %s\n", mvMinGridPos.ToString().c_str());
if (bLog)
Log("MaxPos: %s\n", mvMaxGridPos.ToString().c_str());
////////////////////////////////////
// Add nodes to grid
for (size_t i = 0; i < mvNodes.size(); ++i) {
cAINode *pNode = mvNodes[i];
cVector2f vLocalPos(pNode->GetPosition().x, pNode->GetPosition().z);
vLocalPos -= mvMinGridPos;
cVector2l vGridPos(0);
// Have checks so we are sure there is no division by zero.
if (mvGridSize.x > 0)
vGridPos.x = (int)(vLocalPos.x / mvGridSize.x);
if (mvGridSize.y > 0)
vGridPos.y = (int)(vLocalPos.y / mvGridSize.y);
if (false)
Log("Adding node %d, world: (%s) local (%s), at %d : %d\n", i,
pNode->GetPosition().ToString().c_str(),
vLocalPos.ToString().c_str(),
vGridPos.x, vGridPos.y);
mvGrids[vGridPos.y * (mvGridMapSize.x + 1) + vGridPos.x].mlstNodes.push_back(pNode);
}
}
//-----------------------------------------------------------------------
cAINodeIterator cAINodeContainer::GetNodeIterator(const cVector3f &avPosition, float afRadius) {
return cAINodeIterator(this, avPosition, afRadius);
}
//-----------------------------------------------------------------------
static const cVector2f gvPosAdds[] = {cVector2f(0, 0),
cVector2f(1, 0),
cVector2f(-1, 0),
cVector2f(0, 1),
cVector2f(0, -1)};
bool cAINodeContainer::FreePath(const cVector3f &avStart, const cVector3f &avEnd, int alRayNum,
tAIFreePathFlag aFlags, iAIFreePathCallback *apCallback) {
iPhysicsWorld *pPhysicsWorld = mpWorld->GetPhysicsWorld();
if (pPhysicsWorld == NULL)
return true;
if (alRayNum < 0 || alRayNum > 5)
alRayNum = 5;
/////////////////////////////
// Calculate the right vector
const cVector3f vForward = cMath::Vector3Normalize(avEnd - avStart);
const cVector3f vUp = cVector3f(0, 1.0f, 0);
const cVector3f vRight = cMath::Vector3Cross(vForward, vUp);
// Get the center
const cVector3f vStartCenter = mbNodeIsAtCenter ? avStart : avStart + cVector3f(0, mvSize.y / 2, 0);
const cVector3f vEndCenter = mbNodeIsAtCenter ? avEnd : avEnd + cVector3f(0, mvSize.y / 2, 0);
// Get the half with and height. Make them a little smaller so that player can slide over funk on floor.
const float fHalfWidth = mvSize.x * 0.4f;
const float fHalfHeight = mvSize.y * 0.4f;
// Setup ray callback
mpRayCallback->SetFlags(aFlags);
// Iterate through all the rays.
for (int i = 0; i < alRayNum; ++i) {
cVector3f vAdd = vRight * (gvPosAdds[i].x * fHalfWidth) + vUp * (gvPosAdds[i].y * fHalfHeight);
cVector3f vStart = vStartCenter + vAdd;
cVector3f vEnd = vEndCenter + vAdd;
mpRayCallback->Reset();
mpRayCallback->mpCallback = apCallback;
pPhysicsWorld->CastRay(mpRayCallback, vStart, vEnd, false, false, false, true);
if (mpRayCallback->Intersected())
return false;
}
return true;
}
//-----------------------------------------------------------------------
void cAINodeContainer::SaveToFile(const tString &asFile) {
TiXmlDocument *pXmlDoc = hplNew(TiXmlDocument, (asFile.c_str()));
TiXmlElement *pRootElem = static_cast(pXmlDoc->InsertEndChild(TiXmlElement("AINodes")));
for (size_t i = 0; i < mvNodes.size(); ++i) {
cAINode *pNode = mvNodes[i];
TiXmlElement *pNodeElem = static_cast(pRootElem->InsertEndChild(TiXmlElement("Node")));
pNodeElem->SetAttribute("Name", pNode->GetName().c_str());
for (int edge = 0; edge < pNode->GetEdgeNum(); ++edge) {
cAINodeEdge *pEdge = pNode->GetEdge(edge);
TiXmlElement *pEdgeElem = static_cast(pNodeElem->InsertEndChild(TiXmlElement("Edge")));
pEdgeElem->SetAttribute("Node", pEdge->mpNode->GetName().c_str());
tString sDistance = cString::ToString(pEdge->mfDistance);
pEdgeElem->SetAttribute("Distance", sDistance.c_str());
}
}
if (pXmlDoc->SaveFile() == false) {
Error("Couldn't save XML file %s\n", asFile.c_str());
}
hplDelete(pXmlDoc);
}
//-----------------------------------------------------------------------
void cAINodeContainer::LoadFromFile(const tString &asFile) {
BuildNodeGridMap();
TiXmlDocument *pXmlDoc = hplNew(TiXmlDocument, (asFile.c_str()));
if (pXmlDoc->LoadFile() == false) {
Warning("Couldn't open XML file %s\n", asFile.c_str());
hplDelete(pXmlDoc);
return;
}
TiXmlElement *pRootElem = pXmlDoc->RootElement();
TiXmlElement *pNodeElem = pRootElem->FirstChildElement("Node");
for (; pNodeElem != NULL; pNodeElem = pNodeElem->NextSiblingElement("Node")) {
tString sName = cString::ToString(pNodeElem->Attribute("Name"), "");
cAINode *pNode = GetNodeFromName(sName);
TiXmlElement *pEdgeElem = pNodeElem->FirstChildElement("Edge");
for (; pEdgeElem != NULL; pEdgeElem = pEdgeElem->NextSiblingElement("Edge")) {
tString sNodeName = cString::ToString(pEdgeElem->Attribute("Node"), "");
cAINode *pEdgeNode = GetNodeFromName(sNodeName);
cAINodeEdge Edge;
Edge.mpNode = pEdgeNode;
Edge.mfDistance = cString::ToFloat(pEdgeElem->Attribute("Distance"), 0);
Edge.mfSqrDistance = Edge.mfDistance * Edge.mfDistance;
pNode->mvEdges.push_back(Edge);
}
}
hplDelete(pXmlDoc);
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
cVector2l cAINodeContainer::GetGridPosFromLocal(const cVector2f &avLocalPos) {
cVector2l vGridPos;
vGridPos.x = (int)(avLocalPos.x / mvGridSize.x);
vGridPos.y = (int)(avLocalPos.y / mvGridSize.y);
if (vGridPos.x < 0)
vGridPos.x = 0;
if (vGridPos.y < 0)
vGridPos.y = 0;
if (vGridPos.x > mvGridMapSize.x)
vGridPos.x = mvGridMapSize.x;
if (vGridPos.y > mvGridMapSize.y)
vGridPos.y = mvGridMapSize.y;
return vGridPos;
}
//-----------------------------------------------------------------------
cAIGridNode *cAINodeContainer::GetGrid(const cVector2l &avPos) {
int lIndex = avPos.y * (mvGridMapSize.x + 1) + avPos.x;
// Log(" index: %d Max: %d\n",lIndex,(int)mvGrids.size());
return &mvGrids[lIndex];
}
//-----------------------------------------------------------------------
} // namespace hpl