int OgreDetourTileCache::rasterizeTileLayers(InputGeom* geom, const int tx, const int ty, const rcConfig& cfg, TileCacheData* tiles, const int maxTiles) { if (!geom || geom->isEmpty()) { m_recast->m_pLog->logMessage("ERROR: buildTile: Input mesh is not specified."); return 0; } if (!geom->getChunkyMesh()) { m_recast->m_pLog->logMessage("ERROR: buildTile: Input mesh has no chunkyTriMesh built."); return 0; } //TODO make these member variables? FastLZCompressor comp; RasterizationContext rc; const float* verts = geom->getVerts(); const int nverts = geom->getVertCount(); // The chunky tri mesh in the inputgeom is a simple spatial subdivision structure that allows to // process the vertices in the geometry relevant to this part of the tile. // The chunky tri mesh is a grid of axis aligned boxes that store indices to the vertices in verts // that are positioned in that box. const rcChunkyTriMesh* chunkyMesh = geom->getChunkyMesh(); // Tile bounds. const float tcs = m_tileSize * m_cellSize; rcConfig tcfg; memcpy(&tcfg, &m_cfg, sizeof(tcfg)); tcfg.bmin[0] = m_cfg.bmin[0] + tx*tcs; tcfg.bmin[1] = m_cfg.bmin[1]; tcfg.bmin[2] = m_cfg.bmin[2] + ty*tcs; tcfg.bmax[0] = m_cfg.bmin[0] + (tx+1)*tcs; tcfg.bmax[1] = m_cfg.bmax[1]; tcfg.bmax[2] = m_cfg.bmin[2] + (ty+1)*tcs; tcfg.bmin[0] -= tcfg.borderSize*tcfg.cs; tcfg.bmin[2] -= tcfg.borderSize*tcfg.cs; tcfg.bmax[0] += tcfg.borderSize*tcfg.cs; tcfg.bmax[2] += tcfg.borderSize*tcfg.cs; // This is part of the regular recast navmesh generation pipeline as in OgreRecast::NavMeshBuild() // but only up till step 4 and slightly modified. // Allocate voxel heightfield where we rasterize our input data to. rc.solid = rcAllocHeightfield(); if (!rc.solid) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'solid'."); return 0; } if (!rcCreateHeightfield(m_ctx, *rc.solid, tcfg.width, tcfg.height, tcfg.bmin, tcfg.bmax, tcfg.cs, tcfg.ch)) { m_recast->m_pLog->logMessage("ERROR: 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 // an array which can hold the max number of triangles you need to process. rc.triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; if (!rc.triareas) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'm_triareas' ("+Ogre::StringConverter::toString(chunkyMesh->maxTrisPerChunk)+")."); return 0; } float tbmin[2], tbmax[2]; tbmin[0] = tcfg.bmin[0]; tbmin[1] = tcfg.bmin[2]; tbmax[0] = tcfg.bmax[0]; tbmax[1] = tcfg.bmax[2]; int cid[512];// TODO: Make grow when returning too many items. const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); if (!ncid) { return 0; // empty } 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; memset(rc.triareas, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(m_ctx, tcfg.walkableSlopeAngle, verts, nverts, tris, ntris, rc.triareas); rcRasterizeTriangles(m_ctx, verts, nverts, tris, rc.triareas, ntris, *rc.solid, tcfg.walkableClimb); } // 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(m_ctx, tcfg.walkableClimb, *rc.solid); rcFilterLedgeSpans(m_ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid); rcFilterWalkableLowHeightSpans(m_ctx, tcfg.walkableHeight, *rc.solid); rc.chf = rcAllocCompactHeightfield(); if (!rc.chf) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'chf'."); return 0; } if (!rcBuildCompactHeightfield(m_ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid, *rc.chf)) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Could not build compact data."); return 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(m_ctx, tcfg.walkableRadius, *rc.chf)) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Could not erode."); return 0; } // Mark areas of dynamically added convex polygons const ConvexVolume* const* vols = geom->getConvexVolumes(); for (int i = 0; i < geom->getConvexVolumeCount(); ++i) { rcMarkConvexPolyArea(m_ctx, vols[i]->verts, vols[i]->nverts, vols[i]->hmin, vols[i]->hmax, (unsigned char)vols[i]->area, *rc.chf); } // Up till this part was more or less the same as OgreRecast::NavMeshBuild() // The following part is specific for creating a 2D intermediary navmesh tile. rc.lset = rcAllocHeightfieldLayerSet(); if (!rc.lset) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'lset'."); return 0; } if (!rcBuildHeightfieldLayers(m_ctx, *rc.chf, tcfg.borderSize, tcfg.walkableHeight, *rc.lset)) { m_recast->m_pLog->logMessage("ERROR: buildNavigation: Could not build heightfield layers."); return 0; } rc.ntiles = 0; for (int i = 0; i < rcMin(rc.lset->nlayers, MAX_LAYERS); ++i) { TileCacheData* tile = &rc.tiles[rc.ntiles++]; const rcHeightfieldLayer* layer = &rc.lset->layers[i]; // Store header dtTileCacheLayerHeader header; header.magic = DT_TILECACHE_MAGIC; header.version = DT_TILECACHE_VERSION; // Tile layer location in the navmesh. header.tx = tx; header.ty = ty; header.tlayer = i; dtVcopy(header.bmin, layer->bmin); dtVcopy(header.bmax, layer->bmax); // Tile info. header.width = (unsigned char)layer->width; header.height = (unsigned char)layer->height; header.minx = (unsigned char)layer->minx; header.maxx = (unsigned char)layer->maxx; header.miny = (unsigned char)layer->miny; header.maxy = (unsigned char)layer->maxy; header.hmin = (unsigned short)layer->hmin; header.hmax = (unsigned short)layer->hmax; dtStatus status = dtBuildTileCacheLayer(&comp, &header, layer->heights, layer->areas, layer->cons, &tile->data, &tile->dataSize); if (dtStatusFailed(status)) { return 0; } } // Transfer ownsership of tile data from build context to the caller. int n = 0; for (int i = 0; i < rcMin(rc.ntiles, maxTiles); ++i) { tiles[n++] = rc.tiles[i]; rc.tiles[i].data = 0; rc.tiles[i].dataSize = 0; } return n; }
static int rasterizeTileLayers(BuildContext* ctx, CMapMesh* geom, const int tx, const int ty, const rcConfig& cfg, TileCacheData* tiles, const int maxTiles) { if (!geom || !geom->GetChunkyMesh()) { ctx->log(RC_LOG_ERROR, "buildTile: Input mesh is not specified."); return 0; } FastLZCompressor comp; RasterizationContext rc; const float* verts = geom->GetVerts(); const int nverts = geom->GetNumVerts(); const rcChunkyTriMesh* chunkyMesh = geom->GetChunkyMesh(); // Tile bounds. const float tcs = cfg.tileSize * cfg.cs; rcConfig tcfg; memcpy(&tcfg, &cfg, sizeof(tcfg)); tcfg.bmin[0] = cfg.bmin[0] + tx*tcs; tcfg.bmin[1] = cfg.bmin[1]; tcfg.bmin[2] = cfg.bmin[2] + ty*tcs; tcfg.bmax[0] = cfg.bmin[0] + (tx+1)*tcs; tcfg.bmax[1] = cfg.bmax[1]; tcfg.bmax[2] = cfg.bmin[2] + (ty+1)*tcs; tcfg.bmin[0] -= tcfg.borderSize*tcfg.cs; tcfg.bmin[2] -= tcfg.borderSize*tcfg.cs; tcfg.bmax[0] += tcfg.borderSize*tcfg.cs; tcfg.bmax[2] += tcfg.borderSize*tcfg.cs; // Allocate voxel heightfield where we rasterize our input data to. rc.solid = rcAllocHeightfield(); if (!rc.solid) { ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); return 0; } if (!rcCreateHeightfield(ctx, *rc.solid, tcfg.width, tcfg.height, tcfg.bmin, tcfg.bmax, tcfg.cs, tcfg.ch)) { ctx->log(RC_LOG_ERROR, "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. rc.triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; if (!rc.triareas) { ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk); return 0; } float tbmin[2], tbmax[2]; tbmin[0] = tcfg.bmin[0]; tbmin[1] = tcfg.bmin[2]; tbmax[0] = tcfg.bmax[0]; tbmax[1] = tcfg.bmax[2]; int cid[512];// TODO: Make grow when returning too many items. const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); if (!ncid) { return 0; // empty } 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; memset(rc.triareas, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(ctx, tcfg.walkableSlopeAngle, verts, nverts, tris, ntris, rc.triareas); rcRasterizeTriangles(ctx, verts, nverts, tris, rc.triareas, ntris, *rc.solid, tcfg.walkableClimb); } // 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, tcfg.walkableClimb, *rc.solid); rcFilterLedgeSpans(ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid); rcFilterWalkableLowHeightSpans(ctx, tcfg.walkableHeight, *rc.solid); rc.chf = rcAllocCompactHeightfield(); if (!rc.chf) { ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); return 0; } if (!rcBuildCompactHeightfield(ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid, *rc.chf)) { ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); return 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(ctx, tcfg.walkableRadius, *rc.chf)) { ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); return 0; } #if 0 // (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, *rc.chf); } #endif // 0 rc.lset = rcAllocHeightfieldLayerSet(); if (!rc.lset) { ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'lset'."); return 0; } if (!rcBuildHeightfieldLayers(ctx, *rc.chf, tcfg.borderSize, tcfg.walkableHeight, *rc.lset)) { ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build heighfield layers."); return 0; } rc.ntiles = 0; for (int i = 0; i < rcMin(rc.lset->nlayers, MAX_LAYERS); ++i) { TileCacheData* tile = &rc.tiles[rc.ntiles++]; const rcHeightfieldLayer* layer = &rc.lset->layers[i]; // Store header dtTileCacheLayerHeader header; header.magic = DT_TILECACHE_MAGIC; header.version = DT_TILECACHE_VERSION; // Tile layer location in the navmesh. header.tx = tx; header.ty = ty; header.tlayer = i; dtVcopy(header.bmin, layer->bmin); dtVcopy(header.bmax, layer->bmax); // Tile info. header.width = (unsigned char)layer->width; header.height = (unsigned char)layer->height; header.minx = (unsigned char)layer->minx; header.maxx = (unsigned char)layer->maxx; header.miny = (unsigned char)layer->miny; header.maxy = (unsigned char)layer->maxy; header.hmin = (unsigned short)layer->hmin; header.hmax = (unsigned short)layer->hmax; dtStatus status = dtBuildTileCacheLayer(&comp, &header, layer->heights, layer->areas, layer->cons, &tile->data, &tile->dataSize); if (dtStatusFailed(status)) { return 0; } } // Transfer ownsership of tile data from build context to the caller. int n = 0; for (int i = 0; i < rcMin(rc.ntiles, maxTiles); ++i) { tiles[n++] = rc.tiles[i]; rc.tiles[i].data = 0; rc.tiles[i].dataSize = 0; } return n; }