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

421 lines
14 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "dgl/dgl.h"
#include "Core/color.h"
//-------------------------------------------------------------------------------------
static bool sShowFieldEdges = false;
static bool sFilterDownEdges = false;
static S32 sShowFFTeam = 0;
static S32 sEdgesDrawn = 0;
//-------------------------------------------------------------------------------------
struct SortedNode
{
GraphNode * node;
F32 dist;
SortedNode(GraphNode* n=NULL, F32 d=0.0) {node = n; dist = d;}
};
class SortedList : public Vector<SortedNode>
{
static S32 QSORT_CALLBACK cmpSortedNodes(const void* , const void* );
public:
void init(const GraphNodeList& list, const Point3F& camLoc);
void sort();
};
S32 QSORT_CALLBACK SortedList::cmpSortedNodes(const void * a,const void * b)
{
F32 A = ((SortedNode*)a)->dist;
F32 B = ((SortedNode*)b)->dist;
return (A < B ? -1 : (A > B ? 1 : 0));
}
void SortedList::sort()
{
dQsort((void* )(this->address()), this->size(), sizeof(SortedNode), cmpSortedNodes);
}
// Set up the list with distances (squared (faster)), and sort it.
void SortedList::init(const GraphNodeList& list, const Point3F& camLoc)
{
clear();
for (S32 i = 0; i < list.size(); i++)
{
GraphNode * node = list[i];
SortedNode sortNode(node, (node->location() - camLoc).lenSquared());
push_back(sortNode);
}
sort();
}
//-------------------------------------------------------------------------------------
extern void wireCube(F32 size, Point3F pos);
static void drawNode(const Point3F& pos, const ColorF& color, U32 whichType = 0)
{
glPointSize(whichType ? 9 : 4);
glBegin(GL_POINTS);
if (whichType)
glColor3f(1, 1, 1);
else
glColor3f(color.red, color.green, color.blue);
glVertex3f(pos.x, pos.y, pos.z);
glEnd();
}
static void drawNode(const Point3F& pos, U32 whichType = 0)
{
glPointSize(whichType ? 9 : 4);
glBegin(GL_POINTS);
if (whichType)
glColor3f(1, 1, 1);
else
glColor3f(1, 0, 0);
glVertex3f(pos.x, pos.y, pos.z);
glEnd();
}
// Draw node size to indicate roam radius amount. Try square root relationship.
static void drawOutdoorNode(const Point3F& pos, F32 roamRad, bool highlight)
{
F32 mapRoamRad = mSqrt(roamRad * 2.0);
S32 numPoints = mFloor(mapRoamRad) + 1;
glPointSize(numPoints);
glBegin(GL_POINTS);
if (highlight)
glColor3f(1, 1, 1);
else
glColor3f(1, 0, 0);
glVertex3f(pos.x, pos.y, pos.z);
glEnd();
}
static void renderEdge(const Point3F& src, const Point3F& dst,
const ColorF* col = 0, bool wide=false, bool flip = false)
{
const Point3F& from = (flip ? dst : src);
const Point3F& to = (flip ? src : dst);
Point3F extra = (to - from);
F32 len = extra.len();
if (len > 0.01)
{
extra *= (0.04 / len);
extra += to;
glLineWidth(wide ? 3 : 1);
glBegin(GL_LINES);
if(col)
glColor3f(col->red, col->green, col->blue);
else
glColor3f(0, 0.3, 1);
glVertex3f(from.x, from.y, from.z);
glVertex3f(extra.x, extra.y, extra.z);
glEnd();
glLineWidth(1);
sEdgesDrawn++;
}
}
//-------------------------------------------------------------------------------------
static void renderSegments(Vector<LineSegment>& segments)
{
static Point3F morph(0.0007,0.0037,0.0013);
static Point3F a(0,0,0);
for (S32 i = 0; i < segments.size(); i++)
{
ColorF color(mFabs(mCos(a.x)), mFabs(mCos(a.y)), mFabs(mCos(a.z)));
a += morph;
if (a.x > M_2PI) a.x -= M_2PI;
if (a.y > M_2PI) a.y -= M_2PI;
if (a.z > M_2PI) a.z -= M_2PI;
LineSegment& seg = segments[i];
renderEdge(seg.getEnd(0), seg.getEnd(1), &color);
}
}
static void renderBoxes(Vector<Point3F>& boxLocs)
{
for (S32 i = 0; i < boxLocs.size(); i++) {
Point3F loc = boxLocs[i];
wireCube(0.22f, loc);
}
}
void NavigationGraph::pushRenderSeg(const LineSegment& lineSeg)
{
if (mRenderThese.size() < 800)
mRenderThese.push_back(lineSeg);
}
void NavigationGraph::pushRenderBox(Point3F boxLoc, F32 zadd)
{
if (mRenderBoxes.size() < 100) {
boxLoc.z += zadd;
mRenderBoxes.push_back(boxLoc);
}
}
//-------------------------------------------------------------------------------------
// RENDERING
void NavigationGraph::render(Point3F &camPos, bool)
{
GridArea w = getWorldRect();
if (mNodeList.size() && gotOneWeCanUse())
{
U32 filters = Con::getIntVariable("$GraphRenderFilter");
sFilterDownEdges = (filters & 1);
sShowFFTeam = Con::getIntVariable("$GraphShowFFTeam");
// Signal fields with flashing-
sShowFieldEdges = !(Sim::getCurrentTime() & 0x600);
// Render the last path query, avoid transients on ends. Note we need to switch
// to something which uses a bit on the edge instead of counter on node.
for (S32 k = mTempNodeBuf.size() - 1; k >= 0; k--)
if (GraphNode * tempNode = lookupNode(mTempNodeBuf[k]))
if (!tempNode->transient())
tempNode->setOnPath();
if (haveTerrain() && sDrawOutdoorNodes)
{
GridArea c = getGridRectangle(camPos, 13);
if (w.intersect(c))
{
GraphEdge edgeBuffer[MaxOnDemandEdges];
GraphNodeList nodesInArea;
SortedList sortedList;
S32 numNodes = getNodesInArea(nodesInArea, w);
sortedList.init(nodesInArea, camPos);
sEdgesDrawn = 0;
for (S32 i = 0; i < numNodes && sEdgesDrawn < sEdgeRenderMaxOutdoor; i++)
{
bool highlight;
GraphNode * node = sortedList[i].node;
if (sShowThreatened < 0)
highlight = (node->render0() || node->submerged());
else
highlight = (node->threats() & showWhichThreats());
Point3F nodeLoc = node->getRenderPos();
F32 roamRad = getRoamRadius(nodeLoc);
drawOutdoorNode(nodeLoc, roamRad, highlight);
drawNeighbors(node, node->getEdges(edgeBuffer), camPos);
}
}
}
// if (sDrawTransients)
if (sDrawOutdoorNodes || sDrawIndoorNodes)
renderTransientNodes();
if (sDrawIndoorNodes)
renderInteriorNodes(camPos);
if (mRenderThese.size() > 0) {
renderSegments(mRenderThese);
if (mRenderThese.size() > 9) {
for (S32 i = mRenderThese.size() / 8; i >= 0; i--)
mRenderThese.erase_fast((gRandGen.randI()&0xFFFFFF) % mRenderThese.size());
}
}
if (mRenderBoxes.size() > 0) {
renderBoxes(mRenderBoxes);
if (mRenderBoxes.size() > 3) {
for (S32 i = mRenderBoxes.size() / 3; i >= 0; i--)
mRenderBoxes.erase_fast((gRandGen.randI()&0xFFFFFF) % mRenderBoxes.size());
}
}
}
}
#define IndoorRenderThresh 49.0
#define MinIndoorBoxWidth 7.0
#define MaxIndoorBoxWidth 57.0
#define MaxIndoorRender 100
void NavigationGraph::renderInteriorNodes(Point3F camPos)
{
static F32 boxW = 24;
GraphEdge edgeBuffer[MaxOnDemandEdges];
ColorF color(0, 1, 0);
GraphNodeList renderList;
//==> Would of course be better to clip to box in FRONT of camera...
Point3F boxOff(boxW, boxW, 111);
Box3F clipBox(camPos - boxOff, camPos + boxOff);
mIndoorTree.getIntersecting(renderList, clipBox);
S32 numNodes = renderList.size();
SortedList sortedList;
sortedList.init(renderList, camPos);
sEdgesDrawn = 0;
// Resize box based on density. Note we always want to go tall though.
// if (numNodes <= MaxIndoorRender)
// boxW = getMin(MaxIndoorBoxWidth, boxW + 0.5);
// else
// boxW = getMax(MinIndoorBoxWidth, boxW - 0.5);
for (S32 i = 0; i < numNodes && sEdgesDrawn < sEdgeRenderMaxIndoor; i++)
{
bool highlight;
GraphNode * node = sortedList[i].node;
Point3F pos = node->getRenderPos();
GraphEdgeArray edges = node->getEdges(edgeBuffer);
if (sShowThreatened < 0)
highlight = node->render0();
else
highlight = (node->threats() & showWhichThreats());
drawNode(pos, color, highlight);
drawNeighbors(node, edges, camPos, getMin(boxW + 40.0, 80.0));
}
}
void NavigationGraph::renderTransientNodes()
{
GraphEdge edgeBuffer[MaxOnDemandEdges];
for (S32 i = 0; i < mMaxTransients; i++)
if (GraphNode * node = mNodeList[mTransientStart + i]) {
Point3F pos = node->getRenderPos();
drawNode(pos);
drawNeighbors(node, node->getEdges(edgeBuffer), pos);
}
}
// Find the two intermediate points to connect to between node volumes. We are given
// that the edge has a border definition.
void NavigationGraph::getCrossingPoints(S32 from, Point3F* twoPoints, GraphEdge* edge)
{
GraphBoundary & B = mBoundaries[edge->mBorder];
twoPoints[0] = B.seekPt;
if (GraphEdge * edgeBack = lookupNode(edge->mDest)->getEdgeTo(from))
twoPoints[1] = mBoundaries[edgeBack->mBorder].seekPt;
else
twoPoints[1] = B.seekPt + (B.normal * (B.distIn * 2));
twoPoints[0].z += 0.22;
twoPoints[1].z += 0.22;
}
static const ColorF borderColor(0.0, 0.9, 0.11); // green
static const ColorF steepColor(0.5, 0.5, 0.5); // grey
static const ColorF jettingColor(0.9, 0.9, 0.0); // yellow
static const ColorF pathColor(1.0, 1.0, 1.0); // white
// Note that index is only used for comparison to eliminate drawing both ways.
// The transient draw routine above forces the draw by passing zero. The others
// use their proper index.
void NavigationGraph::drawNeighbors(GraphNode* fromNode, GraphEdgeArray edges,
const Point3F& camPos, F32 within)
{
Point3F fromLoc = fromNode->getRenderPos();
S32 fromIndex = fromNode->getIndex();
bool isTransient = fromNode->transient();
within *= within;
while (GraphEdge * edge = edges++) {
// if (edge->mDest > fromIndex || isTransient) {
if (1) { // Think we need to render both ways...
// Flash field edges-
if (edge->getTeam() && !sShowFieldEdges)
{
// Console variable $graphShowFFTeam will make it only flash those edges
// owned by the specified team-
if (!sShowFFTeam || edge->getTeam() == sShowFFTeam)
continue;
}
GraphNode * toNode = mNodeList[edge->mDest];
Point3F toLoc = toNode->getRenderPos(), mid, low, high;
ColorF color(0.07, 0.07, 0.98);
F32 shadePct = F32(toNode->onPath()) * (1.0 / F32(GraphMaxOnPath));
bool whitenPath = (shadePct > 0.0 && fromNode->onPath());
bool isJetting = edge->isJetting();
bool isSteep = edge->isSteep();
bool isBorder = edge->isBorder();
if (sFilterDownEdges && (toLoc.z < fromLoc.z))
continue;
if (isSteep) color = steepColor;
else if (isJetting) color = jettingColor;
else if (isBorder) color = borderColor;
if (whitenPath) {
color.red = scaleBetween(color.red, 1.0f, shadePct);
color.green = scaleBetween(color.green, 1.0f, shadePct);
color.blue = scaleBetween(color.blue, 1.0f, shadePct);
toNode->decOnPath();
fromNode->decOnPath();
}
if (isJetting) {
if (sDrawJetConnections && (toLoc-camPos).lenSquared() < within) {
bool flip;
if(fromLoc.z > toLoc.z) {
flip = true;
(mid = low = toLoc).z = (high = fromLoc).z;
}
else {
flip = false;
(mid = low = fromLoc).z = (high = toLoc).z;
}
if (edge->hasHop()) {
Point3F aboveH(high.x, high.y, high.z + edge->getHop());
mid.z += edge->getHop();
renderEdge(low, mid, &color, whitenPath, flip);
renderEdge(mid, aboveH, &color, whitenPath, flip);
renderEdge(aboveH, high, &color, whitenPath, flip);
}
else {
renderEdge(fromLoc, mid, &color, whitenPath);
renderEdge(mid, toLoc, &color, whitenPath);
}
}
}
else if (isBorder && mHaveVolumes) {
Point3F connectPts[3+1];
connectPts[0] = fromLoc;
connectPts[3] = toLoc;
getCrossingPoints(fromIndex, &connectPts[1], edge);
for (S32 c = 0; c < 3;) {
Point3F end = connectPts[c++];
Point3F start = connectPts[c];
if (isTransient && !whitenPath) {
end.z += 0.28, start.z += 0.28;
ColorF invertColor(1-color.red, 1-color.green, 1-color.blue);
renderEdge(start, end, &invertColor, whitenPath);
}
else {
renderEdge(start, end, &color, whitenPath);
}
}
}
else {
renderEdge(fromLoc, toLoc, &color, whitenPath);
}
}
}//edge loop
}