Jump to content
  • entries
    10
  • comments
    21
  • views
    22,151

Line Of Sight/Vision


Chris Paulson

1,642 views

 Share

For NPC AI to work they have to have simulated vision. This is done by using raycast/linepick.

 

My vision system does the following:-

 

Takes into account head position - using head bone

Takes into account rotation of NPC spine bone (used for aiming)

Build list of enemies in view range

Takes into account FOV

It looks through each of the ememies bones to see if any part of the enemy is visible. It returns location of the first bone found. This is to make sure enemies are still seen even if they are partially obscured. It has a render function to help with debugging the AI.

 

Here's the code: -

 

#ifndef VISION_H
#define VISION_H

#include "gamelib/include/gamelib.h"
#include "gamelib/include/scene.h"
#include "actor/include/animBody.h"

typedef enum SeenState
{
SEEN_IDLE,
SEEN_ALERT,
SEEN_ACTIVE
};

typedef enum visionMode
{
VISION_BONE,
VISION_BOUNDING,
VISION_NAVMESH
};

//
// Implement actor vision
//
class Vision
{
protected:
TScene *m_scene;
bodyControl *m_body;
float m_lastCheckTime;
float m_lastSeenTime;
float m_notSeenTime;
float m_lockTime;
float m_updateRate;
float m_idleTime;
bool m_seen;
float m_viewRange;
float m_viewAngle;
int m_cnt;
public:
SeenState m_state;
map<float,TEntity> m_nearbyEnemy;
TEntity m_entity, m_lastTarget, m_pivot, m_head, m_target;
string m_team;
TPick m_pick;
TVec3 m_lastSeenPos, m_targetPos;
Vision(TScene *s, TEntity e, TEntity head, bodyControl *pbody);

bool canSeeEnemy(visionMode mode= VISION_BONE);
bool canSeeEntity(TEntity target, visionMode mode = VISION_BONE);
bool canSeeBoundBox(TVec3 pos, TEntity target);
bool canSeeBone( TVec3 pos, TEntity target);
TVec3 getAimAtLocation();

bool navMeshCanSeeEntity(TEntity target);
inline TEntity getLastTarget() { return m_lastTarget; } 
inline SeenState Vision::getSeenState() { return m_state; }
float distanceToEnemy();

void Render(TCamera cam);
};

#endif

 

#include "actor/include/vision.h"
#include "gamelib/include/gamemath.h"
//#define DEBUG_VISION

Vision::Vision( TScene *s, TEntity e, TEntity h, bodyControl *pBody ) : m_lastCheckTime(0), m_updateRate(100), m_lastTarget(NULL), m_seen(false),
		m_viewRange(50), m_viewAngle(180), m_cnt(0), m_lockTime(5000), m_idleTime(60000)
{
m_entity = e;
m_scene = s;
m_cnt = 0;
m_lastSeenTime = 0;
m_seen = false;
m_state = SEEN_IDLE;
m_pivot = CreatePivot();
EntityParent( m_pivot, m_entity );
m_body = pBody;
m_head = h;
m_notSeenTime = AppTime();
m_team = GetEntityKey(e,"team");
}

