Jump to content

Chris Paulson

Members
  • Posts

    134
  • Joined

  • Last visited

Everything posted by Chris Paulson

  1. After a lot of fiddling I have got this working. Here's a blitz example: - Strict Framework leadwerks.engine Graphics 800,600 RegisterAbstractPath AppDir 'RegisterAbstractPath ".." If Not CreateWorld() RuntimeError "Failed to create world." Local cam:TCamera=CreateCamera() MoveEntity cam,Vec3(-0.2,0,-1) 'Local mesh:TMesh=CreateCube() 'Local sphere:TMesh=CreateSphere() Local hip:TBody = CreateBodyPivot( ) PositionEntity(hip,Vec3(0)) Local thigh:TBody = CreateBodyBox( 0.001, 0.2, 0.001 ) Local shin:TBody = CreateBodyBox( 0.001, 0.2, 0.001 ) Local foot:TBody = CreateBodyBox( 0.08, 0.02, 0.001 ) EntityType(thigh,1) EntityType(shin,1) EntityType(foot,1) Collisions(1,1,0) SetBodyDamping( thigh, 0, 0 ) SetBodyDamping( shin, 0, 0 ) SetBodyDamping( foot, 0, 0 ) MoveEntity(hip,Vec3(0,0.1,0)) MoveEntity(shin,Vec3(0,-0.2,0)) MoveEntity(foot,Vec3(0,-0.3,0)) SetBodyMass(thigh,10) SetBodyMass(shin,10) SetBodyMass(foot,10) SetBodyGravityMode( thigh,0) SetBodyGravityMode( shin,0) SetBodyGravityMode( foot,0) SetBodyFriction( thigh,0,0) SetBodyFriction( shin,0,0) SetBodyFriction( foot,0,0) Local knee:TBody = CreateBodyPivot( ) MoveEntity(knee,Vec3(0,-0.1,0)) EntityParent( knee, thigh ) SetBodyDamping( knee, 0, 0 ) Local kneeorb:TEntity = CreateSphere( 8, knee ) ScaleEntity( kneeorb, Vec3(0.01) ) Local ankle:TBody = CreateBodyPivot() MoveEntity(ankle,Vec3(0,-0.3,0),1) EntityParent( ankle, shin ) 'SetBodyMass(ankle,1) Local footorb:TEntity = CreateSphere( 8, ankle ) PositionEntity(footorb, Vec3(0,-0.3,0),1) ScaleEntity( footorb, Vec3(0.01) ) ''EntityParent( foot, shin ) Local ballhip:TJoint = CreateJointBall( hip, thigh, Vec3(0,0.1,0) ) SetBallJointLimits( ballhip, Vec3(0,0,0), 150, 1) 'Local ballhip:TJoint = CreateJointHinge( hip, thigh, Vec3(0,-0.1,0), Vec3(1,0,0) ) 'SetHingeJointLimits( ballhip, -90, 90 ) Local ballknee:TJoint = CreateJointHinge( thigh, shin, Vec3(0,-0.1,0), Vec3(0,0,1) ) SetHingeJointLimits( ballknee, -100, -5 ) 'Local ballknee:TJoint = CreateJointBall( thigh, shin, Vec3(0,-0.1,0) ) Local ballankle:TJoint = CreateJointHinge( shin, foot, Vec3(0,-0.3,0), Vec3(0,0,1) ) SetHingeJointLimits( ballankle, -40, 40 ) SetJointCollisionMode(ballhip,0) SetJointCollisionMode(ballknee,0) SetJointCollisionMode(ballankle,0) Local position:TVec3 Local lastposition:TVec3 = vec3(0,0,0) DebugPhysics(True) While Not KeyHit(KEY_ESCAPE) position=CameraProject(cam,vec3(MouseX(),MouseY(),1)) PositionEntity( foot, position) SetBodyVelocity(foot, vec3(0)) ' This is needed to make stuff solve IK at decent speed lastposition = EntityPosition(footorb,1) SetBodyVelocity(foot, vec3((position.x - lastposition.x) *5, (position.y - lastposition.y)*5, (position.z - lastposition.z)*5)) lastposition = position UpdateWorld( AppSpeed() ) ' UpdateWorld(100 ) RenderWorld DrawText "Move the mouse around.",0,0 Flip Wend
  2. Your right about the hinges etc. I'm struggling though, you can't get the rotation of a hinge/joint and you can't get it's position. If I do any of the mentioned calls LE GPFs. So I am having to try and work out these values which kind of defeats the object of using it as an easy replacement for an IK solver.
  3. Chris Paulson

    Next up...

    One thought on scene graph, it would be nice to be able to do ForEachEntityAABB( entityType, AABB... ) So you could give AI models or pivot etc a certain entity type and get there list efficiently (as per your previous post about Stalker AI)
  4. Chris Paulson

    Next up...

    Hi Josh, check out the unity locomotion add on: - All the code is downloadable as a zip from the unity web site.
  5. Great idea however bit confused by combination of bodies etc. I would have thought: - Ball joint - parented to hip pivot Body box for thigh Ball joint for knee Body box for shin Hinge for ankle Body box for foot To give:- . | . | .- but I'm probably wrong as I haven't done this before.
  6. File Name: Game WIP Missing files File Submitter: Chris Paulson File Submitted: 08 Jan 2010 File Category: C/C++ Code Sorry, I missed some files from my project. Click here to download this file
  7. So to move a foot up I'll have to rotate the hip and knee joint? I presume doing rotations like this are the only option. I guessing the maths for the rotations are going to be tricky!
  8. Hi, I've started looking a footplacement of an NPC so it's feet are on the ground instead of floating. I've tried modifying a foot bone position (using EntityPosition) believing LE had IK to sort out all the other bone positions/rotations, however what I saw was the mesh messed up. I'm I doing something wrong? Has LE really got IK?
  9. I beleive this is because the position of the controller changed in LE versions and the zero Y pos is the centre of the controller. If controller is 1.8 high do this:- pos = EntityPosition( controller, 1) PositionEntity(yourModel, vec3(pos.X, pos.Y - (1.8/2), pos.Z,1)
  10. I've thought about this for my own stuff and this is what I'd do:- Got message i've been hit by bullet Message contains location of bullet hit For each bone in my model work out with math fiddle closest bone in model to hit location next if bone is head then HEAD SHOT! endif Hopefully in a month or two I'll have a working example in C++. However it seems to me you prehaps deciding what colour to paint your hand built car before you've even tightened the first nut or done your first weld.... I'd try to get the basics done first.
  11. Thanks - the alien (a++) stuff came from AIGamedev.com. Well worth going and reading a few articles to give yourself ideas how the pro's go about stuff.
  12. I thought BOOST was multi platform and was going to be adopted as a ANSI standard?
  13. File Name: Game WIP File Submitter: Chris Paulson File Submitted: 05 Jan 2010 File Category: C/C++ Code Hi, a few people have been asking for access to my code so here it is. It's WIP so not tidy, as weeks/months go on it will hopefully improve and I'll post updates. Credits go to:- Pixel Perfect for the player controller Lumooja for Gamelib (however I have hacked it to bits) Mikko Mononen for recast AIGamedev.com for A++ Click here to download this file
  14. If your doing threaded stuff in C++ may I suggest the BOOST libraries. I have examples code if you need it.
  15. Placing a rifle to sit in the hands of an NPC can be a bit of a fiddle so I thought I'd post the code. The code relies on bones for accurate placement:- Trigger bone Gun Butt/Rest bone Trigger hand Butt hand I place config stuff like this in an XML file for easy editting, to read the XML I use a nice library tinyXML. Ignore the bullet code as this is a temp hack I will tidy later. Hope this helps. PS Thanks to Pixel Perfect for supplying gun model etc for my testing, without him I'd be "assetless". #include "gun/include/gun.h" #include "gun/include/bullet.h" #include "gamelib.h" //#define DEBUG_GUN_POS ActorGun::ActorGun( TScene *pScene, string pFile, TEntity par, TEntity spine, TEntity pTrig, TEntity pRest ): m_fireRate(300), m_range(50) { m_lastFireTime = AppTime(); m_scene = pScene; m_scene->framewerk.GetMain().SetWorld(); m_entity = LoadModel( (char *)pFile.c_str() ); m_spine = spine; m_parent = par; EntityType(m_entity, 5,1); EntityParent( m_entity, m_parent); m_handTrigger = pTrig; m_handRest = pRest; HookUpdateEvent(m_scene->updateEvent); #ifdef DEBUG_GUN_POS HookRenderEvent(m_scene->renderEvent); #endif } bool ActorGun::withinRange( float pDist ) { return (pDist <= m_range); } void ActorGun::Render( float gameLoopTime) { #ifdef DEBUG_GUN_POS TVec3 trigPos = EntityPosition( m_handTrigger, 1); TVec3 restPos = EntityPosition( m_handRest, 1); TVec3 trigBonePos = EntityPosition( m_triggerBone, 1); TVec3 restBonePos = EntityPosition( m_restBone, 1); SetColor( Vec4(1,0,0,1) ); // red tdDraw( m_scene->cam, trigPos, Vec3( trigPos.X, trigPos.Y + 0.1, trigPos.Z) ); SetColor( Vec4(0,0,1,1) ); // blue tdDraw( m_scene->cam, restPos, Vec3( restPos.X, restPos.Y + 0.1, restPos.Z) ); SetColor( Vec4(0,1,0,1) ); // green tdDraw( m_scene->cam, trigBonePos, Vec3( trigBonePos.X, trigBonePos.Y - 0.1, trigBonePos.Z) ); SetColor( Vec4(1,1,1,1) ); // white tdDraw( m_scene->cam, restBonePos, Vec3( restBonePos.X, restBonePos.Y - 0.1, restBonePos.Z) ); TVec3 pos = EntityPosition(m_muzzelBone,1); TVec3 pos1 = TFormVector( Vec3(0,0,-1), m_spine, NULL); pos1.X += pos.X; pos1.Y += pos.Y; pos1.Z += pos.Z; tdDraw( m_scene->cam, pos, pos1 ); #endif } // Position the gun in the actors hand void ActorGun::Update(float gameLoopTime) { TVec3 trigPos; TVec3 restPos = EntityPosition( m_handRest, 1); TVec3 handPos = EntityPosition( m_handTrigger, 1); float diffx = handPos.X - restPos.X; float diffy = handPos.Y - restPos.Y; float diffz = handPos.Z - restPos.Z; AlignToVector( m_entity, Vec3(diffx,diffy,diffz),3 ); TVec3 pos = EntityPosition(m_entity,1); trigPos = EntityPosition(m_triggerBone,1); diffx = pos.X - trigPos.X; diffy = pos.Y - trigPos.Y; diffz = pos.Z - trigPos.Z; trigPos = EntityPosition( m_handTrigger, 1); trigPos.X += diffx; trigPos.Y += diffy; trigPos.Z += diffz; PositionEntity(m_entity, trigPos, 1); list<Bullet*>::iterator bulletList; for(bulletList = m_bullets.begin(); bulletList != m_bullets.end(); ) { if ((*bulletList)->m_life == 0) { delete *bulletList; m_bullets.erase( bulletList++ ); } else ++bulletList; } } bool ActorGun::fireGun() { if (AppTime() < m_lastFireTime ) return false; m_lastFireTime = AppTime() + m_fireRate; Bullet *bullet; TVec3 pos; pos = EntityPosition(m_muzzelBone,1); //bullet = new Bullet( pos, TFormVector( Vec3(0,0,-1), m_muzzelBone, NULL), m_scene ); bullet = new Bullet( pos, TFormVector( Vec3(0,0,-1), m_spine, NULL), m_scene ); m_bullets.push_back( bullet ); return true; } TEntity gunPickSource; int _stdcall gunFilter( TEntity entity ) { if ( isParent(gunPickSource, entity) ) return 0; return 1; } bool ActorGun::canHit( TEntity target, TVec3 tagetPos) { gunPickSource = m_entity; TVec3 pos = EntityPosition(m_muzzelBone,1); TPick pick; if (LinePick( &pick, pos, tagetPos, 0, 0, (BP)gunFilter)) { if (isParent(target, pick.entity )) return true; } else return true; return false; } // Read in gun information from an XML file bool ActorGun::readGunFile( string pFile ) { string filename; string typeName; string bone; filename = AbstractPath( (str)pFile.c_str()); TiXmlDocument doc( filename.c_str() ); TiXmlElement* child = 0; TiXmlElement *bones = 0; doc.LoadFile( filename.c_str() ); bones = doc.FirstChildElement("bones"); if(bones) { child = bones->FirstChildElement(); while(child) { typeName = child->Attribute("type"); if(typeName == "trigger") { m_triggerBone = FindChild( m_entity, (str)child->Attribute("name") ); m_triggerPos = EntityPosition( m_triggerBone ); } typeName = child->Attribute("type"); if(typeName == "rest") { m_restBone = FindChild( m_entity, (str)child->Attribute("name") ); m_restPos = EntityPosition( m_restBone ); } if(typeName == "muzzel") { m_muzzelBone = FindChild( m_entity, (str)child->Attribute("name") ); } child = child->NextSiblingElement(); } } return true; } Include: - #ifndef GUN_H #define GUN_H #include "tinyxml.h" #include "gamelib/include/scene.h" #include "event/include/gameEvent.h" #include "gun/include/bullet.h" #include <list> class ActorGun : public GameObject { protected: TScene *m_scene; public: TEntity m_parent; TEntity m_entity; TEntity m_actor; TEntity m_handTrigger; TEntity m_handRest; TEntity m_triggerBone; TEntity m_restBone; TEntity m_muzzelBone; TEntity m_spine; TVec3 m_triggerPos; TVec3 m_restPos; list<Bullet*> m_bullets; float m_fireRate; float m_lastFireTime; float m_range; ActorGun( TScene *pScene, std::string pFile, TEntity par, TEntity spine, TEntity pTrig, TEntity pRest ); virtual void Update( float gameLoopTime); virtual void Render( float gameLoopTime); bool fireGun(); bool withinRange( float pDist ); bool canHit( TEntity target, TVec3 tagetPos); bool readGunFile( std::string pFile ); }; #endif
  16. Just thought of another thing that would be great for making "thread safe". Raycasting (linepick), basically a working game will be doing 49% rendering, 29% pyhsics, 20% line pick and 2% other stuff IMHO. Linepicks are expensive so it would be great to be able to split them into a thread taking requests off a queue.
  17. I found everything to do with LE is not thread safe, even simple stuff like EntityPosition. I would really like LE (or bits of) to be thread safe so I could sub-divide up my AI stuff into threads. It would be a big bonus if a single place like RenderWorld or RenderLights which are intensive were made thread safe.
  18. I was worried that doing that many line picks would be just as slow. But with your evidence I'll drop the controller. I was going to in the future anyway as I wanted to do foot placement and IK as I think floaty NPCs on slopes/stairs looks "old hat". For far off characters (not viewed by players eyes) I could use the navmesh as it knows an estimate of where the floor is and recast is very fast. However footplacement my be beyond me and will be out of reach for a lot of leadwerks users, so the controller does need to be sorted.
  19. Since my last update I have tried to get recast callable from within blitz to make it available for everyone. However I have failed as I have not got enough Blitz/C++/DLL skills. I'm hoping someone will offer some help. I was unhappy how I handled dynamic movable objects as there was a chance the steering wouldn't avoid hitting them. Because of this I rewrote my interface to recast to use a tiled nav mesh, this means I could dynamically update a tile if a object moved in/out of a tile. The regeneration of a tile could take upto 20ms which would cause nasty frame rate spikes so I had to move the processing and pathfinding etc into a different thread. This has done the trick and I now get smooth frame rates even when causing lots of tile regenerations. I found out while doing this however that Leadwerks doesn't like threading, if ANY LW commands is issued while a thread is doings a LW command the program crashes. This mean't I had to implement mutex locks around LW commands. Here's the code for handling the navmesh requests:- #include "leadcast/include/navmesh.h" #include <time.h> #include "gamelib.h" #define DEBUG_PATH NavMesh::NavMesh() : m_keepInterResults(false), m_tileMesh(NULL), m_chunkyMesh(0), m_triflags(0), m_solid(0), m_chf(0), m_cset(0), m_pmesh(0), m_dmesh(0), m_tileSize(32), m_tileTriCount(0), m_leadMesh(NULL), m_showMesh(false), m_stoprequested(false) { resetCommonSettings(); memset(m_tileBmin, 0, sizeof(m_tileBmin)); memset(m_tileBmax, 0, sizeof(m_tileBmax)); m_polyPickExt[0] = 2; m_polyPickExt[1] = 4; m_polyPickExt[2] = 2; } static void getPolyCenter(dtTiledNavMesh* navMesh, dtTilePolyRef ref, float* center) { const dtTilePoly* p = navMesh->getPolyByRef(ref); if (!p) return; const float* verts = navMesh->getPolyVertsByRef(ref); center[0] = 0; center[1] = 0; center[2] = 0; for (int i = 0; i < (int)p->nv; ++i) { const float* v = &verts[p->v[i]*3]; center[0] += v[0]; center[1] += v[1]; center[2] += v[2]; } const float s = 1.0f / p->nv; center[0] *= s; center[1] *= s; center[2] *= s; } void NavMesh::makeTileMesh(const dtTileHeader* header) { TMaterial material; for (int i = 0; i < header->npolys; ++i) { const dtTilePoly* p = &header->polys[i]; const dtTilePolyDetail* pd = &header->dmeshes[i]; int vp; material = CreateMaterial(); SetMaterialColor( material , Vec4( float(rand()) / float(RAND_MAX), float(rand()) / float(RAND_MAX), float(rand()) / float(RAND_MAX), 1) ); TSurface surface = CreateSurface(m_leadMesh, material); int vidx = 0; for (int j = 0; j < pd->ntris; ++j) { const unsigned char* t = &header->dtris[(pd->tbase+j)*4]; for (int k = 0; k < 3; ++k) { if (t[k] < p->nv) { vp = p->v[t[k]]*3; AddVertex( surface, Vec3( header->verts[ vp ]*-1, header->verts[ vp+1], header->verts[ vp+2]),Vec3(0,0,1) ); } else { vp = (pd->vbase+t[k]-p->nv)*3; AddVertex( surface, Vec3( header->dverts[ vp ]*-1, header->dverts[ vp+1], header->dverts[ vp+2]),Vec3(0,0,1) ); } } AddTriangle( surface, vidx+1, vidx, vidx+2 ); vidx +=3; } } } void NavMesh::makeTiledNavMesh(const dtTiledNavMesh* mesh) { if (!mesh) return; if (m_leadMesh) { FreeEntity(m_leadMesh); } m_leadMesh = CreateMesh(NULL); for (int i = 0; i < DT_MAX_TILES; ++i) { const dtTile* tile = mesh->getTile(i); if (!tile->header) continue; makeTileMesh(tile->header); } // FlipMesh(m_leadMesh); UpdateMesh( m_leadMesh ); } void NavMesh::buildAllTiles() { const float ts = m_tileSize*m_cellSize; float x,y,z; float sTime = clock(); char buf[256]; y = m_bmin[1]; for(x = m_bmin[0]-ts; x<=(m_bmax[0]+ts);x+=ts) { for(z = m_bmin[2]-ts; z<=(m_bmax[2]+ts);z+=ts) { buildTile( Vec3(x,y,z) ); } } sprintf(buf, "Time %f", clock() - sTime); AppLogMode(1); AppLog(buf); } bool NavMesh::init() { if (!m_verts || !m_tris) { printf("No verts or tris\n"); return false; } // delete m_navMesh; m_tileMesh = NULL; m_tileMesh = new dtTiledNavMesh; if (!m_tileMesh) { AppLog("Could not allocate navmehs"); return false; } if (!m_tileMesh->init(m_bmin, m_tileSize*m_cellSize, m_agentMaxClimb*m_cellHeight)) { AppLog("Could not init navmesh"); return false; } // Build chunky mesh. // delete m_chunkyMesh; m_chunkyMesh = new rcChunkyTriMesh; if (!m_chunkyMesh) { AppLog("buildTiledNavigation: Out of memory 'm_chunkyMesh'."); return false; } if (!rcCreateChunkyTriMesh(m_verts, m_tris, m_ntris, 256, m_chunkyMesh)) { AppLog("buildTiledNavigation: Could not build chunky mesh."); return false; } return true; } void NavMesh::buildTile(const TVec3 pos) { if (!m_tileMesh) return; const float ts = m_tileSize*m_cellSize; int tx = (int)floorf((pos.X-m_bmin[0]) / ts); int ty = (int)floorf((pos.Z-m_bmin[2]) / ts); if (tx < 0 || ty < 0) return; rcMeshLoaderSBX *loader = new rcMeshLoaderSBX; m_tileBmin[0] = m_bmin[0] + tx*ts; m_tileBmin[1] = m_bmin[1]; m_tileBmin[2] = m_bmin[2] + ty*ts; m_tileBmax[0] = m_bmin[0] + (tx+1)*ts; m_tileBmax[1] = m_bmax[1]; m_tileBmax[2] = m_bmin[2] + (ty+1)*ts; { boost::mutex::scoped_lock l(m_mutex); TVec6 box; box.X1 = m_tileBmin[0]*-1; box.Y0 = m_tileBmin[1]; box.Z0 = m_tileBmin[2]; box.X0 = m_tileBmax[0]*-1; box.Y1 = m_tileBmax[1]; box.Z1 = m_tileBmax[2]; loader->processDynamic( box ); m_dverts = loader->getVerts(); m_ndverts = loader->getVertCount(); m_dtris = loader->getTris(); m_ndtris = loader->getTriCount(); } // m_tileCol[0] = 0.3f; m_tileCol[1] = 0.8f; m_tileCol[2] = 0; m_tileCol[3] = 1; int dataSize = 0; unsigned char* data = buildTileMesh(m_tileBmin, m_tileBmax, dataSize); if (data) { boost::mutex::scoped_lock l(m_mutex); // Remove any previous data (navmesh owns and deletes the data). m_tileMesh->removeTileAt(tx,ty,0,0); // Let the navmesh own the data. if (!m_tileMesh->addTileAt(tx,ty,data,dataSize,true)) delete [] data; } delete loader; } void NavMesh::removeTile(const TVec3 pos) { if (!m_tileMesh) return; const float ts = m_tileSize*m_cellSize; const int tx = (int)floorf((pos.X-m_bmin[0]) / ts); const int ty = (int)floorf((pos.Z-m_bmin[2]) / ts); m_tileBmin[0] = m_bmin[0] + tx*ts; m_tileBmin[1] = m_bmin[1]; m_tileBmin[2] = m_bmin[2] + ty*ts; m_tileBmax[0] = m_bmin[0] + (tx+1)*ts; m_tileBmax[1] = m_bmax[1]; m_tileBmax[2] = m_bmin[2] + (ty+1)*ts; m_tileCol[0] = 0.8f; m_tileCol[1] = 0.1f; m_tileCol[2] = 0; m_tileCol[3] = 1; unsigned char* rdata = 0; int rdataSize = 0; if (m_tileMesh->removeTileAt(tx,ty,&rdata,&rdataSize)) delete [] rdata; } void NavMesh::cleanup() { delete [] m_triflags; m_triflags = 0; delete m_solid; m_solid = 0; delete m_chf; m_chf = 0; delete m_cset; m_cset = 0; delete m_pmesh; m_pmesh = 0; delete m_dmesh; m_dmesh = 0; } unsigned char* NavMesh::buildTileMesh(const float* bmin, const float* bmax, int& dataSize) { if (!m_verts || ! m_tris) { AppLog( "buildNavigation: Input mesh is not specified."); return 0; } cleanup(); // Init build configuration from GUI memset(&m_cfg, 0, sizeof(m_cfg)); m_cfg.cs = m_cellSize; m_cfg.ch = m_cellHeight; m_cfg.walkableSlopeAngle = m_agentMaxSlope; m_cfg.walkableHeight = (int)ceilf(m_agentHeight / m_cfg.ch); m_cfg.walkableClimb = (int)ceilf(m_agentMaxClimb / m_cfg.ch); m_cfg.walkableRadius = (int)ceilf(m_agentRadius / m_cfg.cs); m_cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); m_cfg.maxSimplificationError = m_edgeMaxError; m_cfg.minRegionSize = (int)rcSqr(m_regionMinSize); m_cfg.mergeRegionSize = (int)rcSqr(m_regionMergeSize); m_cfg.maxVertsPerPoly = (int)m_vertsPerPoly; m_cfg.tileSize = (int)m_tileSize; m_cfg.borderSize = m_cfg.walkableRadius*2 + 2; // Reserve enough padding. m_cfg.width = m_cfg.tileSize + m_cfg.borderSize*2; m_cfg.height = m_cfg.tileSize + m_cfg.borderSize*2; m_cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; vcopy(m_cfg.bmin, bmin); vcopy(m_cfg.bmax, bmax); m_cfg.bmin[0] -= m_cfg.borderSize*m_cfg.cs; m_cfg.bmin[2] -= m_cfg.borderSize*m_cfg.cs; m_cfg.bmax[0] += m_cfg.borderSize*m_cfg.cs; m_cfg.bmax[2] += m_cfg.borderSize*m_cfg.cs; // Allocate voxel heighfield where we rasterize our input data to. m_solid = new rcHeightfield; if (!m_solid) { AppLog("buildNavigation: Out of memory 'solid'."); return 0; } if (!rcCreateHeightfield(*m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { AppLog( "buildNavigation: Could not create solid heightfield."); return 0; } // Allocate array that can hold triangle flags. // If you have multiple meshes you need to process, allocate // and array which can hold the max number of triangles you need to process. m_triflags = new unsigned char[__max(m_chunkyMesh->maxTrisPerChunk,m_ndtris)]; if (!m_triflags) { AppLog( "buildNavigation: Out of memory 'triangleFlags' (%d).", m_chunkyMesh->maxTrisPerChunk); return 0; } float tbmin[2], tbmax[2]; tbmin[0] = m_cfg.bmin[0]; tbmin[1] = m_cfg.bmin[2]; tbmax[0] = m_cfg.bmax[0]; tbmax[1] = m_cfg.bmax[2]; int cid[256];// TODO: Make grow when returning too many items. int ncid = rcGetChunksInRect(m_chunkyMesh, tbmin, tbmax, cid, 256); if (!ncid) return 0; m_tileTriCount = 0; for (int i = 0; i < ncid; ++i) { const rcChunkyTriMeshNode& node = m_chunkyMesh->nodes[cid[i]]; const int* tris = &m_chunkyMesh->tris[node.i*3]; const int ntris = node.n; m_tileTriCount += ntris; memset(m_triflags, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(m_cfg.walkableSlopeAngle, m_verts, m_nverts, tris, ntris, m_triflags); rcRasterizeTriangles(m_verts, m_nverts, tris, m_triflags, ntris, *m_solid); } // Do dynamic stuff here if (m_ndverts) { memset(m_triflags, 0, m_ndtris*sizeof(unsigned char)); m_tileTriCount += m_ndtris; rcMarkWalkableTriangles(m_cfg.walkableSlopeAngle, m_dverts, m_ndverts, m_dtris, m_ndtris, m_triflags); rcRasterizeTriangles(m_dverts, m_ndverts, m_dtris, m_triflags, m_ndtris, *m_solid); } if (!m_keepInterResults) { delete [] m_triflags; m_triflags = 0; } // Once all geoemtry is rasterized, we do initial pass of filtering to // remove unwanted overhangs caused by the conservative rasterization // as well as filter spans where the character cannot possibly stand. rcFilterLedgeSpans(m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(m_cfg.walkableHeight, *m_solid); // Compact the heightfield so that it is faster to handle from now on. // This will result more cache coherent data as well as the neighbours // between walkable cells will be calculated. m_chf = new rcCompactHeightfield; if (!m_chf) { AppLog( "buildNavigation: Out of memory 'chf'."); return 0; } if (!rcBuildCompactHeightfield(m_cfg.walkableHeight, m_cfg.walkableClimb, RC_WALKABLE, *m_solid, *m_chf)) { AppLog( "buildNavigation: Could not build compact data."); return 0; } if (!m_keepInterResults) { delete m_solid; m_solid = 0; } // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(*m_chf)) { AppLog( "buildNavigation: Could not build distance field."); return 0; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(*m_chf, m_cfg.walkableRadius, m_cfg.borderSize, m_cfg.minRegionSize, m_cfg.mergeRegionSize)) { AppLog( "buildNavigation: Could not build regions."); return 0; } // Create contours. m_cset = new rcContourSet; if (!m_cset) { AppLog("buildNavigation: Out of memory 'cset'."); return 0; } if (!rcBuildContours(*m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { AppLog( "buildNavigation: Could not create contours."); return 0; } // Build polygon navmesh from the contours. m_pmesh = new rcPolyMesh; if (!m_pmesh) { AppLog("buildNavigation: Out of memory 'pmesh'."); return 0; } if (!rcBuildPolyMesh(*m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { AppLog( "buildNavigation: Could not triangulate contours."); return 0; } // Build detail mesh. m_dmesh = new rcPolyMeshDetail; if (!m_dmesh) { AppLog( "buildNavigation: Out of memory 'dmesh'."); return 0; } if (!rcBuildPolyMeshDetail(*m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) { AppLog( "buildNavigation: Could build polymesh detail."); return 0; } if (!m_keepInterResults) { delete m_chf; m_chf = 0; delete m_cset; m_cset = 0; } unsigned char* navData = 0; int navDataSize = 0; if (m_cfg.maxVertsPerPoly == DT_TILE_VERTS_PER_POLYGON) { // Remove padding from the polymesh data. TODO: Remove this odditity. for (int i = 0; i < m_pmesh->nverts; ++i) { unsigned short* v = &m_pmesh->verts[i*3]; v[0] -= (unsigned short)m_cfg.borderSize; v[2] -= (unsigned short)m_cfg.borderSize; } if (!dtCreateNavMeshTileData(m_pmesh->verts, m_pmesh->nverts, m_pmesh->polys, m_pmesh->npolys, m_pmesh->nvp, m_dmesh->meshes, m_dmesh->verts, m_dmesh->nverts, m_dmesh->tris, m_dmesh->ntris, bmin, bmax, m_cfg.cs, m_cfg.ch, m_cfg.tileSize, m_cfg.walkableClimb, &navData, &navDataSize)) { AppLog( "Could not build Detour navmesh."); } } m_tileMemUsage = navDataSize/1024.0f; dataSize = navDataSize; return navData; } void NavMesh::testBuild() { float meshBMin[3], meshBMax[3]; m_loader = new rcMeshLoaderSBX(); m_loader->process(); resetCommonSettings(); rcCalcBounds(m_loader->getVerts(), m_loader->getVertCount(), m_bmin, m_bmax); m_verts = m_loader->getVerts(); m_tris = m_loader->getTris(); m_ntris = m_loader->getTriCount(); init(); buildAllTiles(); if (m_showMesh) makeTiledNavMesh( m_tileMesh ); } void NavMesh::calcTilePos( TVec3 pos, TilePos &tpos) { const float ts = m_tileSize*m_cellSize; tpos.tx = (int)floorf((pos.X-m_bmin[0]) / ts); tpos.ty = (int)floorf((pos.Z-m_bmin[2]) / ts); } void NavMesh::addBuildRequest( TVec3 pos, TVec6 box, TEntity e ) { TilePos tpos; box.X0 *=-1; box.X1 *=-1; //bl pos = Vec3(box.X0,box.Y0,box.Z0); calcTilePos( pos, tpos ); m_rebuildRequest[ tpos ] = pos; //tl pos = Vec3( box.X0, box.Y0, box.Z1); calcTilePos( pos, tpos ); m_rebuildRequest[ tpos ] = pos; //br pos = Vec3( box.X1, box.Y0, box.Z0); calcTilePos( pos, tpos ); m_rebuildRequest[ tpos ] = pos; //tr pos = Vec3( box.X1, box.Y0, box.Z1); calcTilePos( pos, tpos ); m_rebuildRequest[ tpos ] = pos; } NavRequest* NavMesh::newPathRequest( TVec3 spos, TVec3 epos ) { if (spos == epos) return NULL; NavRequest *req = new NavRequest; req->m_spos[0] = spos.X*-1; req->m_spos[1] = spos.Y; req->m_spos[2] = spos.Z; req->m_epos[0] = epos.X*-1; req->m_epos[1] = epos.Y; req->m_epos[2] = epos.Z; req->m_startPos = spos; req->m_endPos = spos; req->m_mesh = m_tileMesh; m_pathRequests[ req ] = req; return req; } void NavMesh::removePathRequest( NavRequest *nav) { m_pathRequests.erase( nav ); delete nav; } float NavMesh::findDistanceToWall( TVec3 p, float dist, float *hit, float *hitNormal, int *npolys, float *navhit ) { float spos[3], epos[3]; float wallDist; dtTilePolyRef startRef; spos[0] = p.X*-1; spos[1] = p.Y; spos[2] = p.Z; epos[0] = p.X*-1; epos[1] = p.Y; epos[2] = p.Z; startRef = m_tileMesh->findNearestPoly(spos, m_polyPickExt); if (!startRef) { AppLog( "Not anywhere near navmesh"); return 999999; } *npolys = m_tileMesh->raycast( startRef, spos, epos, *navhit, m_polys, MAX_POLYS); wallDist = m_tileMesh->findDistanceToWall(startRef, spos, dist, hit, hitNormal); return wallDist; } void NavMesh::startThread() { m_thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&NavMesh::handleThread, this))); } void NavMesh::handleThread() { while (!m_stoprequested) { update(); Sleep(30); } } void NavMesh::update() { // float startTime = AppTime(); NavRequest *navreq; std::map<TilePos,TVec3,TilePosCompare>::iterator req; std::map<NavRequest*,NavRequest*>::iterator findList; bool doneOne = false; req=m_rebuildRequest.begin(); if (req!=m_rebuildRequest.end()) { //removeTile(req->second); buildTile( req->second ); m_rebuildRequest.erase(req); navVersion++; doneOne = true; } if (m_showMesh && doneOne) makeTiledNavMesh( m_tileMesh ); { boost::mutex::scoped_lock l(m_mutex); findList = m_pathRequests.begin(); while (findList != m_pathRequests.end()) { findList->second->processPathRequest(); m_pathRequests.erase(findList++); } } } void NavRequest::makeBasicPath() { m_navVersion = navVersion; m_startRef = m_mesh->findNearestPoly(m_spos, m_polyPickExt); m_endRef = m_mesh->findNearestPoly(m_epos, m_polyPickExt); m_npolys = m_mesh->findPath(m_startRef, m_endRef, m_spos, m_epos, m_polys, MAX_POLYS); if (m_npolys) m_nstraightPath = m_mesh->findStraightPath(m_spos, m_epos, m_polys, m_npolys, m_straightPath, MAX_POLYS); m_status = NAV_COMPLETED; } void NavRequest::processPathRequest() { makeBasicPath(); makeSteerPath( m_startPos, m_nstraightPath ); } TVec3 toVec3Open( OpenSteer::Vec3 p ) { return Vec3(p.x,p.y,p.z); } void NavRequest::render( TCamera cam ) { #ifdef DEBUG_PATH TVec3 p1; TVec3 p2; if (m_nstraightPath < 2) return; SetBlend(BLEND_ALPHA); SetColor( Vec4(1,0,0,1) ); // red for( int i=0;i<m_nstraightPath-1;i++) { tdDraw( cam, pathVec3( i ), pathVec3( i+1 ) ); } SetColor( Vec4(0,1,0,1) ); // red for( int i=0;i<m_numPoints-1;i++) { tdDraw( cam, toVec3Open(m_pathPoints[i]) , toVec3Open(m_pathPoints[i+1]) ); } #endif } OpenSteer::Vec3 *toOpenVec3( TVec3 pos ) { OpenSteer::Vec3 *rpos = new OpenSteer::Vec3; rpos->x = pos.X; rpos->y = pos.Y; rpos->z = pos.Z; return rpos; } bool NavRequest::makeSteerPath( TVec3 startPos, int maxPoints ) { if (!complete()) return false; maxPoints = MAX_POLYS; // temp fix // Check if recalc needed if (m_navVersion != navVersion) makeBasicPath(); else { float spos[3]; spos[0] = startPos.X*-1; spos[1] = startPos.Y; spos[2] = startPos.Z; m_nstraightPath = m_mesh->findStraightPath(spos, m_epos, m_polys, m_npolys, m_straightPath, maxPoints); } m_numPoints = __min( m_nstraightPath, maxPoints ); if (m_numPoints <= 2 && startPos == m_endPos) { m_numPoints = 0; return false; } if (m_openPath) { delete m_openPath; m_openPath = NULL; } if (m_pathPoints) { delete m_pathPoints; m_pathPoints = NULL; } m_openPath = new OpenSteer::PolylineSegmentedPathwaySingleRadius; if (m_numPoints > 2) { m_pathPoints = new OpenSteer::Vec3[m_numPoints]; for( int i=0;i < m_numPoints;i++) { m_pathPoints[i] = *toOpenVec3( pathVec3( i ) ); } m_numPoints -=1; } else { m_pathPoints = new OpenSteer::Vec3[2]; m_pathPoints[0] = *toOpenVec3(startPos); m_pathPoints[1] = *toOpenVec3(m_endPos); m_numPoints = 2; } m_openPath->setPathway( m_numPoints, m_pathPoints, 0.01, false ); return true; } Here's the recast vert loader code: - #include "leo.h" #include "recast.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #define _USE_MATH_DEFINES #include <math.h> #include "ledcast.h" #include "ledcast_statmeshSimple.h" #include "rcMeshLoaderSBX.h" using namespace LEO; rcMeshLoaderSBX *currentLoader; bool processMesh( TEntity entity ) { TMesh mesh = (TMesh)entity; int SurfCnt = CountSurfaces( mesh ); TSurface surface; TVec3 vec; int vertIdx; float factor=1; vertIdx = currentLoader->getVertCount(); for( int s = 1; s <= SurfCnt; s++ ) { surface = GetSurface( mesh, s ); if (surface) { for( int t = 0; t < CountTriangles( surface ); t++) { for( int v = 0; v < 3; v++) { vec = GetVertexPosition( surface, TriangleVertex( surface, t, v )); vec=TFormPoint(Vec3(vec.X,vec.Y,vec.Z),entity,NULL); currentLoader->addVertex( (vec.X)*-factor, (vec.Y)*factor, (vec.Z)*factor, currentLoader->vcap ); } currentLoader->addTriangle( vertIdx+1, vertIdx, vertIdx+2, currentLoader->tcap ); vertIdx=vertIdx+3; } } else break; } return true; } int _stdcall recastLoadMesh( TEntity entity, byte* extra ) { TMesh mesh = (TMesh)entity; std::string entityClass, cn, en; int type, type1; // Only bother with collision type 1 cn=GetEntityKey(entity,"classname"); en=GetEntityKey(entity,"name"); entityClass = GetEntityKey(entity, "class",""); type = atoi( GetEntityKey(entity, "collisiontype","1") ); if (type >0) EntityType(entity, type, 1); type1 = GetEntityType(entity); if (currentLoader->m_dynamic) { if (type1 != 2) return true; }else{ if (type1 != 1) return true; } if (entityClass != "Model") return true; if( (cn=="water")|| (cn=="physics_prop")|| (cn=="physics_pivot")|| (cn=="joint_motor")|| (cn=="info_waypoint")|| (cn=="info_playerstart")|| (cn=="player")|| (cn=="light_directional")|| (cn=="light_spot")|| (cn=="light_point")|| (cn=="env_emitter")|| (cn=="env_sound")) { HideEntity(entity); return true; } processMesh( GetChild(entity, 1) ); return true; } int _stdcall recastLoadTerrain( TEntity terrain, byte* extra ) { std::string entityClass; entityClass = GetEntityKey(terrain, "class",""); if (entityClass != "Terrain") return false; float mapSize = 1024; float x; float z; float mapHalfSize = 512; float height; int vertIdx; float minx = 0, minz =0, maxx=0, maxz=0; if (!terrain) return false; vertIdx = currentLoader->getVertCount(); for(x = -mapHalfSize; x<mapHalfSize - 1; x++) { for(z = -mapHalfSize; z<mapHalfSize - 1; z++) { height = TerrainElevation( terrain, x, z); if ((height != 0) || ((x>=minx && x<=maxx) && (z>=minz && z <= maxz))) { minx = __min(minx, x); minz = __min(minz, z); maxx = __max(maxx, x); maxz = __max(maxz, z); //br height = TerrainElevation( terrain, (float)x+1, (float)z); currentLoader->addVertex( (x+1)*-1, height, z, currentLoader->vcap ); //tr height = TerrainElevation( terrain, (float)x+1, (float)z+1); currentLoader->addVertex( (x+1)*-1, height, z+1, currentLoader->vcap ); //bl height = TerrainElevation( terrain, x, z); currentLoader->addVertex( x*-1, height, z, currentLoader->vcap ); currentLoader->addTriangle( vertIdx, vertIdx+1, vertIdx+2, currentLoader->tcap ); vertIdx=vertIdx+3; //Trianlge 2 //tl height = TerrainElevation( terrain, (float)x, (float)z+1); currentLoader->addVertex( x*-1, height, z+1, currentLoader->vcap ); //bl height = TerrainElevation( terrain, (float)x, (float)z); currentLoader->addVertex( x*-1, height, z, currentLoader->vcap ); //tr height = TerrainElevation( terrain, (float)x+1, (float)z+1); currentLoader->addVertex( (x+1)*-1, height, z+1, currentLoader->vcap ); currentLoader->addTriangle( vertIdx, vertIdx+1, vertIdx+2, currentLoader->tcap ); vertIdx=vertIdx+3; } else height = -1; } } return true; } int _stdcall recastLoadMeshNormals( TEntity entity, byte* extra ) { TMesh mesh = (TMesh)entity; std::string entityClass; entityClass = GetEntityKey(entity, "class",""); if (entityClass != "Mesh") return false; TSurface surface; int SurfCnt = CountSurfaces( mesh ); float *normals; normals = (float *)(currentLoader->getNormals()); TVec3 vnorm; for( int s = 1; s <= SurfCnt; s++ ) { surface = GetSurface( mesh, s ); for( int t = 0; t < CountTriangles( surface ); t++) { vnorm = GetVertexNormal( surface, TriangleVertex( surface, t, 1 )); normals[currentLoader->nidx] = vnorm.X; normals[currentLoader->nidx+1] = vnorm.Y; normals[currentLoader->nidx+2] = vnorm.Z; currentLoader->nidx=currentLoader->nidx+3; } } return true; } bool rcMeshLoaderSBX::load(const char* filename) { std::string f = filename; if (f.find("sbx")) scene = LoadScene((str)filename); else if (f.find("gmf")) scene = LoadModel((str)filename); if (!scene) return false; return process(); } bool rcMeshLoaderSBX::process() { currentLoader = this; m_dynamic = false; ForEachEntityDo( (BP)recastLoadMesh,NULL, ENTITY_MESH|ENTITY_MODEL ); ForEachEntityDo( (BP)recastLoadTerrain,NULL, ENTITY_TERRAIN ); setNormals( new float[getTriCount()*3] ); nidx = 0; calcNormals(); return true; } bool rcMeshLoaderSBX::processDynamic( TVec6 box ) { currentLoader = this; m_dynamic = true; ForEachEntityInAABBDo( box, (BP)recastLoadMesh, (byte*)this, ENTITY_MESH|ENTITY_MODEL ); return true; } rcMeshLoaderSBX::~rcMeshLoaderSBX() { if (scene) FreeEntity(scene); }
  20. Just to add to this I've recently done a test with 20 AI controlled NPCs. Each one uses a character controller to move about. The update world statement takes 20 - 30 seconds Hoping this can be improved on as I would like on a level >100 AI characters. My machine spec is: - GW-265 GAINWARD GEFORCE GTX260 "GOLDEN SAMPLE" *216 STREAM PROCESSOR* 55NM SLI 896MB DDR3 TV-OUT/DUAL DVI PCI-EXPRESS 250 GB SATA-II HDD UDMA 300 7200 8MB Motherboard Integrated 5.1 Sound Motherboard Integrated Ethernet Lan (Broadband Ready) 450W PSU Motherboard Integrated Graphics Corsair XMS2 4GB PC2-8500 1066 MHZ (2 x 2 GB) - Lifetime Warranty (DDR2) Intel Core 2 Duo E7500 (2 x 2.9 GHZ) 1066FSB - 3 MB ASUS P5Q 1600FSB (Intel P45)
  21. Have you got a link to the unity location system description? I've seen your video - very nice. I did have one question though, why is the physics mesh so out of place relative to the visible mesh? I've come to the conclusion that the physics mesh is next to useless on animation models, it would be better to create some sort of bounding physics volume.
  22. This is the bit of code that works out teams: - ... 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 ); ... 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; }
  23. The view cone is done by the viewangle bit of code: - // 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)) { The bit about the box ie ForEachEntityAABB is getting a list of nearby enemies. I base it on the idea of NPC/players being in teams for example red team, blue etc. I store the team in a entity key. Anyone in a different team is considered to be an enemy. Currently my code has only seen/not seen logic and I exits at the first line pick success. You could do all of them and work out a % visible but this would effect the FPS serverly.
  24. Tell me the bit your stuck on and I'll help... Here's an overview: - for each enemy in view range check if enemy in FOV for each bone of enemy do line pick at pos of bone if can see then exit end end end I use the bones as this will give me the different important parts of a body. This way if only a head or a hand etc is visible a linepick will succeed.
  25. I've managed to make GMF files from C++ with the GMF library, so this would possibly be a good way to do it. Doubt you'd be able to skip the file part though.
×
×
  • Create New...