Code:
struct ObjData
{
Vertices_t vertices;
Indices32_t indices;
Normals_t normals;
AdtCoords_t *coords;
void clear(){
vertices.clear();
indices.clear();
normals.clear();
}
};
// objData = our tile and the tiles bordering it
// obj = our tile
- (void)generateMeshesWithObj:(ObjData*)objData toDirectory:(NSString*)directory andX:(int)tx andY:(int)ty andTile:(ObjData*)obj{
// create our mesh loader basd on all this data (3x3 based on the tile we want the mesh for)
rcMeshLoaderObj *loader = CreateMeshLoader(objData);
if ( !loader ){
NSLog(@"Unable to create loader for (%d, %d)", tx, ty);
return;
}
InputGeom *geom = new InputGeom();
if ( !geom->loadMesh(0, loader) ){
NSLog(@"Unable to load ObjData");
return;
}
if ( !geom || !geom->getMesh() || !geom->getChunkyMesh() ){
NSLog(@"buildNavigation: Input mesh is not specified.");
return;
}
const float *bmin = geom->getMeshBoundsMin();
const float *bmax = geom->getMeshBoundsMax();
const float* verts = geom->getMesh()->getVerts();
const int nverts = geom->getMesh()->getVertCount();
const rcChunkyTriMesh* chunkyMesh = geom->getChunkyMesh();
rcConfig m_cfg;
// 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)floorf(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.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size
m_cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size
m_cfg.maxVertsPerPoly = (int)m_vertsPerPoly;
m_cfg.tileSize = (int)m_tileSize;
m_cfg.borderSize = m_cfg.walkableRadius + 3; // 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;
rcVcopy(m_cfg.bmin, bmin);
rcVcopy(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;
NSLog(@"Overall...");
NSLog(@" min: {%0.2f, %0.2f, %0.2f}", bmin[0], bmin[1], bmin[2]);
NSLog(@" max: {%0.2f, %0.2f, %0.2f}", bmax[0], bmax[1], bmax[2]);
rcContext *ctx = NULL;
NSDate *start = [NSDate date];
// clean up from previous run
if ( ctx ){
delete ctx;
}
// we only care about the coords for this tile, so lets set our bounds correctly!
NSLog(@"(%d, %d) Generating mesh...", tx, ty);
// ** Get our bounds for the tile we care about
rcMeshLoaderObj *loader2 = CreateMeshLoader(obj);
InputGeom *geomTile = new InputGeom();
if ( !geomTile->loadMesh(0, loader2) ){
NSLog(@"Unable to load ObjData2(%d, %d)", tx, ty);
return;
}
if ( !geomTile || !geomTile->getMesh() || !geomTile->getChunkyMesh() ){
NSLog(@"buildNavigation: Input mesh is not specified.2(%d, %d)", tx, ty);
return;
}
const float *bminTile = geomTile->getMeshBoundsMin();
const float *bmaxTile = geomTile->getMeshBoundsMax();
m_cfg.bmin[0] = bminTile[0];
m_cfg.bmin[2] = bminTile[2];
m_cfg.bmax[0] = bmaxTile[0];
m_cfg.bmax[2] = bmaxTile[2];
// we want to go over the tile's edge a bit!
/*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;*/
// ** End - Get our bounds
NSLog(@" min: {%0.2f, %0.2f, %0.2f}", bminTile[0], bminTile[1], bminTile[2]);
NSLog(@" max: {%0.2f, %0.2f, %0.2f}", bmaxTile[0], bmaxTile[1], bmaxTile[2]);
// set up our context
ctx = new rcContext();
ctx->resetTimers();
ctx->startTimer(RC_TIMER_TOTAL);
// Allocate voxel heightfield where we rasterize our input data to.
rcHeightfield *m_solid = rcAllocHeightfield();
if ( !m_solid ){
NSLog(@"buildNavigation: Out of memory 'solid'.");
return;
}
if ( !rcCreateHeightfield(ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch) ){
NSLog(@"buildNavigation: Could not create solid heightfield.");
return;
}
// 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.
unsigned char *m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk];
if ( !m_triareas ){
NSLog(@"buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk);
return;
}
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[10000];// TODO: Make grow when returning too many items.
const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 10000);
if ( !ncid ){
NSLog(@"ncid error");
return;
}
int m_tileTriCount = 0;
for ( int i = 0; i < ncid; ++i ){
const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
const int* tris = &chunkyMesh->tris[node.i*3];
const int ntris = node.n;
m_tileTriCount += ntris;
memset(m_triareas, 0, ntris*sizeof(unsigned char));
rcMarkWalkableTriangles( ctx, m_cfg.walkableSlopeAngle, verts, nverts, tris, ntris, m_triareas );
rcRasterizeTriangles( ctx, verts, nverts, tris, m_triareas, ntris, *m_solid, m_cfg.walkableClimb );
}
if ( !m_keepInterResults ){
delete [] m_triareas;
m_triareas = 0;
}
// Once all geometry 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.
rcFilterLowHangingWalkableObstacles(ctx, m_cfg.walkableClimb, *m_solid);
rcFilterLedgeSpans(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid);
rcFilterWalkableLowHeightSpans(ctx, 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.
rcCompactHeightfield *m_chf = rcAllocCompactHeightfield();
if ( !m_chf ){
NSLog(@"buildNavigation: Out of memory 'chf'.");
return;
}
if ( !rcBuildCompactHeightfield(ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf) ){
NSLog(@"buildNavigation: Could not build compact data.");
return;
}
if ( !m_keepInterResults ){
rcFreeHeightField(m_solid);
m_solid = 0;
}
// Erode the walkable area by agent radius.
if ( !rcErodeWalkableArea(ctx, m_cfg.walkableRadius, *m_chf) ){
NSLog(@"buildNavigation: Could not erode.");
return;
}
// (Optional) Mark areas.
const ConvexVolume* vols = geom->getConvexVolumes();
for (int i = 0; i < geom->getConvexVolumeCount(); ++i)
rcMarkConvexPolyArea(ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf);
if ( m_monotonePartitioning ){
// Partition the walkable surface into simple regions without holes.
if ( !rcBuildRegionsMonotone(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea) ){
NSLog(@"buildNavigation: Could not build regions.");
return;
}
}
else{
// Prepare for region partitioning, by calculating distance field along the walkable surface.
if ( !rcBuildDistanceField(ctx, *m_chf) ){
NSLog(@"buildNavigation: Could not build distance field.");
return;
}
// Partition the walkable surface into simple regions without holes.
if ( !rcBuildRegions(ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea) ){
NSLog(@"buildNavigation: Could not build regions.");
return;
}
}
// Create contours.
rcContourSet *m_cset = rcAllocContourSet();
if ( !m_cset ){
NSLog(@"buildNavigation: Out of memory 'cset'.");
return;
}
if ( !rcBuildContours(ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset) ){
NSLog(@"buildNavigation: Could not create contours.");
return;
}
if ( m_cset->nconts == 0 ){
NSLog(@"No contours ");
return;
}
// Build polygon navmesh from the contours.
rcPolyMesh *m_pmesh = rcAllocPolyMesh();
if ( !m_pmesh ){
NSLog(@"buildNavigation: Out of memory 'pmesh'.");
return;
}
if ( !rcBuildPolyMesh(ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh) ){
NSLog(@"buildNavigation: Could not triangulate contours.");
return;
}
// Build detail mesh.
rcPolyMeshDetail *m_dmesh = rcAllocPolyMeshDetail();
if ( !m_dmesh ){
NSLog(@"buildNavigation: Out of memory 'dmesh'.");
return;
}
if ( !rcBuildPolyMeshDetail(ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh) ){
NSLog(@"buildNavigation: Could build polymesh detail.");
return;
}
if ( !m_keepInterResults ){
rcFreeCompactHeightfield(m_chf);
m_chf = 0;
rcFreeContourSet(m_cset);
m_cset = 0;
}
unsigned char *navData = NULL;
int navDataSize;
if ( m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON ){
if ( m_pmesh->nverts >= 0xffff ){
// The vertex indices are ushorts, and cannot point to more than 0xffff vertices.
NSLog(@"Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff);
return;
}
// Update poly flags from areas.
for ( int i = 0; i < m_pmesh->npolys; ++i ){
if (m_pmesh->areas[i] == RC_WALKABLE_AREA)
m_pmesh->areas[i] = SAMPLE_POLYAREA_GROUND;
if ( m_pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || m_pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || m_pmesh->areas[i] == SAMPLE_POLYAREA_ROAD ){
m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK;
}
else if ( m_pmesh->areas[i] == SAMPLE_POLYAREA_WATER ){
m_pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM;
}
else if ( m_pmesh->areas[i] == SAMPLE_POLYAREA_DOOR ){
m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
}
}
dtNavMeshCreateParams params;
memset(¶ms, 0, sizeof(params));
params.verts = m_pmesh->verts;
params.vertCount = m_pmesh->nverts;
params.polys = m_pmesh->polys;
params.polyAreas = m_pmesh->areas;
params.polyFlags = m_pmesh->flags;
params.polyCount = m_pmesh->npolys;
params.nvp = m_pmesh->nvp;
params.detailMeshes = m_dmesh->meshes;
params.detailVerts = m_dmesh->verts;
params.detailVertsCount = m_dmesh->nverts;
params.detailTris = m_dmesh->tris;
params.detailTriCount = m_dmesh->ntris;
params.offMeshConVerts = geom->getOffMeshConnectionVerts();
params.offMeshConRad = geom->getOffMeshConnectionRads();
params.offMeshConDir = geom->getOffMeshConnectionDirs();
params.offMeshConAreas = geom->getOffMeshConnectionAreas();
params.offMeshConFlags = geom->getOffMeshConnectionFlags();
params.offMeshConUserID = geom->getOffMeshConnectionId();
params.offMeshConCount = geom->getOffMeshConnectionCount();
params.walkableHeight = m_agentHeight;
params.walkableRadius = m_agentRadius;
params.walkableClimb = m_agentMaxClimb;
params.tileX = tx;
params.tileY = ty;
params.tileLayer = 0;
rcVcopy(params.bmin, m_pmesh->bmin);
rcVcopy(params.bmax, m_pmesh->bmax);
params.cs = m_cfg.cs;
params.ch = m_cfg.ch;
params.buildBvTree = true;
NSLog(@" min: {%0.2f, %0.2f, %0.2f}", m_pmesh->bmin[0], m_pmesh->bmin[1], m_pmesh->bmin[2]);
NSLog(@" max: {%0.2f, %0.2f, %0.2f}", m_pmesh->bmax[0], m_pmesh->bmax[1], m_pmesh->bmax[2]);
if ( !dtCreateNavMeshData(¶ms, &navData, &navDataSize) ){
NSLog(@"Could not build Detour navmesh.");
return;
}
}
ctx->stopTimer(RC_TIMER_TOTAL);
// save!
if ( navDataSize > 0 ){
NSString *path = [NSString stringWithFormat:@"%@%d_%d.mesh", directory, tx, ty];
FILE* fp = fopen([path UTF8String], "wb");
if (!fp)
return;
// Store header
NavMeshSetHeader header;
header.magic = NAVMESHSET_MAGIC;
header.version = NAVMESHSET_VERSION;
header.numTiles = 1;
fwrite(&header, sizeof(NavMeshSetHeader), 1, fp);
// Store tile
NavMeshTileHeader tileHeader;
tileHeader.dataSize = navDataSize;
fwrite(&tileHeader, sizeof(tileHeader), 1, fp);
fwrite(navData, navDataSize, 1, fp);
fclose(fp);
}
NSLog(@"Completed in %0.2f seconds", [start timeIntervalSinceNow] * -1.0f);
if ( geom ){
delete geom;
}
}