bool Vision::navMeshCanSeeEntity(TEntity target)
{
static const int MAX_POLYS = 256;
dtStatPolyRef m_startRef;
dtStatPolyRef m_endRef;
dtStatPolyRef m_polys[MAX_POLYS];
dtStatPolyRef m_parent[MAX_POLYS];
float m_spos[3], m_epos[3];
int m_npolys;
float m_polyPickExt[3];
float wallDist, navhit;

float elapse = AppTime() - m_lastCheckTime;
if((target == m_lastTarget) && (elapse < m_updateRate))
	return m_seen;

m_targetPos = EntityPosition( target, 1);
m_lastCheckTime = AppTime();
m_lastTarget = target;
m_cnt += 1;

m_seen = false;

float dist = EntityDistance(m_entity, target);
// check If the target is within viewrange
if (dist<=m_viewRange)
{
	// observer vector
	PositionEntity( m_pivot, EntityPosition( m_head,1),1);
	TVec3 tform = TFormVector( Vec3(0,0,1), m_body->pivot, NULL);
	AlignToVector(m_pivot, tform, 3, 1);

	TVec3 observerPos = EntityPosition( m_entity, 1);
	// pick vector
	float angle = angleToDest( m_pivot, m_targetPos );
	if(angle <= (m_viewAngle/2.0))
	{
		// check If something is blocking the view
		m_spos[0] = observerPos.X*-1;
		m_spos[1] = observerPos.Y;
		m_spos[2] = observerPos.Z;
		m_epos[0] = m_targetPos.X*-1;
		m_epos[1] = m_targetPos.Y;
		m_epos[2] = m_targetPos.Z;
		m_polyPickExt[0] = 2;
		m_polyPickExt[1] = 4;
		m_polyPickExt[2] = 2;

		m_startRef = m_scene->navmesh->m_navMesh->findNearestPoly(m_spos, m_polyPickExt);
		m_npolys = m_scene->navmesh->m_navMesh->raycast(m_startRef, m_spos, m_epos, navhit, m_polys, MAX_POLYS);

		if (navhit >=1)
		{
			m_seen = true;
			m_lastTarget = target;
			m_lastSeenPos = m_targetPos;
			m_lastSeenTime = AppTime();
			m_state = SEEN_ACTIVE;
		}
	}
}
// observer cannot see target
if (!m_seen)
{
	if ((AppTime() - m_lastSeenTime) < this->m_lockTime)
		m_lastSeenPos = EntityPosition( target, 1);
	m_lastTarget = NULL;
	if ((AppTime() - m_lastSeenTime) > m_idleTime)
		m_state = SEEN_IDLE;
	else
		m_state = SEEN_ACTIVE;
}
else if (m_seen)	// Cheat if it's with locked on time
{
	m_lastTarget = target;
	m_lastSeenPos = EntityPosition( target, 1);
}
return m_seen;
}

TEntity pickSource, pickDest;

int _stdcall filter( TEntity entity ) 
{
if ( isParent(pickSource, entity) ) return 0;

return 1;
}

bool Vision::canSeeBoundBox( TVec3 pos, TEntity target)
{
TVec6 box;
float margin = 0.3;
box = GetEntityAABB( target );
box.X0 += margin;
box.Y0 += margin;
box.Z0 += margin;
box.X1 -= margin;
box.Y1 -= margin;
box.Z1 -= margin;
if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y0, box.Z0), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;

if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y1, box.Z0), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;

if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y0, box.Z0), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;

if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y1, box.Z0), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;


if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y0, box.Z1), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;

if (!LinePick( &m_pick, pos, Vec3(box.X0, box.Y1, box.Z1), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;

if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y0, box.Z1), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;

if (!LinePick( &m_pick, pos, Vec3(box.X1, box.Y1, box.Z1), 0.1,0, (BP)filter )) return true;
if (!m_pick.entity || isParent(m_target, m_pick.entity )) return true;


return false;
}

bool Vision::canSeeBone( TVec3 pos, TEntity target)
{
int i;
for(i = 1; i <= CountChildren(target); i++)
{
	TEntity e = GetChild(target, i);
	string name=GetEntityKey(e,"name");
	if (LinePick( &m_pick, pos, EntityPosition(e,1), 0,0, (BP)filter ))
		if (isParent(m_target, m_pick.entity )) return true;
	if (canSeeBone(pos, e )) return true;
}
return false;
}

int _stdcall checkIfEnemy( TEntity e, byte* extra )
{
Vision *vis = (Vision*)extra;
if (GetEntityType(e) != 3) return 1;		// Not a NPC/Player
string team = GetEntityKey(e,"team");

if (team == vis->m_team) return 1;		// On same team

vis->m_nearbyEnemy[ EntityDistance(vis->m_entity,e) ] = e;

return 1;
}

