engine/ai/graphGroundPlan.cc
2024-01-07 04:36:33 +00:00

1068 lines
32 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "terrain/terrData.h"
#include "Editor/terrainActions.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "Collision/clippedPolyList.h"
#include "ai/graphGroundVisit.h"
#include "terrain/waterBlock.h"
GroundPlan *gGroundPlanTest = NULL;
IMPLEMENT_CONOBJECT(GroundPlan);
// 4 6 7
// 2 N 5
// 0 1 3
Point2I GroundPlan::gridOffset[8] =
{
Point2I(-1, -1),// 0
Point2I(0, -1), Point2I(-1, 0), // 1 2
Point2I(1, -1), Point2I(-1, 1), // 3 4
Point2I(1, 0), Point2I(0, 1), // 5 6
Point2I(1, 1) // 7
};
GroundPlan::GroundPlan()
{
mTerrainBlock = 0;
mTotalVisited = 0;
}
//----------------------------------------------------------------------------
GroundPlan::~GroundPlan()
{
}
//----------------------------------------------------------------------------
GridDetail GroundPlan::defaultDetail()
{
GridDetail detail;
detail.mNavFlags[0] = GridDetail::clear;
detail.mNavFlags[1] = GridDetail::clear;
detail.mShadowHts[0] = -1;
detail.mShadowHts[1] = -1;
return detail;
}
//----------------------------------------------------------------------------
void GroundPlan::initPersistFields()
{
Parent::initPersistFields();
}
//----------------------------------------------------------------------------
bool GroundPlan::onAdd()
{
if(!Parent::onAdd())
return false;
mTerrainBlock = getTerrainObj();
gGroundPlanTest = this;
return true;
}
//----------------------------------------------------------------------------
void GroundPlan::onRemove()
{
Parent::onRemove();
gGroundPlanTest = NULL;
}
//----------------------------------------------------------------------------
void GroundPlan::getClippedRegion(S32 index, S32 &st, S32 &ed, S32 &xspan, S32 &yspan)
{
xspan = 16;
yspan = 16;
st = (index - 8) - (8 * mGridDims.x);
ed = (index + 8) + (8 * mGridDims.x);
}
//----------------------------------------------------------------------------
bool GroundPlan::isOnBoundary(S32 i)
{
if (i % mGridDims.x == 0)
return true;
if (i < mGridDims.x)
return true;
if (((i+1) % mGridDims.x) == 0)
return true;
if (i >= ((mGridDims.x * mGridDims.y) - mGridDims.x))
return true;
return false;
}
//----------------------------------------------------------------------------
S32 GroundPlan::gridToIndex(const Point2I &gPos)
{
S32 index;
index = gPos.x - mGridOrigin.x;
index += ((gPos.y - mGridOrigin.y) * mGridDims.x);
return index;
}
//----------------------------------------------------------------------------
void GroundPlan::gridToWorld(const Point2I &gPos, Point3F &wPos)
{
const MatrixF &mat = mTerrainBlock->getTransform();
Point3F origin;
mat.getColumn(3, &origin);
wPos.x = gPos.x * (float)mTerrainBlock->getSquareSize() + origin.x;
wPos.y = gPos.y * (float)mTerrainBlock->getSquareSize() + origin.y;
wPos.z = getGridHeight(gPos);
}
//----------------------------------------------------------------------------
void GroundPlan::worldToGrid(const Point3F &wPos, Point2I &gPos)
{
const MatrixF & mat = mTerrainBlock->getTransform();
Point3F origin;
mat.getColumn(3, &origin);
float x = (wPos.x - origin.x) / (float)mTerrainBlock->getSquareSize();
float y = (wPos.y - origin.y) / (float)mTerrainBlock->getSquareSize();
gPos.x = (S32)mFloor(x);
gPos.y = (S32)mFloor(y);
}
//----------------------------------------------------------------------------
void GroundPlan::gridToCenter(const Point2I &gPos, Point2I &cPos)
{
cPos.x = gPos.x & TerrainBlock::BlockMask;
cPos.y = gPos.y & TerrainBlock::BlockMask;
}
//----------------------------------------------------------------------------
F32 GroundPlan::getGridHeight(const Point2I &gPos)
{
Point2I cPos;
gridToCenter(gPos, cPos);
return(fixedToFloat(mTerrainBlock->getHeight(cPos.x, cPos.y)));
}
//----------------------------------------------------------------------------
TerrainBlock *GroundPlan::getTerrainObj()
{
SimObject *obj = Sim::findObject("Terrain");
if(!obj)
return(0);
TerrainBlock *terrain = static_cast<TerrainBlock *>(obj);
return(terrain);
}
//----------------------------------------------------------------------------
bool GroundPlan::inspect(GridArea &area)
{
if(!mTerrainBlock)
{
mTerrainBlock = getTerrainObj();
if(!mTerrainBlock)
{
Con::printf("no Terrain object");
return false;
}
}
AssertFatal(area.extent.x > 0 && area.extent.y > 0, "no extents to mission area");
GridArea inspectArea;
Point3F wPos(area.point.x, area.point.y, 0);
worldToGrid(wPos, inspectArea.point);
mGridOrigin = inspectArea.point;
mGridDims.x = (area.extent.x >> 3);
mGridDims.y = (area.extent.y >> 3);
mGridDatabase.clear();
// build the initial database with default details
for(S32 y = mGridOrigin.y; y < (mGridOrigin.y + mGridDims.y); y++)
{
for(S32 x = mGridOrigin.x; x < (mGridOrigin.x + mGridDims.x); x++)
{
GridDetail detail;
detail = defaultDetail();
mGridDatabase.push_back(detail);
}
}
// perform mission area inspection
GridArea inspectionArea(mGridOrigin, mGridDims);
InspectionVisitor inspector(inspectionArea, *this);
inspector.traverse();
computeNavFlags();
findNeighbors();
Con::printf("total zero squares inspected: %d\n", mTotalVisited);
// were done!
return true;
}
//-------------------------------------------------------------------------------------
// static void doTempBreak() {}
// static Point3F minBreakPt(936, 1184, 0);
// static Point3F maxBreakPt(944, 1192, 0);
//-------------------------------------------------------------------------------------
void GroundPlan::inspectSquare(const GridArea &gridArea, GridDetail &square)
{
S32 sqSize = mTerrainBlock->getSquareSize();
Point2I cPos;
gridToCenter(gridArea.point, cPos);
bool is45 = isSplit45(cPos);
bool isEmpty = false;
Point3F verts[2][3];
Point3F mid1;
Point3F mid2;
Point3F wPos;
gridToWorld(gridArea.point+gridOffset[6], mid1); // 0,1
gridToWorld(gridArea.point+gridOffset[5], mid2); // 1,0
gridToWorld(gridArea.point, wPos);
Box3F area;
area.min = wPos;
area.max.x = area.min.x + sqSize;
area.max.y = area.min.y + sqSize;
area.max.z = getGridHeight(gridArea.point + gridOffset[7]);
// if (within_2D(area.min, minBreakPt, 0.1))
// if (within_2D(area.max, maxBreakPt, 0.1))
// doTempBreak();
if(is45)
{
verts[0][0] = area.min;
verts[0][1] = mid1;
verts[0][2] = area.max;
verts[1][0] = area.min;
verts[1][1] = area.max;
verts[1][2] = mid2;
}
else
{
verts[0][0] = area.min;
verts[0][1] = mid1;
verts[0][2] = mid2;
verts[1][0] = mid1;
verts[1][1] = area.max;
verts[1][2] = mid2;
}
square.mWorld[0][0] = verts[0][0];
square.mWorld[0][1] = verts[0][1];
square.mWorld[0][2] = verts[0][2];
square.mWorld[1][0] = verts[1][0];
square.mWorld[1][1] = verts[1][1];
square.mWorld[1][2] = verts[1][2];
// check for empty square
GridSquare *gs = mTerrainBlock->findSquare(0, gridArea.point);
if(gs->flags & GridSquare::Empty)
isEmpty = true;
if(!isEmpty)
{
U32 mask = InteriorObjectType | TurretObjectType;
RayInfo collision;
Point3F endPt1, endPt2, endPt3, endPt4;
endPt1 = verts[0][1]; // mid1 (0,1)
endPt2 = verts[1][1]; // area max
endPt3 = verts[1][2]; // mid2 (1,0)
endPt4 = verts[0][0]; // area min
for (S32 i = 0; i < 2; i++)
{
bool done = false;
if (is45) {
if (i == 0) {
if(gServerContainer.castRay(endPt4, endPt1, mask, &collision) ||
gServerContainer.castRay(endPt1, endPt2, mask, &collision) ||
gServerContainer.castRay(endPt2, endPt4, mask, &collision))
{
square.mNavFlags[i] = GridDetail::obstructed;
square.mShadowHt = -1;
done = true;
}
}
else
{
if(gServerContainer.castRay(endPt4, endPt2, mask, &collision) ||
gServerContainer.castRay(endPt2, endPt3, mask, &collision) ||
gServerContainer.castRay(endPt3, endPt4, mask, &collision))
{
square.mNavFlags[i] = GridDetail::obstructed;
square.mShadowHt = -1;
done = true;
}
}
}
else
{
if(i == 0)
{
if(gServerContainer.castRay(endPt4, endPt1, mask, &collision) ||
gServerContainer.castRay(endPt1, endPt3, mask, &collision) ||
gServerContainer.castRay(endPt3, endPt4, mask, &collision))
{
square.mNavFlags[i] = GridDetail::obstructed;
square.mShadowHt = -1;
done = true;
}
}
else
{
if(gServerContainer.castRay(endPt1, endPt2, mask, &collision) ||
gServerContainer.castRay(endPt2, endPt3, mask, &collision) ||
gServerContainer.castRay(endPt3, endPt1, mask, &collision))
{
square.mNavFlags[i] = GridDetail::obstructed;
square.mShadowHt = -1;
done = true;
}
}
}
if(!done)
{
Point3F min, mid, max;
VectorF norm1, norm2, norm3;
Box3F bBox(endPt4, endPt2);
Point3F extra(1, 1, 0);
bBox.min -= extra;
bBox.max += extra;
bBox.max.z += 1000;
if(is45) {
if(i == 0) {
min = endPt4;
mid = endPt1;
max = endPt2;
norm1.set(-1, 0, 0);
norm2.set(0, 1, 0);
norm3.set(0.7071, -0.7071, 0);
}
else {
min = endPt4;
mid = endPt2;
max = endPt3;
norm1.set(-0.7071, 0.7071, 0);
norm2.set(1, 0, 0);
norm3.set(0, -1, 0);
}
}
else {
if(i == 0) {
min = endPt4;
mid = endPt1;
max = endPt3;
norm1.set(-1, 0, 0);
norm2.set(0.7071, 0.7071, 0);
norm3.set(0, -1, 0);
}
else {
min = endPt1;
mid = endPt2;
max = endPt3;
norm1.set(0, 1, 0);
norm2.set(1, 0, 0);
norm3.set(-0.7071, -0.7071, 0);
}
}
F32 threshold = 2.2, heightDiff;
if(setupPolyList(bBox, min, mid, max, norm1, norm2, norm3, &heightDiff))
{
if(heightDiff > threshold)
{
square.mNavFlags[i] = GridDetail::shadowed;
square.mShadowHts[i] = heightDiff;
}
else
{
square.mNavFlags[i] = GridDetail::obstructed;
square.mShadowHts[i] = -1;
}
}
else
{
square.mNavFlags[i] = GridDetail::clear;
square.mShadowHts[i] = -1;
}
}
}
}
else
{
square.mNavFlags[0] = GridDetail::obstructed;
square.mNavFlags[1] = GridDetail::obstructed;
square.mShadowHts[0] = -1;
square.mShadowHts[1] = -1;
}
}
//-------------------------------------------------------------------------------------
// Changed to account for actual worst case height in the triangle. As it was, it
// would yeild negative heights on some obstructed areas on slopes, which would cause
// the inspect to mark it clear.
bool GroundPlan::setupPolyList(const Box3F &bBox,
const Point3F &min,
const Point3F &mid,
const Point3F &max,
const VectorF &norm1,
const VectorF &norm2,
const VectorF &norm3,
F32 *height )
{
ClippedPolyList polyList;
polyList.mPlaneList.clear();
polyList.mPlaneList.setSize(5);
// Build a bottom plane using the three points.
Point3F vec1 = (mid - max);
Point3F vec2 = (mid - min);
VectorF bottomNormal;
mCross(vec1, vec2, &bottomNormal);
if (bottomNormal.z > 0)
bottomNormal.z = -bottomNormal.z;
// Need to find maximum Z value as basis for determining height.
F32 highestZ = min.z;
if (max.z > highestZ)
highestZ = max.z;
if (mid.z > highestZ)
highestZ = mid.z;
// Adjust out the triangle to ensure more "breathing room" around obstacles
Point3F adjusted1 = (min + (norm1 * 0.8f));
Point3F adjusted2 = (mid + (norm2 * 0.8f));
Point3F adjusted3 = (max + (norm3 * 0.8f));
polyList.mNormal.set(0, 0, 0);
polyList.mPlaneList[0].set(adjusted1, norm1);
polyList.mPlaneList[1].set(adjusted2, norm2);
polyList.mPlaneList[2].set(adjusted3, norm3);
// polyList.mPlaneList[3].set(min, VectorF(0, 0, -1));
polyList.mPlaneList[3].set(min, bottomNormal);
polyList.mPlaneList[4].set(bBox.max, VectorF(0, 0, 1));
U32 mask = InteriorObjectType | TurretObjectType;
// build the poly list
if(gServerContainer.buildPolyList(bBox, mask, &polyList))
{
F32 lowestZ = 10000000, lowestDeltaZ;
bool found = false;
for (S32 j = 0; j < polyList.mPolyList.size(); j++) {
ClippedPolyList::Poly *p = &(polyList.mPolyList[j]);
for (S32 k = p->vertexStart; k < (p->vertexStart + p->vertexCount); k++) {
F32 ht = polyList.mVertexList[polyList.mIndexList[k]].point.z;
if(ht < lowestZ) {
lowestZ = ht;
lowestDeltaZ = (ht - highestZ);
found = true;
}
}
}
if (found) {
*height = lowestDeltaZ;
return true;
}
}
return false;
}
//----------------------------------------------------------------------------
void GroundPlan::computeNavFlags()
{
bool isBottomRow = false;
bool isLeftEdge = false;
bool is45 = false;
bool hasWater = missionHasWater();
BitSet32 obsBits;
S32 totalNodes = mGridDatabase.size();
// detection of water or liquids
Vector<WaterBlock *> blocks;
if(hasWater)
getAllWaterBlocks(blocks);
U32 i;
for(i = 0; i < totalNodes; i++)
{
is45 = isSplit45(*(getPosition(i)));
isBottomRow = false;
isLeftEdge = false;
if(i % mGridDims.x == 0)
isLeftEdge = true;
if(i < mGridDims.x)
isBottomRow = true;
GridDetail &g = mGridDatabase[i];
packTriangleBits(g, obsBits, GridDetail::obstructed, i, isLeftEdge, isBottomRow);
if(is45)
{
if(obsBits.testStrict(0xff))
g.mNavFlag = GridDetail::obstructed;
else
{
// check for shadowed node based on surrounding triangles
BitSet32 shBits;
packTriangleBits(g, shBits, GridDetail::shadowed, i, isLeftEdge, isBottomRow);
if(shBits > 0)
{
g.mNavFlag = GridDetail::shadowed;
//g.mShadowHt = 20;
g.mShadowHt = lowestShadowHt(g, shBits, i);
}
else
g.mNavFlag = GridDetail::clear;
}
}
else
{
if(obsBits.test(BIT(1)) && obsBits.test(BIT(2)) && obsBits.test(BIT(5)) && obsBits.test(BIT(6)))
g.mNavFlag = GridDetail::obstructed;
else
{
// check for shadowed node based on surrounding triangles
BitSet32 shBits;
packTriangleBits(g, shBits, GridDetail::shadowed, i, isLeftEdge, isBottomRow);
if(shBits > 0)
{
g.mNavFlag = GridDetail::shadowed;
g.mShadowHt = 20;
g.mShadowHt = lowestShadowHt(g, shBits, i);
}
else
g.mNavFlag = GridDetail::clear;
}
}
// set water bit if needed
if(hasWater)
{
U32 blockCount;
for(blockCount = 0; blockCount < blocks.size(); blockCount++)
{
Point2I *gPos = getPosition(i);
Point3F wPos;
gridToWorld((*gPos), wPos);
if((wPos.x < -292 && wPos.x > -298) && (wPos.y < -130 && wPos.y > -135))
bool test = true;
if(blocks[blockCount]->isPointSubmergedSimple(wPos))
{
g.mNavFlag = GridDetail::submerged;
break; // once the bit is set, it can't change.
}
}
}
}
}
//----------------------------------------------------------------------------
void GroundPlan::findNeighbors()
{
bool isBottomRow = false;
bool isLeftEdge = false;
bool is45;
BitSet32 obsBits;
S32 totalNodes = mGridDatabase.size();
U32 i;
for(i = 0; i < totalNodes; i++)
{
isBottomRow = false;
isLeftEdge = false;
if(i % mGridDims.x == 0)
isLeftEdge = true;
if(i < mGridDims.x)
isBottomRow = true;
is45 = isSplit45(*(getPosition(i)));
GridDetail &g = mGridDatabase[i];
packTriangleBits(g, obsBits, GridDetail::obstructed, i, isLeftEdge, isBottomRow);
// we have the info, now find the neighbors
g.mNeighbors.clear();
if(is45)
{
if(!obsBits.test(BIT(0)) || !obsBits.test(BIT(1)))
g.mNeighbors.set(BIT(0));
if(!obsBits.test(BIT(1)) || !obsBits.test(BIT(2)))
g.mNeighbors.set(BIT(1));
if(!obsBits.test(BIT(4)) || !obsBits.test(BIT(0)))
g.mNeighbors.set(BIT(2));
if(!obsBits.test(BIT(2)) || !obsBits.test(BIT(3)))
g.mNeighbors.set(BIT(3));
if(!obsBits.test(BIT(4)) || !obsBits.test(BIT(5)))
g.mNeighbors.set(BIT(4));
if(!obsBits.test(BIT(3)) || !obsBits.test(BIT(7)))
g.mNeighbors.set(BIT(5));
if(!obsBits.test(BIT(5)) || !obsBits.test(BIT(6)))
g.mNeighbors.set(BIT(6));
if(!obsBits.test(BIT(6)) || !obsBits.test(BIT(7)))
g.mNeighbors.set(BIT(7));
}
else
{
// todo:
// check for slope when crossing triangle boundry.
if(!obsBits.test(BIT(0)) && !obsBits.test(BIT(1)))
g.mNeighbors.set(BIT(0));
if(!obsBits.test(BIT(1)) || !obsBits.test(BIT(2)))
g.mNeighbors.set(BIT(1));
if(!obsBits.test(BIT(1)) || !obsBits.test(BIT(5)))
g.mNeighbors.set(BIT(2));
if(!obsBits.test(BIT(2)) && !obsBits.test(BIT(3)))
g.mNeighbors.set(BIT(3));
if(!obsBits.test(BIT(4)) && !obsBits.test(BIT(5)))
g.mNeighbors.set(BIT(4));
if(!obsBits.test(BIT(6)) || !obsBits.test(BIT(2)))
g.mNeighbors.set(BIT(5));
if(!obsBits.test(BIT(5)) || !obsBits.test(BIT(6)))
g.mNeighbors.set(BIT(6));
if(!obsBits.test(BIT(6)) && !obsBits.test(BIT(7)))
g.mNeighbors.set(BIT(7));
}
}
}
//----------------------------------------------------------------------------
S32 GroundPlan::getNavigable(Vector<U8> &navFlags, Vector<F32> &shadowHts)
{
U32 i;
S32 shadowed = 0;
S32 totalNodes = mGridDatabase.size();
navFlags.setSize( totalNodes );
shadowHts.setSize( totalNodes );
for(i = 0; i < totalNodes; i++)
{
navFlags[i] = mGridDatabase[i].mNavFlag;
shadowHts[i] = mGridDatabase[i].mShadowHt;
if(mGridDatabase[i].mNavFlag == GridDetail::shadowed)
shadowed++;
}
return shadowed;
}
//----------------------------------------------------------------------------
Vector<U8> &GroundPlan::getNeighbors(Vector<U8> &neighbors)
{
U32 i;
S32 totalNodes = mGridDatabase.size();
neighbors.setSize( totalNodes );
for(i = 0; i < totalNodes; i++)
neighbors[i] = mGridDatabase[i].mNeighbors;
return neighbors;
}
//----------------------------------------------------------------------------
void GroundPlan::packTriangleBits(const GridDetail &g, BitSet32 &bits, S32 bitType, S32 index, bool isLeftEdge, bool isBottomRow)
{
GridDetail *grid;
bool isRightEdge = false;
bool isTopRow = false;
if(((index+1) % mGridDims.x) == 0)
isRightEdge = true;
if(index >= ((mGridDims.x * mGridDims.y) - mGridDims.x))
isTopRow = true;
bits.clear();
if(isBottomRow && isRightEdge)
{
// these are considered obstructed triangles
// since we are sitting at the origin
if(bitType == GridDetail::obstructed)
bits.set(BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(6)|BIT(7));
grid = &(mGridDatabase[getNeighborDetails(index, 2)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(4));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(5));
}
else if(isLeftEdge && isTopRow)
{
// these are considered obstructed triangles
// since we are sitting at the origin
if(bitType == GridDetail::obstructed)
bits.set(BIT(0)|BIT(1)|BIT(4)|BIT(5)|BIT(6)|BIT(7));
grid = &(mGridDatabase[getNeighborDetails(index, 1)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(2));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(3));
}
else if(!isBottomRow && !isLeftEdge)
{
// checks for right and top edges
if(isTopRow && !isRightEdge)
{
if(bitType == GridDetail::obstructed)
bits.set(BIT(6)|BIT(7)|BIT(4)|BIT(5));
grid = &(mGridDatabase[getNeighborDetails(index, 0)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(0));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(1));
grid = &(mGridDatabase[getNeighborDetails(index, 1)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(2));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(3));
}
else if(isRightEdge && !isTopRow)
{
if(bitType == GridDetail::obstructed)
bits.set(BIT(2)|BIT(3)|BIT(6)|BIT(7));
grid = &(mGridDatabase[getNeighborDetails(index, 0)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(0));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(1));
grid = &(mGridDatabase[getNeighborDetails(index, 2)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(4));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(5));
}
else if(index != mGridDatabase.size()-1)
{
// need to test all triangles
grid = &(mGridDatabase[getNeighborDetails(index, 0)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(0));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(1));
grid = &(mGridDatabase[getNeighborDetails(index, 1)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(2));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(3));
grid = &(mGridDatabase[getNeighborDetails(index, 2)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(4));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(5));
if(g.mNavFlags[0] == bitType)
bits.set(BIT(6));
if(g.mNavFlags[1] == bitType)
bits.set(BIT(7));
}
else
{
if(bitType == GridDetail::obstructed)
bits.set(BIT(6)|BIT(7)|BIT(2)|BIT(3)|BIT(4)|BIT(5));
grid = &(mGridDatabase[getNeighborDetails(index, 0)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(0));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(1));
}
}
else if(!isBottomRow)
{
// these are considered obstructed triangles
// since we are sitting on the left edge
if(bitType == GridDetail::obstructed)
bits.set(BIT(0)|BIT(1)|BIT(5)|BIT(4));
grid = &(mGridDatabase[getNeighborDetails(index, 1)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(2));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(3));
if(g.mNavFlags[0] == bitType)
bits.set(BIT(6));
if(g.mNavFlags[1] == bitType)
bits.set(BIT(7));
}
else if(!isLeftEdge)
{
// these are considered obstructed triangles
// since we are sitting on the bottom row
if(bitType == GridDetail::obstructed)
bits.set(BIT(0)|BIT(1)|BIT(2)|BIT(3));
if(g.mNavFlags[0] == bitType)
bits.set(BIT(6));
if(g.mNavFlags[1] == bitType)
bits.set(BIT(7));
grid = &(mGridDatabase[getNeighborDetails(index, 2)]);
if(grid->mNavFlags[0] == bitType)
bits.set(BIT(4));
if(grid->mNavFlags[1] == bitType)
bits.set(BIT(5));
}
else
{
// these are considered obstructed triangles
// since we are sitting at the origin
if (bitType == GridDetail::obstructed)
bits.set(BIT(0)|BIT(1)|BIT(2)|BIT(3)|BIT(4)|BIT(5));
// we are at the origin
if(g.mNavFlags[0] == bitType)
bits.set(BIT(6));
if(g.mNavFlags[1] == bitType)
bits.set(BIT(7));
}
}
//----------------------------------------------------------------------------
// static Point3F breakPt(160, -272, 0);
static void doBreak() {}
//----------------------------------------------------------------------------
F32 GroundPlan::lowestShadowHt(const GridDetail &g, BitSet32 &bits, S32 index)
{
F32 minList[8];
S32 N = 0;
// if (within_2D(g.mWorld[0][0], breakPt, 1.0))
// doBreak();
GridDetail * grid = &(mGridDatabase[getNeighborDetails(index, 0)]);
if(bits.test(BIT(0)))
minList[N++] = grid->mShadowHts[0];
if(bits.test(BIT(1)))
minList[N++] = grid->mShadowHts[1];
grid = &(mGridDatabase[getNeighborDetails(index, 1)]);
if(bits.test(BIT(2)))
minList[N++] = grid->mShadowHts[0];
if(bits.test(BIT(3)))
minList[N++] = grid->mShadowHts[1];
grid = &(mGridDatabase[getNeighborDetails(index, 2)]);
if(bits.test(BIT(4)))
minList[N++] = grid->mShadowHts[0];
if(bits.test(BIT(5)))
minList[N++] = grid->mShadowHts[1];
if(bits.test(BIT(6)))
minList[N++] = g.mShadowHts[0];
if(bits.test(BIT(7)))
minList[N++] = g.mShadowHts[1];
// Not sure if this is the perfect fix - only assume there is any height to this if
// any of the shadow heights were checked. Is small value for N==0 small enough?
F32 lowHt;
if (N)
{
lowHt = 10000;
while(N--)
lowHt = getMin(lowHt, minList[N]);
}
else
{
doBreak();
lowHt = 2.0;
}
return lowHt;
}
//----------------------------------------------------------------------------
static void findWaterCallback(SceneObject *obj, S32 val)
{
Vector<SceneObject*> *list = (Vector<SceneObject *> *)val;
list->push_back(obj);
}
//----------------------------------------------------------------------------
bool GroundPlan::missionHasWater()
{
Vector<SceneObject *> objects;
gServerContainer.findObjects(WaterObjectType, findWaterCallback, (S32)&objects);
return objects.size();
}
//----------------------------------------------------------------------------
void GroundPlan::getAllWaterBlocks(Vector<WaterBlock *> &blocks)
{
Vector<SceneObject *> objects;
gServerContainer.findObjects(WaterObjectType, findWaterCallback, (S32)&objects);
blocks.setSize(objects.size());
U32 i;
for(i = 0; i < objects.size(); i++)
blocks[i] = dynamic_cast<WaterBlock*>(objects[i]);
}
//----------------------------------------------------------------
bool GroundPlan::setTerrainGraphInfo(TerrainGraphInfo* info)
{
Point2I dims(getGridDimWidth(), getGridDimHeight());
info->originGrid = getOrigin();
info->gridTopRight = info->originGrid;
info->gridDimensions = dims;
info->gridTopRight += info->gridDimensions;
info->gridTopRight += TerrainGraphInfo::gridOffs[GridBottomLeft];
// Query the ground plan database for neighbors and such:
info->numShadows = getNavigable(info->navigableFlags, info->shadowHeights);
info->nodeCount = getNeighbors(info->neighborFlags).size();
AssertFatal(info->nodeCount == dims.x * dims.y, "Weird Ground Plan Information");
// This gets set up with a separate call (since it takes a while to compute).
setSizeAndClear(info->roamRadii, info->nodeCount);
// Set up the world origin for translations.
TerrainBlock * block = getTerrainObj();
AssertFatal(block, "NavGraph setGround() can't find a terrain block....");
block->getTransform().getColumn(3, &info->originWorld);
AssertFatal(info->originWorld.z == 0.0f, "Graph expects terrain origin z == 0");
return true;
}
//----------------------------------------------------------------------------
#define AlignmentMask 31
// Round out the area to power of two boundary (AlignmentMask + 1), taking
// into the account the terrain block origin.
GridArea GroundPlan::alignTheArea(const GridArea& areaIn)
{
Point3F offsetF;
TerrainBlock * block = getTerrainObj();
block->getTransform().getColumn(3, &offsetF);
Point2I offset(mFloor(offsetF.x), mFloor(offsetF.y));
// Figure how much needs to be added adjusted area to get it aligned nice,
// then we'll add that to the original one - i.e. unadjust.
Point2I adjPointBy(areaIn.point);
adjPointBy -= offset;
adjPointBy.x &= AlignmentMask;
adjPointBy.y &= AlignmentMask;
// Add to extent to account for moving point back, then round up.
Point2I newExtent(areaIn.extent);
newExtent += adjPointBy;
newExtent.x += AlignmentMask;
newExtent.y += AlignmentMask;
newExtent.x &= (~AlignmentMask);
newExtent.y &= (~AlignmentMask);
// Assemble the area we want-
GridArea newArea(areaIn);
newArea.point -= adjPointBy;
newArea.extent = newExtent;
return newArea;
}
//----------------------------------------------------------------------------
void GroundPlan::genExterior(Point2I &min, Point2I &max, NavigationGraph *nav)
{
GridArea area;
// Persist fields can specify custom area, else use passed in mission area.
if (!nav->customArea(area))
{
area.point = min;
area.extent = max;
}
area = alignTheArea(area);
inspect(area);
}
//----------------------------------------------------------------------------
// Inspect the terrain if it is present, return bool indicating if so.
static bool cInspect(SimObject *obj, S32, const char **argv)
{
GroundPlan *plan = static_cast<GroundPlan*>(obj);
NavigationGraph *nav = static_cast<NavigationGraph*>(Sim::findObject("NavGraph"));
if(plan && nav && nav->haveTerrain())
{
Point2I point, extent;
dSscanf(argv[2], "%d %d", &point.x, &point.y);
dSscanf(argv[3], "%d %d", &extent.x, &extent.y);
plan->genExterior(point, extent, nav);
nav->setGround(plan);
return true;
}
return false;
}
//----------------------------------------------------------------------------
void GroundPlan::consoleInit()
{
Parent::consoleInit();
Con::linkNamespaces("SimObject", "GroundPlan");
Con::addCommand("GroundPlan", "inspect", cInspect, "gp.inspect(min, max);", 4, 4);
}