bool Vision::canSeeEnemy(visionMode mode)
{
if (m_lastTarget)
{
	if (GetEntityType(m_lastTarget) == 3)
	{
		if ((AppTime() - m_lastSeenTime) < this->m_lockTime)
			return canSeeEntity(m_lastTarget, mode );
	}
	else
	{
		m_lastTarget = NULL;
		m_state = SEEN_IDLE;
	}
}

m_nearbyEnemy.clear();
TVec6 box;
TVec3 pos;
pos = EntityPosition( m_entity, 1);
box.X0 = pos.X - m_viewRange;
box.Y0 = pos.Y - m_viewRange;
box.Z0 = pos.Z - m_viewRange;
box.X1 = pos.X + m_viewRange;
box.Y1 = pos.Y + m_viewRange;
box.Z1 = pos.Z + m_viewRange;
ForEachEntityInAABBDo( box, (BP)checkIfEnemy, (byte*)this, ENTITY_MODEL );
map<float,TEntity>::iterator enemies;

enemies=m_nearbyEnemy.begin();
while(enemies!=m_nearbyEnemy.end())
{
	if (canSeeEntity(enemies->second, mode))
		return true;
	enemies++;
}
return false;
}

bool Vision::canSeeEntity(TEntity target, visionMode mode)
{
BOOL seen;
float elapse = AppTime() - m_lastCheckTime;

if((target == m_lastTarget) && (elapse < m_updateRate))
	return m_seen;


m_targetPos = EntityPosition( target, 1);
m_lastCheckTime = AppTime();
m_lastTarget = target;
m_cnt += 1;

seen = false;

float dist = EntityDistance(m_entity, target);
// check If the target is within viewrange
if (dist<=m_viewRange)
{
	// observer vector
	TVec3 pos = EntityPosition( m_head,1);
	PositionEntity( m_pivot, pos,1);
	TVec3 tform = TFormVector( Vec3(0,0,1), m_body->pivot, NULL);
	AlignToVector(m_pivot, tform, 3, 1);

	TVec3 observerPos = EntityPosition( m_entity, 1);
	// pick vector
	float angle = angleToDest( m_pivot, m_targetPos );
	if(angle <= (m_viewAngle/2.0))
	{
		m_target = target;
		pickSource = m_entity;

		if (mode == VISION_BONE)
			seen = canSeeBone(pos, GetChild(GetChild(target,1),1));
		else if (mode == VISION_BOUNDING)
			seen = canSeeBoundBox(pos, GetChild(target,1));
		else // todo
			seen = navMeshCanSeeEntity(GetChild(target,1));
		if (seen)
		{
			m_lastTarget = target;
			m_lastSeenPos = m_targetPos;
			m_lastSeenTime = AppTime();
			m_state = SEEN_ACTIVE;
		}
	}
}
// observer cannot see target
if (!seen)
{
	m_notSeenTime = AppTime();
	if ((AppTime() - m_lastSeenTime) < this->m_lockTime)
		m_lastSeenPos = EntityPosition( target, 1);

	if ((AppTime() - m_lastSeenTime) > m_idleTime)
		m_state = SEEN_IDLE;
	else
		m_state = SEEN_ACTIVE;
	if ((AppTime() - m_lastSeenTime) < 0)		// 0.25 sec delay time - to stop twitchy ai
		m_seen = true;
	else
		m_seen = false;

}
else if (seen)	// Cheat if it's with locked on time
{
	m_lastTarget = target;
	m_lastSeenPos = EntityPosition( target, 1);
	if ((AppTime() - m_notSeenTime) < 0)		// 0.25 sec delay time - to stop twitchy ai
		m_seen = false;
	else
		m_seen = true;
}
return m_seen;
}

TVec3 Vision::getAimAtLocation()
{
if (m_pick.entity)
	return Vec3(m_pick.X, m_pick.Y, m_pick.Z );


TEntity target = getLastTarget();
target = GetChild( target, 1 );
TVec6 ab = GetEntityAABB( target );
TVec3 pos = EntityPosition( target, 1 );
pos.Y = ab.Y0 + ((ab.Y1 - ab.Y0) * 0.5);
return pos;
}

float Vision::distanceToEnemy()
{
if (!m_seen)
	return MAX_DEC;
return EntityDistance(m_entity, m_lastTarget);
}

void Vision::Render(TCamera cam)
{
#ifdef DEBUG_VISION
TVec3 pos,pos1;
float elapse = AppTime() - m_lastCheckTime;
char buf[150];
string from, to;
string stateString[3];
stateString[0] = "idle";
stateString[1] = "alert";
stateString[2] = "active";

pos = EntityPosition(m_pivot,1);
pos1 = TFormVector( Vec3( 0,0,-2), m_pivot, NULL);
SetColor( Vec4(0,1,0,1) ); // green
pos1.X += pos.X;
pos1.Y += pos.Y;
pos1.Z += pos.Z;
tdDraw( cam, pos, pos1 );
tdDrawText( cam, pos, "s" );
tdDrawText( cam, pos1, "e" );

if (m_seen) {
	SetColor( Vec4(1,1,1,1) ); // White
	tdDraw( cam, pos, m_lastSeenPos );
	from = GetEntityKey(m_lastTarget,"name","none");
	to = GetEntityKey(m_entity,"name","none");
	sprintf(buf,"See (%f) (%d) %s %s Angle (%f)", elapse, m_cnt, from.c_str(), to.c_str(), angleToDest(m_pivot, m_targetPos) );
	tdDrawText( cam, pos, buf );
}
else
{
	SetColor( Vec4(1,0,0,1) ); // red
	sprintf(buf,"No See %s (%f) (%d) Angle (%f)", stateString[m_state].c_str(), elapse, m_cnt, angleToDest(m_pivot, m_targetPos) );
	tdDrawText( cam, pos, buf );

	if (m_pick.entity)
	{
		SetColor( Vec4(0,0,1,1) ); // blue
		tdDraw( cam, pos, Vec3(m_pick.X, m_pick.Y, m_pick.Z) );
	}
}
if (m_lastTarget)
{
	SetColor( Vec4(0,1,1,1) ); // yellow
	tdDraw( cam, pos, EntityPosition(m_lastTarget,1) );
}
#endif
}

 

#include "gamemath.h"
#include "pathsteer.h"
#include <assert.h>

// 
// Convert radians to degress
//
float Rad2Deg (double Angle) 
{
 static double ratio = 180.0 / 3.141592653589793238;

 return Angle * ratio;
}


float calcAngle( TVec3 pOrigin, TVec3 pDest )
{
float a;
float b;

a = pDest.X - pOrigin.X;
b = pDest.Z - pOrigin.Z;
if (a == 0 && b == 0) return 0;

if (a < 0)
	return Rad2Deg( acos( b / sqrt( (a * a) + (b * b ))) );
else
	return Rad2Deg( -acos( b / sqrt( (a * a) + (b * b ))) );
}

//
// Calc distance to destination
//
float distanceTo(TVec3 spos, TVec3 epos, distanceType distanceFunction )
{
float dx= abs(epos.X - spos.X);
float dy = abs(epos.Y - spos.Y);
float dz= abs(epos.Z - spos.Z);
switch(distanceFunction)
{
case distanceEuclidean:
	return sqrt( (dx * dx) + (dz * dz) + (dy * dy));
case distancePseudoEuclidean:
	return (dx * dx) + (dz * dz) + (dy * dy);
case distanceManhatten:
	return dx + dz + dy;
case distanceDiagonalShortcut:
	if( dx > dz)
	     return 1.4*dz + (dx-dz);
	else
	     return 1.4*dx + (dz-dx);
default:
	assert( 0,"Bad distance function");
}
return 0;
}

//
// Calc angle from src to dst
//
float angleToDest( TEntity observer, TVec3 targetPos )
{
//observer vector
TVec3 tform = TFormVector( Vec3(0,0,1), observer, NULL);
TVec3 observerPos = EntityPosition( observer,1 );
float dist = distanceTo( observerPos, targetPos );
//pick vector
float dx = (observerPos.X - targetPos.X) / dist;
float dy = (observerPos.Y - targetPos.Y) / dist;
float dz = (observerPos.Z - targetPos.Z) / dist;
//dot product
float dot = (tform.X*dx) + (tform.Y*dy) + (tform.Z*dz);
float ang = Rad2Deg( acos( dot ) );
return ang;
}

 Share

0 Comments


Recommended Comments

There are no comments to display.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...