// Calculates convex hull on xz-plane of points on 'pts' ConvexVolume::ConvexVolume(InputGeom* geom, float offset) { // TODO protect against too many vectors! int hullVertIndices[MAX_CONVEXVOL_PTS]; float* pts = geom->getVerts(); int npts = geom->getVertCount(); // Find lower-leftmost point. int hull = 0; for (int i = 1; i < npts; ++i) if (ConvexVolume::cmppt(&pts[i*3], &pts[hull*3])) hull = i; // Gift wrap hull. int endpt = 0; int i = 0; do { hullVertIndices[i++] = hull; endpt = 0; for (int j = 1; j < npts; ++j) if (hull == endpt || ConvexVolume::left(&pts[hull*3], &pts[endpt*3], &pts[j*3])) endpt = j; hull = endpt; } while (endpt != hullVertIndices[0] && i < MAX_CONVEXVOL_PTS/2); // TODO: number of hull points is limited, but in a naive way. In large meshes the best candidate points for the hull might not be selected // Leave the other half of the points for expanding the hull nverts = i; // Copy geometry vertices to convex hull for (int i = 0; i < nverts; i++) rcVcopy(&verts[i*3], &pts[hullVertIndices[i]*3]); area = SAMPLE_POLYAREA_DOOR; // You can choose whatever flag you assing to the poly area // Find min and max height of convex hull hmin = geom->getMeshBoundsMin()[1]; hmax = geom->getMeshBoundsMax()[1]; // 3D mesh min and max bounds rcVcopy(bmin, geom->getMeshBoundsMin()); rcVcopy(bmax, geom->getMeshBoundsMax()); //TODO offsetting is still broken for a lot of shapes! Fix this! // Offset convex hull if needed if(offset > 0.01f) { float offsetVerts[MAX_CONVEXVOL_PTS * 3]; // An offset hull is allowed twice the number of vertices int nOffsetVerts = rcOffsetPoly(verts, nverts, offset, offsetVerts, MAX_CONVEXVOL_PTS); if (nOffsetVerts <= 0) return; for(int i = 0; i < nOffsetVerts; i++) rcVcopy(&verts[i*3], &offsetVerts[i*3]); nverts = nOffsetVerts; // Modify the bounds with offset (except height) bmin[0] = bmin[0]-offset; bmin[2] = bmin[2]-offset; bmax[0] = bmax[0]+offset; bmax[2] = bmax[2]+offset; } }
bool OgreRecast::NavMeshBuild(InputGeom* input) { // TODO: clean up unused variables m_pLog->logMessage("NavMeshBuild Start"); // // Step 1. Initialize build config. // // Reset build times gathering. m_ctx->resetTimers(); // Start the build process. m_ctx->startTimer(RC_TIMER_TOTAL); // // Step 2. Rasterize input polygon soup. // InputGeom *inputGeom = input; rcVcopy(m_cfg.bmin, inputGeom->getMeshBoundsMin()); rcVcopy(m_cfg.bmax, inputGeom->getMeshBoundsMax()); rcCalcGridSize(m_cfg.bmin, m_cfg.bmax, m_cfg.cs, &m_cfg.width, &m_cfg.height); int nverts = inputGeom->getVertCount(); int ntris = inputGeom->getTriCount(); Ogre::Vector3 min; FloatAToOgreVect3(inputGeom->getMeshBoundsMin(), min); Ogre::Vector3 max; FloatAToOgreVect3(inputGeom->getMeshBoundsMax(), max); //Ogre::LogManager::getSingletonPtr()->logMessage("Bounds: "+Ogre::StringConverter::toString(min) + " "+ Ogre::StringConverter::toString(max)); m_pLog->logMessage("Building navigation:"); m_pLog->logMessage(" - " + Ogre::StringConverter::toString(m_cfg.width) + " x " + Ogre::StringConverter::toString(m_cfg.height) + " cells"); m_pLog->logMessage(" - " + Ogre::StringConverter::toString(nverts/1000.0f) + " K verts, " + Ogre::StringConverter::toString(ntris/1000.0f) + " K tris"); // Allocate voxel heightfield where we rasterize our input data to. m_solid = rcAllocHeightfield(); if (!m_solid) { m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'solid'."); return false; } if (!rcCreateHeightfield(m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { m_pLog->logMessage("ERROR: buildNavigation: Could not create solid heightfield. Possibly it requires too much memory, try setting a higher cellSize and cellHeight value."); return false; } // Allocate array that can hold triangle area types. // If you have multiple meshes you need to process, allocate // an array which can hold the max number of triangles you need to process. m_triareas = new unsigned char[ntris]; if (!m_triareas) { m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'm_triareas' ("+Ogre::StringConverter::toString(ntris)+")."); return false; } // Find triangles which are walkable based on their slope and rasterize them. // If your input data is multiple meshes, you can transform them here, calculate // the are type for each of the meshes and rasterize them. memset(m_triareas, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle, inputGeom->getVerts(), inputGeom->getVertCount(), inputGeom->getTris(), inputGeom->getTriCount(), m_triareas); rcRasterizeTriangles(m_ctx, inputGeom->getVerts(), inputGeom->getVertCount(), inputGeom->getTris(), m_triareas, inputGeom->getTriCount(), *m_solid, m_cfg.walkableClimb); if (!m_keepInterResults) { delete [] m_triareas; m_triareas = 0; } // // Step 3. Filter walkables surfaces. // // 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. rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, *m_solid); rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, *m_solid); // // Step 4. Partition walkable surface to simple regions. // // 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 = rcAllocCompactHeightfield(); if (!m_chf) { m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'chf'."); return false; } if (!rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) { m_pLog->logMessage("ERROR: buildNavigation: Could not build compact data."); return false; } if (!m_keepInterResults) { rcFreeHeightField(m_solid); m_solid = 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf)) { m_pLog->logMessage("ERROR: buildNavigation: Could not erode walkable areas."); return false; } // TODO implement // (Optional) Mark areas. //const ConvexVolume* vols = m_geom->getConvexVolumes(); //for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) // rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(m_ctx, *m_chf)) { m_pLog->logMessage("ERROR: buildNavigation: Could not build distance field."); return false; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_pLog->logMessage("ERROR: buildNavigation: Could not build regions."); return false; } // // Step 5. Trace and simplify region contours. // // Create contours. m_cset = rcAllocContourSet(); if (!m_cset) { m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'cset'."); return false; } if (!rcBuildContours(m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { m_pLog->logMessage("ERROR: buildNavigation: Could not create contours."); return false; } if (m_cset->nconts == 0) { // In case of errors see: http://groups.google.com/group/recastnavigation/browse_thread/thread/a6fbd509859a12c8 // You should probably tweak the parameters m_pLog->logMessage("ERROR: No contours created (Recast)!"); } // // Step 6. Build polygons mesh from contours. // // Build polygon navmesh from the contours. m_pmesh = rcAllocPolyMesh(); if (!m_pmesh) { m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'pmesh'."); return false; } if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { // Try modifying the parameters. I experienced this error when setting agentMaxClimb too high. m_pLog->logMessage("ERROR: buildNavigation: Could not triangulate contours."); return false; } // // Step 7. Create detail mesh which allows to access approximate height on each polygon. // m_dmesh = rcAllocPolyMeshDetail(); if (!m_dmesh) { m_pLog->logMessage("ERROR: buildNavigation: Out of memory 'pmdtl'."); return false; } if (!rcBuildPolyMeshDetail(m_ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) { m_pLog->logMessage("ERROR: buildNavigation: Could not build detail mesh."); return false; } if (!m_keepInterResults) { rcFreeCompactHeightfield(m_chf); m_chf = 0; rcFreeContourSet(m_cset); m_cset = 0; } // At this point the navigation mesh data is ready, you can access it from m_pmesh. // See duDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data. // // (Optional) Step 8. Create Detour data from Recast poly mesh. // // The GUI may allow more max points per polygon than Detour can handle. // Only build the detour navmesh if we do not exceed the limit. if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { m_pLog->logMessage("Detour 1000"); unsigned char* navData = 0; int navDataSize = 0; // 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; m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; } } // Set navmesh params 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; // no off mesh connections yet m_offMeshConCount=0 ; params.offMeshConVerts = m_offMeshConVerts ; params.offMeshConRad = m_offMeshConRads ; params.offMeshConDir = m_offMeshConDirs ; params.offMeshConAreas = m_offMeshConAreas ; params.offMeshConFlags = m_offMeshConFlags ; params.offMeshConUserID = m_offMeshConId ; params.offMeshConCount = m_offMeshConCount ; params.walkableHeight = m_agentHeight; params.walkableRadius = m_agentRadius; params.walkableClimb = m_agentMaxClimb; rcVcopy(params.bmin, m_pmesh->bmin); rcVcopy(params.bmax, m_pmesh->bmax); params.cs = m_cfg.cs; params.ch = m_cfg.ch; m_pLog->logMessage("Detour 2000"); if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { m_pLog->logMessage("ERROR: Could not build Detour navmesh."); return false; } m_pLog->logMessage("Detour 3000"); m_navMesh = dtAllocNavMesh(); if (!m_navMesh) { dtFree(navData); m_pLog->logMessage("ERROR: Could not create Detour navmesh"); return false; } m_pLog->logMessage("Detour 4000"); dtStatus status; status = m_navMesh->init(navData, navDataSize, DT_TILE_FREE_DATA); if (dtStatusFailed(status)) { dtFree(navData); m_pLog->logMessage("ERROR: Could not init Detour navmesh"); return false; } m_pLog->logMessage("Detour 5000"); m_navQuery = dtAllocNavMeshQuery(); status = m_navQuery->init(m_navMesh, 2048); m_pLog->logMessage("Detour 5500"); if (dtStatusFailed(status)) { m_pLog->logMessage("ERROR: Could not init Detour navmesh query"); return false; } m_pLog->logMessage("Detour 6000"); } m_ctx->stopTimer(RC_TIMER_TOTAL); /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cleanup stuff we don't need // delete [] rc_verts ; // delete [] rc_tris ; // delete [] rc_trinorms ; //CreateRecastPolyMesh(*m_pmesh) ; // Debug render it m_pLog->logMessage("NavMeshBuild End"); return true; }
/// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_LAYERS); const int w = chf.width; const int h = chf.height; rcScopedDelete<unsigned char> srcReg = (unsigned char*)rcAlloc(sizeof(unsigned char)*chf.spanCount, RC_ALLOC_TEMP); if (!srcReg) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'srcReg' (%d).", chf.spanCount); return false; } memset(srcReg,0xff,sizeof(unsigned char)*chf.spanCount); const int nsweeps = chf.width; rcScopedDelete<rcLayerSweepSpan> sweeps = (rcLayerSweepSpan*)rcAlloc(sizeof(rcLayerSweepSpan)*nsweeps, RC_ALLOC_TEMP); if (!sweeps) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'sweeps' (%d).", nsweeps); return false; } // Partition walkable area into monotone regions. int prevCount[256]; unsigned char regId = 0; for (int y = borderSize; y < h-borderSize; ++y) { memset(prevCount,0,sizeof(int)*regId); unsigned char sweepId = 0; for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; if (chf.areas[i] == RC_NULL_AREA) continue; unsigned char sid = 0xff; // -x if (rcGetCon(s, 0) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(0); const int ay = y + rcGetDirOffsetY(0); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 0); if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff) sid = srcReg[ai]; } if (sid == 0xff) { sid = sweepId++; sweeps[sid].nei = 0xff; sweeps[sid].ns = 0; } // -y if (rcGetCon(s,3) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(3); const int ay = y + rcGetDirOffsetY(3); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, 3); const unsigned char nr = srcReg[ai]; if (nr != 0xff) { // Set neighbour when first valid neighbour is encoutered. if (sweeps[sid].ns == 0) sweeps[sid].nei = nr; if (sweeps[sid].nei == nr) { // Update existing neighbour sweeps[sid].ns++; prevCount[nr]++; } else { // This is hit if there is nore than one neighbour. // Invalidate the neighbour. sweeps[sid].nei = 0xff; } } } srcReg[i] = sid; } } // Create unique ID. for (int i = 0; i < sweepId; ++i) { // If the neighbour is set and there is only one continuous connection to it, // the sweep will be merged with the previous one, else new region is created. if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == (int)sweeps[i].ns) { sweeps[i].id = sweeps[i].nei; } else { if (regId == 255) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Region ID overflow."); return false; } sweeps[i].id = regId++; } } // Remap local sweep ids to region ids. for (int x = borderSize; x < w-borderSize; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { if (srcReg[i] != 0xff) srcReg[i] = sweeps[srcReg[i]].id; } } } // Allocate and init layer regions. const int nregs = (int)regId; rcScopedDelete<rcLayerRegion> regs = (rcLayerRegion*)rcAlloc(sizeof(rcLayerRegion)*nregs, RC_ALLOC_TEMP); if (!regs) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regs' (%d).", nregs); return false; } memset(regs, 0, sizeof(rcLayerRegion)*nregs); for (int i = 0; i < nregs; ++i) { regs[i].layerId = 0xff; regs[i].ymin = 0xffff; regs[i].ymax = 0; } // Find region neighbours and overlapping regions. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; unsigned char lregs[RC_MAX_LAYERS]; int nlregs = 0; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned char ri = srcReg[i]; if (ri == 0xff) continue; regs[ri].ymin = rcMin(regs[ri].ymin, s.y); regs[ri].ymax = rcMax(regs[ri].ymax, s.y); // Collect all region layers. if (nlregs < RC_MAX_LAYERS) lregs[nlregs++] = ri; // Update neighbours for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = x + rcGetDirOffsetX(dir); const int ay = y + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); const unsigned char rai = srcReg[ai]; if (rai != 0xff && rai != ri) addUnique(regs[ri].neis, regs[ri].nneis, rai); } } } // Update overlapping regions. for (int i = 0; i < nlregs-1; ++i) { for (int j = i+1; j < nlregs; ++j) { if (lregs[i] != lregs[j]) { rcLayerRegion& ri = regs[lregs[i]]; rcLayerRegion& rj = regs[lregs[j]]; addUnique(ri.layers, ri.nlayers, lregs[j]); addUnique(rj.layers, rj.nlayers, lregs[i]); } } } } } // Create 2D layers from regions. unsigned char layerId = 0; static const int MAX_STACK = 64; unsigned char stack[MAX_STACK]; int nstack = 0; for (int i = 0; i < nregs; ++i) { rcLayerRegion& root = regs[i]; // Skip alreadu visited. if (root.layerId != 0xff) continue; // Start search. root.layerId = layerId; root.base = 1; nstack = 0; stack[nstack++] = (unsigned char)i; while (nstack) { // Pop front rcLayerRegion& reg = regs[stack[0]]; nstack--; for (int j = 0; j < nstack; ++j) stack[j] = stack[j+1]; const int nneis = (int)reg.nneis; for (int j = 0; j < nneis; ++j) { const unsigned char nei = reg.neis[j]; rcLayerRegion& regn = regs[nei]; // Skip already visited. if (regn.layerId != 0xff) continue; // Skip if the neighbour is overlapping root region. if (contains(root.layers, root.nlayers, nei)) continue; // Skip if the height range would become too large. const int ymin = rcMin(root.ymin, regn.ymin); const int ymax = rcMax(root.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; if (nstack < MAX_STACK) { // Deepen stack[nstack++] = (unsigned char)nei; // Mark layer id regn.layerId = layerId; // Merge current layers to root. for (int k = 0; k < regn.nlayers; ++k) addUnique(root.layers, root.nlayers, regn.layers[k]); root.ymin = rcMin(root.ymin, regn.ymin); root.ymax = rcMax(root.ymax, regn.ymax); } } } layerId++; } // Merge non-overlapping regions that are close in height. const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; for (int i = 0; i < nregs; ++i) { rcLayerRegion& ri = regs[i]; if (!ri.base) continue; unsigned char newId = ri.layerId; for (;;) { unsigned char oldId = 0xff; for (int j = 0; j < nregs; ++j) { if (i == j) continue; rcLayerRegion& rj = regs[j]; if (!rj.base) continue; // Skip if teh regions are not close to each other. if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) continue; // Skip if the height range would become too large. const int ymin = rcMin(ri.ymin, rj.ymin); const int ymax = rcMax(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) continue; // Make sure that there is no overlap when mergin 'ri' and 'rj'. bool overlap = false; // Iterate over all regions which have the same layerId as 'rj' for (int k = 0; k < nregs; ++k) { if (regs[k].layerId != rj.layerId) continue; // Check if region 'k' is overlapping region 'ri' // Index to 'regs' is the same as region id. if (contains(ri.layers,ri.nlayers, (unsigned char)k)) { overlap = true; break; } } // Cannot merge of regions overlap. if (overlap) continue; // Can merge i and j. oldId = rj.layerId; break; } // Could not find anything to merge with, stop. if (oldId == 0xff) break; // Merge for (int j = 0; j < nregs; ++j) { rcLayerRegion& rj = regs[j]; if (rj.layerId == oldId) { rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.nlayers; ++k) addUnique(ri.layers, ri.nlayers, rj.layers[k]); // Update heigh bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } } } } // Compact layerIds unsigned char remap[256]; memset(remap, 0, 256); // Find number of unique layers. layerId = 0; for (int i = 0; i < nregs; ++i) remap[regs[i].layerId] = 1; for (int i = 0; i < 256; ++i) { if (remap[i]) remap[i] = layerId++; else remap[i] = 0xff; } // Remap ids. for (int i = 0; i < nregs; ++i) regs[i].layerId = remap[regs[i].layerId]; // No layers, return empty. if (layerId == 0) { ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } // Create layers. rcAssert(lset.layers == 0); const int lw = w - borderSize*2; const int lh = h - borderSize*2; // Build contracted bbox for layers. float bmin[3], bmax[3]; rcVcopy(bmin, chf.bmin); rcVcopy(bmax, chf.bmax); bmin[0] += borderSize*chf.cs; bmin[2] += borderSize*chf.cs; bmax[0] -= borderSize*chf.cs; bmax[2] -= borderSize*chf.cs; lset.nlayers = (int)layerId; lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); if (!lset.layers) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers); return false; } memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); // Store layers. for (int i = 0; i < lset.nlayers; ++i) { unsigned char curId = (unsigned char)i; // Allocate memory for the current layer. rcHeightfieldLayer* layer = &lset.layers[i]; memset(layer, 0, sizeof(rcHeightfieldLayer)); const int gridSize = sizeof(unsigned char)*lw*lh; layer->heights = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->heights) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize); return false; } memset(layer->heights, 0xff, gridSize); layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->areas) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); return false; } memset(layer->areas, 0, gridSize); layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->cons) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); return false; } memset(layer->cons, 0, gridSize); // Find layer height bounds. int hmin = 0, hmax = 0; for (int j = 0; j < nregs; ++j) { if (regs[j].base && regs[j].layerId == curId) { hmin = (int)regs[j].ymin; hmax = (int)regs[j].ymax; } } layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; // Adjust the bbox to fit the heighfield. rcVcopy(layer->bmin, bmin); rcVcopy(layer->bmax, bmax); layer->bmin[1] = bmin[1] + hmin*chf.ch; layer->bmax[1] = bmin[1] + hmax*chf.ch; layer->hmin = hmin; layer->hmax = hmax; // Update usable data region. layer->minx = layer->width; layer->maxx = 0; layer->miny = layer->height; layer->maxy = 0; // Copy height and area from compact heighfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) { const int cx = borderSize+x; const int cy = borderSize+y; const rcCompactCell& c = chf.cells[cx+cy*w]; for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) { const rcCompactSpan& s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0xff) continue; // Skip of does nto belong to current layer. unsigned char lid = regs[srcReg[j]].layerId; if (lid != curId) continue; // Update data bounds. layer->minx = rcMin(layer->minx, x); layer->maxx = rcMax(layer->maxx, x); layer->miny = rcMin(layer->miny, y); layer->maxy = rcMax(layer->maxy, y); // Store height and area type. const int idx = x+y*lw; layer->heights[idx] = (unsigned char)(s.y - hmin); layer->areas[idx] = chf.areas[j]; // Check connection. unsigned char portal = 0; unsigned char con = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); unsigned char alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; // Portal mask if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { portal |= (unsigned char)(1<<dir); // Update height so that it matches on both sides of the portal. const rcCompactSpan& as = chf.spans[ai]; if (as.y > hmin) layer->heights[idx] = rcMax(layer->heights[idx], (unsigned char)(as.y - hmin)); } // Valid connection mask if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { const int nx = ax - borderSize; const int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) con |= (unsigned char)(1<<dir); } } } layer->cons[idx] = (portal << 4) | con; } } } if (layer->minx > layer->maxx) layer->minx = layer->maxx = 0; if (layer->miny > layer->maxy) layer->miny = layer->maxy = 0; } ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; }
dtNavMesh* buildMesh(InputGeom* geom, WCellBuildContext* wcellContext, int numCores) { dtNavMesh* mesh = 0; if (!geom || !geom->getMesh()) { CleanupAfterBuild(); wcellContext->log(RC_LOG_ERROR, "buildTiledNavigation: No vertices and triangles."); return 0; } mesh = dtAllocNavMesh(); if (!mesh) { CleanupAfterBuild(); wcellContext->log(RC_LOG_ERROR, "buildTiledNavigation: Could not allocate navmesh."); return 0; } // setup some default parameters rcConfig cfg; memset(&cfg, 0, sizeof(rcConfig)); const float agentHeight = 2.1f; // most character toons are about this tall const float agentRadius = 0.6f; // most character toons are about this big around const float agentClimb = 1.0f; // character toons can step up this far. Seems ridiculously high ... const float tileSize = 1600.0f/3.0f/16.0f; // The size of one chunk cfg.cs = 0.1f; // cell size is a sort of resolution -> the bigger the faster cfg.ch = 0.05f; // cell height -> distance from mesh to ground, if too low, recast will not build essential parts of the mesh for some reason cfg.walkableSlopeAngle = 50.0f; // max climbable slope, bigger values won't make much of a change cfg.walkableHeight = (int)ceilf(agentHeight/cfg.ch);// minimum space to ceiling cfg.walkableClimb = (int)floorf(agentClimb/cfg.ch); // how high the agent can climb in one step cfg.walkableRadius = (int)ceilf(agentRadius/cfg.cs);// minimum distance to objects cfg.tileSize = (int)(tileSize/cfg.cs + 0.5f); cfg.maxEdgeLen = cfg.tileSize/2;; cfg.borderSize = cfg.walkableRadius + 3; cfg.width = cfg.tileSize + cfg.borderSize*2; cfg.height = cfg.tileSize + cfg.borderSize*2; cfg.maxSimplificationError = 1.3f; cfg.minRegionArea = (int)rcSqr(8); // Note: area = size*size cfg.mergeRegionArea = (int)rcSqr(20); // Note: area = size*size cfg.maxVertsPerPoly = 3; cfg.detailSampleDist = cfg.cs * 9; cfg.detailSampleMaxError = cfg.ch * 1.0f; // default calculations - for some reason not included in basic recast const float* bmin = geom->getMeshBoundsMin(); const float* bmax = geom->getMeshBoundsMax(); int gw = 0, gh = 0; rcCalcGridSize(bmin, bmax, cfg.cs, &gw, &gh); const int ts = cfg.tileSize; const int tw = (gw + ts-1) / ts; const int th = (gh + ts-1) / ts; // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. int tileBits = rcMin((int)ilog2(nextPow2(tw*th)), 14); if (tileBits > 14) tileBits = 14; int polyBits = 22 - tileBits; int maxTiles = 1 << tileBits; int maxPolysPerTile = 1 << polyBits; dtNavMeshParams params; rcVcopy(params.orig, geom->getMeshBoundsMin()); params.tileWidth = cfg.tileSize * cfg.cs; params.tileHeight = cfg.tileSize * cfg.cs; params.maxTiles = maxTiles; params.maxPolys = maxPolysPerTile; dtStatus status; status = mesh->init(¶ms); if (dtStatusFailed(status)) { CleanupAfterBuild(); wcellContext->log(RC_LOG_ERROR, "buildTiledNavigation: Could not init navmesh."); return 0; } // start building const float tcs = cfg.tileSize*cfg.cs; wcellContext->startTimer(RC_TIMER_TEMP); TileAdder Adder; dispatcher.Reset(); dispatcher.maxHeight = th; dispatcher.maxWidth = tw; int numThreads = 0; numThreads = std::min(2*numCores, 8); boost::thread *threads[8]; for(int i = 0; i < numThreads; ++i) { QuadrantTiler newTiler; newTiler.geom = geom; newTiler.cfg = cfg; newTiler.ctx = *wcellContext; boost::thread newThread(boost::ref(newTiler)); threads[i] = &newThread; } Adder.mesh = mesh; Adder.numThreads = numThreads; boost::thread AdderThread(boost::ref(Adder)); AdderThread.join(); // Start the build process. wcellContext->stopTimer(RC_TIMER_TEMP); return mesh; }
bool rcBuildCompactHeightfield(rcContext* ctx, const int walkableHeight, const int walkableClimb, rcHeightfield& hf, rcCompactHeightfield& chf) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); const int w = hf.width; const int h = hf.height; const int spanCount = rcGetHeightFieldSpanCount(ctx, hf); // Fill in header. chf.width = w; chf.height = h; chf.spanCount = spanCount; chf.walkableHeight = walkableHeight; chf.walkableClimb = walkableClimb; chf.maxRegions = 0; rcVcopy(chf.bmin, hf.bmin); rcVcopy(chf.bmax, hf.bmax); chf.bmax[1] += walkableHeight*hf.ch; chf.cs = hf.cs; chf.ch = hf.ch; chf.cells = (rcCompactCell*)rcAlloc(sizeof(rcCompactCell)*w*h, RC_ALLOC_PERM); if (!chf.cells) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.cells' (%d)", w*h); return false; } memset(chf.cells, 0, sizeof(rcCompactCell)*w*h); chf.spans = (rcCompactSpan*)rcAlloc(sizeof(rcCompactSpan)*spanCount, RC_ALLOC_PERM); if (!chf.spans) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.spans' (%d)", spanCount); return false; } memset(chf.spans, 0, sizeof(rcCompactSpan)*spanCount); chf.areas = (unsigned char*)rcAlloc(sizeof(unsigned char)*spanCount, RC_ALLOC_PERM); if (!chf.areas) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Out of memory 'chf.areas' (%d)", spanCount); return false; } memset(chf.areas, RC_NULL_AREA, sizeof(unsigned char)*spanCount); const int MAX_HEIGHT = 0xffff; // Fill in cells and spans. int idx = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcSpan* s = hf.spans[x + y*w]; // If there are no spans at this cell, just leave the data to index=0, count=0. if (!s) continue; rcCompactCell& c = chf.cells[x+y*w]; c.index = idx; c.count = 0; while (s) { if (s->area != RC_NULL_AREA) { const int bot = (int)s->smax; const int top = s->next ? (int)s->next->smin : MAX_HEIGHT; chf.spans[idx].y = (unsigned short)rcClamp(bot, 0, 0xffff); chf.spans[idx].h = (unsigned char)rcClamp(top - bot, 0, 0xff); chf.areas[idx] = s->area; idx++; c.count++; } s = s->next; } } } // Find neighbour connections. const int MAX_LAYERS = RC_NOT_CONNECTED-1; int tooHighNeighbour = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan& s = chf.spans[i]; for (int dir = 0; dir < 4; ++dir) { rcSetCon(s, dir, RC_NOT_CONNECTED); const int nx = x + rcGetDirOffsetX(dir); const int ny = y + rcGetDirOffsetY(dir); // First check that the neighbour cell is in bounds. if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue; // Iterate over all neighbour spans and check if any of the is // accessible from current cell. const rcCompactCell& nc = chf.cells[nx+ny*w]; for (int k = (int)nc.index, nk = (int)(nc.index+nc.count); k < nk; ++k) { const rcCompactSpan& ns = chf.spans[k]; const int bot = rcMax(s.y, ns.y); const int top = rcMin(s.y+s.h, ns.y+ns.h); // Check that the gap between the spans is walkable, // and that the climb height between the gaps is not too high. if ((top - bot) >= walkableHeight && rcAbs((int)ns.y - (int)s.y) <= walkableClimb) { // Mark direction as walkable. const int idx = k - (int)nc.index; if (idx < 0 || idx > MAX_LAYERS) { tooHighNeighbour = rcMax(tooHighNeighbour, idx); continue; } rcSetCon(s, dir, idx); break; } } } } } } if (tooHighNeighbour > MAX_LAYERS) { ctx->log(RC_LOG_ERROR, "rcBuildCompactHeightfield: Heightfield has too many layers %d (max: %d)", tooHighNeighbour, MAX_LAYERS); } ctx->stopTimer(RC_TIMER_BUILD_COMPACTHEIGHTFIELD); return true; }
void KX_NavMeshObject::DrawNavMesh(NavMeshRenderMode renderMode) { if (!m_navMesh) return; MT_Vector3 color(0.f, 0.f, 0.f); switch (renderMode) { case RM_POLYS : case RM_WALLS : for (int pi=0; pi<m_navMesh->getPolyCount(); pi++) { const dtStatPoly* poly = m_navMesh->getPoly(pi); for (int i = 0, j = (int)poly->nv-1; i < (int)poly->nv; j = i++) { if (poly->n[j] && renderMode==RM_WALLS) continue; const float* vif = m_navMesh->getVertex(poly->v[i]); const float* vjf = m_navMesh->getVertex(poly->v[j]); MT_Point3 vi(vif[0], vif[2], vif[1]); MT_Point3 vj(vjf[0], vjf[2], vjf[1]); vi = TransformToWorldCoords(vi); vj = TransformToWorldCoords(vj); KX_RasterizerDrawDebugLine(vi, vj, color); } } break; case RM_TRIS : for (int i = 0; i < m_navMesh->getPolyDetailCount(); ++i) { const dtStatPoly* p = m_navMesh->getPoly(i); const dtStatPolyDetail* pd = m_navMesh->getPolyDetail(i); for (int j = 0; j < pd->ntris; ++j) { const unsigned char* t = m_navMesh->getDetailTri(pd->tbase+j); MT_Point3 tri[3]; for (int k = 0; k < 3; ++k) { const float* v; if (t[k] < p->nv) v = m_navMesh->getVertex(p->v[t[k]]); else v = m_navMesh->getDetailVertex(pd->vbase+(t[k]-p->nv)); float pos[3]; rcVcopy(pos, v); flipAxes(pos); tri[k].setValue(pos); } for (int k=0; k<3; k++) tri[k] = TransformToWorldCoords(tri[k]); for (int k=0; k<3; k++) KX_RasterizerDrawDebugLine(tri[k], tri[(k+1)%3], color); } } break; default: /* pass */ break; } }
static bool buildPolyDetail(const float* in, const int nin, const float sampleDist, const float sampleMaxError, const rcCompactHeightfield& chf, const rcHeightPatch& hp, float* verts, int& nverts, rcIntArray& tris, rcIntArray& edges, rcIntArray& samples) { static const int MAX_VERTS = 256; static const int MAX_EDGE = 64; float edge[(MAX_EDGE+1)*3]; int hull[MAX_VERTS]; int nhull = 0; nverts = 0; for (int i = 0; i < nin; ++i) rcVcopy(&verts[i*3], &in[i*3]); nverts = nin; const float cs = chf.cs; const float ics = 1.0f/cs; // Tesselate outlines. // This is done in separate pass in order to ensure // seamless height values across the ply boundaries. if (sampleDist > 0) { for (int i = 0, j = nin-1; i < nin; j=i++) { const float* vj = &in[j*3]; const float* vi = &in[i*3]; bool swapped = false; // Make sure the segments are always handled in same order // using lexological sort or else there will be seams. if (fabsf(vj[0]-vi[0]) < 1e-6f) { if (vj[2] > vi[2]) { rcSwap(vj,vi); swapped = true; } } else { if (vj[0] > vi[0]) { rcSwap(vj,vi); swapped = true; } } // Create samples along the edge. float dx = vi[0] - vj[0]; float dy = vi[1] - vj[1]; float dz = vi[2] - vj[2]; float d = sqrtf(dx*dx + dz*dz); int nn = 1 + (int)floorf(d/sampleDist); if (nn > MAX_EDGE) nn = MAX_EDGE; if (nverts+nn >= MAX_VERTS) nn = MAX_VERTS-1-nverts; for (int k = 0; k <= nn; ++k) { float u = (float)k/(float)nn; float* pos = &edge[k*3]; pos[0] = vj[0] + dx*u; pos[1] = vj[1] + dy*u; pos[2] = vj[2] + dz*u; pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, hp)*chf.ch; } // Simplify samples. int idx[MAX_EDGE] = {0,nn}; int nidx = 2; for (int k = 0; k < nidx-1; ) { const int a = idx[k]; const int b = idx[k+1]; const float* va = &edge[a*3]; const float* vb = &edge[b*3]; // Find maximum deviation along the segment. float maxd = 0; int maxi = -1; for (int m = a+1; m < b; ++m) { float d = distancePtSeg(&edge[m*3],va,vb); if (d > maxd) { maxd = d; maxi = m; } } // If the max deviation is larger than accepted error, // add new point, else continue to next segment. if (maxi != -1 && maxd > rcSqr(sampleMaxError)) { for (int m = nidx; m > k; --m) idx[m] = idx[m-1]; idx[k+1] = maxi; nidx++; } else { ++k; } } hull[nhull++] = j; // Add new vertices. if (swapped) { for (int k = nidx-2; k > 0; --k) { rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); hull[nhull++] = nverts; nverts++; } } else { for (int k = 1; k < nidx-1; ++k) { rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); hull[nhull++] = nverts; nverts++; } } } } // Tesselate the base mesh. edges.resize(0); tris.resize(0); delaunayHull(nverts, verts, nhull, hull, tris, edges); if (tris.size() == 0) { // Could not triangulate the poly, make sure there is some valid data there. if (rcGetLog()) rcGetLog()->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); for (int i = 2; i < nverts; ++i) { tris.push(0); tris.push(i-1); tris.push(i); tris.push(0); } return true; } if (sampleDist > 0) { // Create sample locations in a grid. float bmin[3], bmax[3]; rcVcopy(bmin, in); rcVcopy(bmax, in); for (int i = 1; i < nin; ++i) { rcVmin(bmin, &in[i*3]); rcVmax(bmax, &in[i*3]); } int x0 = (int)floorf(bmin[0]/sampleDist); int x1 = (int)ceilf(bmax[0]/sampleDist); int z0 = (int)floorf(bmin[2]/sampleDist); int z1 = (int)ceilf(bmax[2]/sampleDist); samples.resize(0); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) { float pt[3]; pt[0] = x*sampleDist; pt[1] = (bmax[1]+bmin[1])*0.5f; pt[2] = z*sampleDist; // Make sure the samples are not too close to the edges. if (distToPoly(nin,in,pt) > -sampleDist/2) continue; samples.push(x); samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, hp)); samples.push(z); } } // Add the samples starting from the one that has the most // error. The procedure stops when all samples are added // or when the max error is within treshold. const int nsamples = samples.size()/3; for (int iter = 0; iter < nsamples; ++iter) { // Find sample with most error. float bestpt[3] = {0,0,0}; float bestd = 0; for (int i = 0; i < nsamples; ++i) { float pt[3]; pt[0] = samples[i*3+0]*sampleDist; pt[1] = samples[i*3+1]*chf.ch; pt[2] = samples[i*3+2]*sampleDist; float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4); if (d < 0) continue; // did not hit the mesh. if (d > bestd) { bestd = d; rcVcopy(bestpt,pt); } } // If the max error is within accepted threshold, stop tesselating. if (bestd <= sampleMaxError) break; // Add the new sample point. rcVcopy(&verts[nverts*3],bestpt); nverts++; // Create new triangulation. // TODO: Incremental add instead of full rebuild. edges.resize(0); tris.resize(0); delaunayHull(nverts, verts, nhull, hull, tris, edges); if (nverts >= MAX_VERTS) break; } } return true; }
bool Sample_TempObstacles::handleBuild() { dtStatus status; if (!m_geom || !m_geom->getMesh()) { m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: No vertices and triangles."); return false; } m_tmproc->init(m_geom); // Init cache const float* bmin = m_geom->getMeshBoundsMin(); const float* bmax = m_geom->getMeshBoundsMax(); int gw = 0, gh = 0; rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh); const int ts = (int)m_tileSize; const int tw = (gw + ts-1) / ts; const int th = (gh + ts-1) / ts; // Generation params. rcConfig cfg; memset(&cfg, 0, sizeof(cfg)); cfg.cellSizeXZ = m_cellSize; cfg.cellSizeY = m_cellHeight; cfg.walkableSlopeAngle = m_agentMaxSlope; cfg.walkableHeight = (int)ceilf(m_agentHeight / cfg.cellSizeY); cfg.walkableClimb = (int)floorf(m_agentMaxClimb / cfg.cellSizeY); cfg.walkableRadius = (int)ceilf(m_agentRadius / cfg.cellSizeXZ); cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); cfg.maxSimplificationError = m_edgeMaxError; cfg.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size cfg.maxVertsPerPoly = (int)m_vertsPerPoly; cfg.tileSize = (int)m_tileSize; cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding. cfg.width = cfg.tileSize + cfg.borderSize*2; cfg.height = cfg.tileSize + cfg.borderSize*2; cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); // Tile cache params. dtTileCacheParams tcparams; memset(&tcparams, 0, sizeof(tcparams)); rcVcopy(tcparams.orig, bmin); tcparams.cs = m_cellSize; tcparams.ch = m_cellHeight; tcparams.width = (int)m_tileSize; tcparams.height = (int)m_tileSize; tcparams.walkableHeight = m_agentHeight; tcparams.walkableRadius = m_agentRadius; tcparams.walkableClimb = m_agentMaxClimb; tcparams.maxSimplificationError = m_edgeMaxError; tcparams.maxTiles = tw*th*EXPECTED_LAYERS_PER_TILE; tcparams.maxObstacles = 128; dtFreeTileCache(m_tileCache); m_tileCache = dtAllocTileCache(); if (!m_tileCache) { m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not allocate tile cache."); return false; } status = m_tileCache->init(&tcparams, m_talloc, m_tcomp, m_tmproc); if (dtStatusFailed(status)) { m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not init tile cache."); return false; } dtFreeNavMesh(m_navMesh); m_navMesh = dtAllocNavMesh(); if (!m_navMesh) { m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not allocate navmesh."); return false; } dtNavMeshParams params; memset(¶ms, 0, sizeof(params)); rcVcopy(params.orig, m_geom->getMeshBoundsMin()); params.tileWidth = m_tileSize*m_cellSize; params.tileHeight = m_tileSize*m_cellSize; params.maxTiles = m_maxTiles; params.maxPolys = m_maxPolysPerTile; status = m_navMesh->init(¶ms); if (dtStatusFailed(status)) { m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not init navmesh."); return false; } status = m_navQuery->init(m_navMesh, 2048); if (dtStatusFailed(status)) { m_ctx->log(RC_LOG_ERROR, "buildTiledNavigation: Could not init Detour navmesh query"); return false; } // Preprocess tiles. m_ctx->resetTimers(); m_cacheLayerCount = 0; m_cacheCompressedSize = 0; m_cacheRawSize = 0; for (int y = 0; y < th; ++y) { for (int x = 0; x < tw; ++x) { TileCacheData tiles[MAX_LAYERS]; memset(tiles, 0, sizeof(tiles)); int ntiles = rasterizeTileLayers(m_ctx, m_geom, x, y, cfg, tiles, MAX_LAYERS); for (int i = 0; i < ntiles; ++i) { TileCacheData* tile = &tiles[i]; status = m_tileCache->addTile(tile->data, tile->dataSize, DT_COMPRESSEDTILE_FREE_DATA, 0); if (dtStatusFailed(status)) { dtFree(tile->data); tile->data = 0; continue; } m_cacheLayerCount++; m_cacheCompressedSize += tile->dataSize; m_cacheRawSize += calcLayerBufferSize(tcparams.width, tcparams.height); } } } // Build initial meshes m_ctx->startTimer(RC_TIMER_TOTAL); for (int y = 0; y < th; ++y) for (int x = 0; x < tw; ++x) m_tileCache->buildNavMeshTilesAt(x,y, m_navMesh); m_ctx->stopTimer(RC_TIMER_TOTAL); m_cacheBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; m_cacheBuildMemUsage = m_talloc->high; const dtNavMesh* nav = m_navMesh; int navmeshMemUsage = 0; for (int i = 0; i < nav->getMaxTiles(); ++i) { const dtMeshTile* tile = nav->getTile(i); if (tile->header) navmeshMemUsage += tile->dataSize; } printf("navmeshMemUsage = %.1f kB", navmeshMemUsage/1024.0f); if (m_tool) m_tool->init(this); initToolStates(this); return true; }
virtual void handleClick(const float* /*s*/, const float* p, bool /*shift*/) { m_hitPosSet = true; rcVcopy(m_hitPos,p); }
/// @see rcAllocPolyMeshDetail, rcPolyMeshDetail bool rcMergePolyMeshDetails(rcContext* ctx, rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh) { rcAssert(ctx); ctx->startTimer(RC_TIMER_MERGE_POLYMESHDETAIL); int maxVerts = 0; int maxTris = 0; int maxMeshes = 0; for (int i = 0; i < nmeshes; ++i) { if (!meshes[i]) continue; maxVerts += meshes[i]->nverts; maxTris += meshes[i]->ntris; maxMeshes += meshes[i]->nmeshes; } mesh.nmeshes = 0; mesh.meshes = (unsigned int*)rcAlloc(sizeof(unsigned int)*maxMeshes*4, RC_ALLOC_PERM); if (!mesh.meshes) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4); return false; } mesh.ntris = 0; mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM); if (!mesh.tris) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4); return false; } mesh.nverts = 0; mesh.verts = (dtCoordinates*)rcAlloc(sizeof(dtCoordinates)*maxVerts, RC_ALLOC_PERM); if (!mesh.verts) { ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts*3); return false; } // Merge datas. for (int i = 0; i < nmeshes; ++i) { rcPolyMeshDetail* dm = meshes[i]; if (!dm) continue; for (int j = 0; j < dm->nmeshes; ++j) { unsigned int* dst = &mesh.meshes[mesh.nmeshes*4]; unsigned int* src = &dm->meshes[j*4]; dst[0] = (unsigned int)mesh.nverts+src[0]; dst[1] = src[1]; dst[2] = (unsigned int)mesh.ntris+src[2]; dst[3] = src[3]; mesh.nmeshes++; } for (int k = 0; k < dm->nverts; ++k) { rcVcopy(mesh.verts[mesh.nverts], dm->verts[k]); mesh.nverts++; } for (int k = 0; k < dm->ntris; ++k) { mesh.tris[mesh.ntris*4+0] = dm->tris[k*4+0]; mesh.tris[mesh.ntris*4+1] = dm->tris[k*4+1]; mesh.tris[mesh.ntris*4+2] = dm->tris[k*4+2]; mesh.tris[mesh.ntris*4+3] = dm->tris[k*4+3]; mesh.ntris++; } } ctx->stopTimer(RC_TIMER_MERGE_POLYMESHDETAIL); return true; }
static bool buildPolyDetail(rcContext* ctx, const dtCoordinates* in, const int nin, const float sampleDist, const float sampleMaxError, const rcCompactHeightfield& chf, const rcHeightPatch& hp, dtCoordinates* verts, int& nverts, rcIntArray& tris, rcIntArray& edges, rcIntArray& samples #ifdef MODIFY_VOXEL_FLAG , const char /*area*/ #endif // MODIFY_VOXEL_FLAG ) { static const int MAX_VERTS = 127; static const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). static const int MAX_VERTS_PER_EDGE = 32; dtCoordinates edge[(MAX_VERTS_PER_EDGE+1)]; int hull[MAX_VERTS]; int nhull = 0; nverts = 0; for (int i = 0; i < nin; ++i) rcVcopy(verts[i], in[i]); nverts = nin; const float cs = chf.cs; const float ics = 1.0f/cs; // Tessellate outlines. // This is done in separate pass in order to ensure // seamless height values across the ply boundaries. #ifdef MODIFY_VOXEL_FLAG if( 0 < sampleDist /*&& rcIsTerrainArea( area )*/ ) #else // MODIFY_VOXEL_FLAG if (sampleDist > 0) #endif // MODIFY_VOXEL_FLAG { for (int i = 0, j = nin-1; i < nin; j=i++) { const dtCoordinates* vj = &in[j]; const dtCoordinates* vi = &in[i]; bool swapped = false; // Make sure the segments are always handled in same order // using lexological sort or else there will be seams. if (fabsf(vj->X()-vi->X()) < 1e-6f) { if (vj->Z() > vi->Z()) { rcSwap(vj,vi); swapped = true; } } else { if (vj->X() > vi->X()) { rcSwap(vj,vi); swapped = true; } } // Create samples along the edge. float dx = vi->X() - vj->X(); float dy = vi->Y() - vj->Y(); float dz = vi->Z() - vj->Z(); float d = sqrtf(dx*dx + dz*dz); int nn = 1 + (int)floorf(d/sampleDist); if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE-1; if (nverts+nn >= MAX_VERTS) nn = MAX_VERTS-1-nverts; for (int k = 0; k <= nn; ++k) { float u = (float)k/(float)nn; dtCoordinates* pos = &edge[k]; pos->SetX( vj->X() + dx*u ); pos->SetY( vj->Y() + dy*u ); pos->SetZ( vj->Z() + dz*u ); pos->SetY( getHeight(pos->X(),pos->Y(),pos->Z(), cs, ics, chf.ch, hp)*chf.ch ); } // Simplify samples. int idx[MAX_VERTS_PER_EDGE] = {0,nn}; int nidx = 2; for (int k = 0; k < nidx-1; ) { const int a = idx[k]; const int b = idx[k+1]; const dtCoordinates va( edge[a] ); const dtCoordinates vb( edge[b] ); // Find maximum deviation along the segment. float maxd = 0; int maxi = -1; for (int m = a+1; m < b; ++m) { float dev = distancePtSeg(edge[m],va,vb); if (dev > maxd) { maxd = dev; maxi = m; } } // If the max deviation is larger than accepted error, // add new point, else continue to next segment. if (maxi != -1 && maxd > rcSqr(sampleMaxError)) { for (int m = nidx; m > k; --m) idx[m] = idx[m-1]; idx[k+1] = maxi; nidx++; } else { ++k; } } hull[nhull++] = j; // Add new vertices. if (swapped) { for (int k = nidx-2; k > 0; --k) { rcVcopy(verts[nverts], edge[idx[k]]); hull[nhull++] = nverts; nverts++; } } else { for (int k = 1; k < nidx-1; ++k) { rcVcopy(verts[nverts], edge[idx[k]]); hull[nhull++] = nverts; nverts++; } } } } // Tessellate the base mesh. edges.resize(0); tris.resize(0); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); if (tris.size() == 0) { // Could not triangulate the poly, make sure there is some valid data there. ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon, adding default data."); for (int i = 2; i < nverts; ++i) { tris.push(0); tris.push(i-1); tris.push(i); tris.push(0); } return true; } #ifdef MODIFY_VOXEL_FLAG if( 0 < sampleDist /*&& rcIsTerrainArea( area )*/ ) #else // MODIFY_VOXEL_FLAG if (sampleDist > 0) #endif // MODIFY_VOXEL_FLAG { // Create sample locations in a grid. dtCoordinates bmin, bmax; rcVcopy(bmin, in[0]); rcVcopy(bmax, in[0]); for (int i = 1; i < nin; ++i) { rcVmin(bmin, in[i]); rcVmax(bmax, in[i]); } int x0 = (int)floorf(bmin.X()/sampleDist); int x1 = (int)ceilf(bmax.X()/sampleDist); int z0 = (int)floorf(bmin.Z()/sampleDist); int z1 = (int)ceilf(bmax.Z()/sampleDist); samples.resize(0); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) { const dtCoordinates pt( x*sampleDist, (bmax.Y()+bmin.Y())*0.5f, z*sampleDist ); // Make sure the samples are not too close to the edges. if (distToPoly(nin,in,pt) > -sampleDist/2) continue; samples.push(x); samples.push(getHeight(pt.X(), pt.Y(), pt.Z(), cs, ics, chf.ch, hp)); samples.push(z); samples.push(0); // Not added } } // Add the samples starting from the one that has the most // error. The procedure stops when all samples are added // or when the max error is within treshold. const int nsamples = samples.size()/4; for (int iter = 0; iter < nsamples; ++iter) { if (nverts >= MAX_VERTS) break; // Find sample with most error. dtCoordinates bestpt; float bestd = 0; int besti = -1; for (int i = 0; i < nsamples; ++i) { const int* s = &samples[i*4]; if (s[3]) continue; // skip added. const dtCoordinates pt( s[0]*sampleDist + getJitterX(i)*cs*0.1f, s[1]*chf.ch, s[2]*sampleDist + getJitterY(i)*cs*0.1f ); // The sample location is jittered to get rid of some bad triangulations // which are cause by symmetrical data from the grid structure. float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4); if (d < 0) continue; // did not hit the mesh. if (d > bestd) { bestd = d; besti = i; rcVcopy(bestpt,pt); } } // If the max error is within accepted threshold, stop tesselating. if (bestd <= sampleMaxError || besti == -1) break; // Mark sample as added. samples[besti*4+3] = 1; // Add the new sample point. rcVcopy(verts[nverts],bestpt); nverts++; // Create new triangulation. // TODO: Incremental add instead of full rebuild. edges.resize(0); tris.resize(0); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); } } const int ntris = tris.size()/4; if (ntris > MAX_TRIS) { tris.resize(MAX_TRIS*4); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS); } return true; }
static bool buildPolyDetail(rcContext* ctx, const float* in, const int nin, const float sampleDist, const float sampleMaxError, const int heightSearchRadius, const rcCompactHeightfield& chf, const rcHeightPatch& hp, float* verts, int& nverts, rcIntArray& tris, rcIntArray& edges, rcIntArray& samples) { static const int MAX_VERTS = 127; static const int MAX_TRIS = 255; // Max tris for delaunay is 2n-2-k (n=num verts, k=num hull verts). static const int MAX_VERTS_PER_EDGE = 32; float edge[(MAX_VERTS_PER_EDGE+1)*3]; int hull[MAX_VERTS]; int nhull = 0; nverts = nin; for (int i = 0; i < nin; ++i) rcVcopy(&verts[i*3], &in[i*3]); edges.resize(0); tris.resize(0); const float cs = chf.cs; const float ics = 1.0f/cs; // Calculate minimum extents of the polygon based on input data. float minExtent = polyMinExtent(verts, nverts); // Tessellate outlines. // This is done in separate pass in order to ensure // seamless height values across the ply boundaries. if (sampleDist > 0) { for (int i = 0, j = nin-1; i < nin; j=i++) { const float* vj = &in[j*3]; const float* vi = &in[i*3]; bool swapped = false; // Make sure the segments are always handled in same order // using lexological sort or else there will be seams. if (fabsf(vj[0]-vi[0]) < 1e-6f) { if (vj[2] > vi[2]) { rcSwap(vj,vi); swapped = true; } } else { if (vj[0] > vi[0]) { rcSwap(vj,vi); swapped = true; } } // Create samples along the edge. float dx = vi[0] - vj[0]; float dy = vi[1] - vj[1]; float dz = vi[2] - vj[2]; float d = sqrtf(dx*dx + dz*dz); int nn = 1 + (int)floorf(d/sampleDist); if (nn >= MAX_VERTS_PER_EDGE) nn = MAX_VERTS_PER_EDGE-1; if (nverts+nn >= MAX_VERTS) nn = MAX_VERTS-1-nverts; for (int k = 0; k <= nn; ++k) { float u = (float)k/(float)nn; float* pos = &edge[k*3]; pos[0] = vj[0] + dx*u; pos[1] = vj[1] + dy*u; pos[2] = vj[2] + dz*u; pos[1] = getHeight(pos[0],pos[1],pos[2], cs, ics, chf.ch, heightSearchRadius, hp)*chf.ch; } // Simplify samples. int idx[MAX_VERTS_PER_EDGE] = {0,nn}; int nidx = 2; for (int k = 0; k < nidx-1; ) { const int a = idx[k]; const int b = idx[k+1]; const float* va = &edge[a*3]; const float* vb = &edge[b*3]; // Find maximum deviation along the segment. float maxd = 0; int maxi = -1; for (int m = a+1; m < b; ++m) { float dev = distancePtSeg(&edge[m*3],va,vb); if (dev > maxd) { maxd = dev; maxi = m; } } // If the max deviation is larger than accepted error, // add new point, else continue to next segment. if (maxi != -1 && maxd > rcSqr(sampleMaxError)) { for (int m = nidx; m > k; --m) idx[m] = idx[m-1]; idx[k+1] = maxi; nidx++; } else { ++k; } } hull[nhull++] = j; // Add new vertices. if (swapped) { for (int k = nidx-2; k > 0; --k) { rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); hull[nhull++] = nverts; nverts++; } } else { for (int k = 1; k < nidx-1; ++k) { rcVcopy(&verts[nverts*3], &edge[idx[k]*3]); hull[nhull++] = nverts; nverts++; } } } } // If the polygon minimum extent is small (sliver or small triangle), do not try to add internal points. if (minExtent < sampleDist*2) { triangulateHull(nverts, verts, nhull, hull, tris); return true; } // Tessellate the base mesh. // We're using the triangulateHull instead of delaunayHull as it tends to // create a bit better triangulation for long thin triangles when there // are no internal points. triangulateHull(nverts, verts, nhull, hull, tris); if (tris.size() == 0) { // Could not triangulate the poly, make sure there is some valid data there. ctx->log(RC_LOG_WARNING, "buildPolyDetail: Could not triangulate polygon (%d verts).", nverts); return true; } if (sampleDist > 0) { // Create sample locations in a grid. float bmin[3], bmax[3]; rcVcopy(bmin, in); rcVcopy(bmax, in); for (int i = 1; i < nin; ++i) { rcVmin(bmin, &in[i*3]); rcVmax(bmax, &in[i*3]); } int x0 = (int)floorf(bmin[0]/sampleDist); int x1 = (int)ceilf(bmax[0]/sampleDist); int z0 = (int)floorf(bmin[2]/sampleDist); int z1 = (int)ceilf(bmax[2]/sampleDist); samples.resize(0); for (int z = z0; z < z1; ++z) { for (int x = x0; x < x1; ++x) { float pt[3]; pt[0] = x*sampleDist; pt[1] = (bmax[1]+bmin[1])*0.5f; pt[2] = z*sampleDist; // Make sure the samples are not too close to the edges. if (distToPoly(nin,in,pt) > -sampleDist/2) continue; samples.push(x); samples.push(getHeight(pt[0], pt[1], pt[2], cs, ics, chf.ch, heightSearchRadius, hp)); samples.push(z); samples.push(0); // Not added } } // Add the samples starting from the one that has the most // error. The procedure stops when all samples are added // or when the max error is within treshold. const int nsamples = samples.size()/4; for (int iter = 0; iter < nsamples; ++iter) { if (nverts >= MAX_VERTS) break; // Find sample with most error. float bestpt[3] = {0,0,0}; float bestd = 0; int besti = -1; for (int i = 0; i < nsamples; ++i) { const int* s = &samples[i*4]; if (s[3]) continue; // skip added. float pt[3]; // The sample location is jittered to get rid of some bad triangulations // which are cause by symmetrical data from the grid structure. pt[0] = s[0]*sampleDist + getJitterX(i)*cs*0.1f; pt[1] = s[1]*chf.ch; pt[2] = s[2]*sampleDist + getJitterY(i)*cs*0.1f; float d = distToTriMesh(pt, verts, nverts, &tris[0], tris.size()/4); if (d < 0) continue; // did not hit the mesh. if (d > bestd) { bestd = d; besti = i; rcVcopy(bestpt,pt); } } // If the max error is within accepted threshold, stop tesselating. if (bestd <= sampleMaxError || besti == -1) break; // Mark sample as added. samples[besti*4+3] = 1; // Add the new sample point. rcVcopy(&verts[nverts*3],bestpt); nverts++; // Create new triangulation. // TODO: Incremental add instead of full rebuild. edges.resize(0); tris.resize(0); delaunayHull(ctx, nverts, verts, nhull, hull, tris, edges); } } const int ntris = tris.size()/4; if (ntris > MAX_TRIS) { tris.resize(MAX_TRIS*4); ctx->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Shrinking triangle count from %d to max %d.", ntris, MAX_TRIS); } return true; }
static bool SplitAndStoreLayerRegions(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, unsigned short* srcReg, rcLayerRegionMonotone* regs, const int nregs, rcHeightfieldLayerSet& lset) { // Create 2D layers from regions. unsigned short layerId = 0; rcIntArray stack(64); stack.resize(0); for (int i = 0; i < nregs; ++i) { rcLayerRegionMonotone& root = regs[i]; // Skip already visited. if (root.layerId != 0xffff) continue; // Start search. root.layerId = layerId; root.base = 1; stack.push(i); while (stack.size()) { // Pop front rcLayerRegionMonotone& reg = regs[stack[0]]; for (int j = 1; j < stack.size(); ++j) stack[j - 1] = stack[j]; stack.pop(); const int nneis = (int)reg.neis.size(); for (int j = 0; j < nneis; ++j) { const int nei = reg.neis[j]; rcLayerRegionMonotone& regn = regs[nei]; // Skip already visited. if (regn.layerId != 0xffff) continue; // Skip if the neighbour is overlapping root region. if (root.layers.contains(nei)) continue; // Skip if the height range would become too large. const int ymin = rcMin(root.ymin, regn.ymin); const int ymax = rcMin(root.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; // Deepen stack.push(nei); // Mark layer id regn.layerId = layerId; // Merge current layers to root. for (int k = 0; k < regn.layers.size(); ++k) addUnique(root.layers, regn.layers[k]); root.ymin = rcMin(root.ymin, regn.ymin); root.ymax = rcMax(root.ymax, regn.ymax); } } layerId++; } // Merge non-overlapping regions that are close in height. const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; for (int i = 0; i < nregs; ++i) { rcLayerRegionMonotone& ri = regs[i]; if (!ri.base) continue; unsigned short newId = ri.layerId; for (;;) { unsigned short oldId = 0xffff; for (int j = 0; j < nregs; ++j) { if (i == j) continue; rcLayerRegionMonotone& rj = regs[j]; if (!rj.base) continue; // Skip if the regions are not close to each other. if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) continue; // Skip if the height range would become too large. const int ymin = rcMin(ri.ymin, rj.ymin); const int ymax = rcMin(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) continue; // Make sure that there is no overlap when mergin 'ri' and 'rj'. bool overlap = false; // Iterate over all regions which have the same layerId as 'rj' for (int k = 0; k < nregs; ++k) { if (regs[k].layerId != rj.layerId) continue; // Check if region 'k' is overlapping region 'ri' // Index to 'regs' is the same as region id. if (ri.layers.contains(k)) { overlap = true; break; } } // Cannot merge of regions overlap. if (overlap) continue; // Can merge i and j. oldId = rj.layerId; break; } // Could not find anything to merge with, stop. if (oldId == 0xffff) break; // Merge for (int j = 0; j < nregs; ++j) { rcLayerRegionMonotone& rj = regs[j]; if (rj.layerId == oldId) { rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.layers.size(); ++k) addUnique(ri.layers, rj.layers[k]); // Update heigh bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } } } } // Compact layerIds layerId = 0; if (nregs < 256) { // Compact ids. unsigned short remap[256]; memset(remap, 0, sizeof(unsigned short)*256); // Find number of unique regions. for (int i = 0; i < nregs; ++i) remap[regs[i].layerId] = 1; for (int i = 0; i < 256; ++i) if (remap[i]) remap[i] = layerId++; // Remap ids. for (int i = 0; i < nregs; ++i) regs[i].layerId = remap[regs[i].layerId]; } else { for (int i = 0; i < nregs; ++i) regs[i].remap = true; for (int i = 0; i < nregs; ++i) { if (!regs[i].remap) continue; unsigned short oldId = regs[i].layerId; unsigned short newId = ++layerId; for (int j = i; j < nregs; ++j) { if (regs[j].layerId == oldId) { regs[j].layerId = newId; regs[j].remap = false; } } } } // No layers, return empty. if (layerId == 0) { ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } // Create layers. rcAssert(lset.layers == 0); const int w = chf.width; const int h = chf.height; const int lw = w - borderSize*2; const int lh = h - borderSize*2; // Build contracted bbox for layers. float bmin[3], bmax[3]; rcVcopy(bmin, chf.bmin); rcVcopy(bmax, chf.bmax); bmin[0] += borderSize*chf.cs; bmin[2] += borderSize*chf.cs; bmax[0] -= borderSize*chf.cs; bmax[2] -= borderSize*chf.cs; lset.nlayers = (int)layerId; lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); if (!lset.layers) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'layers' (%d).", lset.nlayers); return false; } memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); // Store layers. for (int i = 0; i < lset.nlayers; ++i) { unsigned short curId = (unsigned short)i; // Allocate memory for the current layer. rcHeightfieldLayer* layer = &lset.layers[i]; memset(layer, 0, sizeof(rcHeightfieldLayer)); const int gridSize = sizeof(unsigned char)*lw*lh; const int gridSize2 = sizeof(unsigned short)*lw*lh; layer->heights = (unsigned short*)rcAlloc(gridSize2, RC_ALLOC_PERM); if (!layer->heights) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'heights' (%d).", gridSize2); return false; } memset(layer->heights, 0xff, gridSize2); layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->areas) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'areas' (%d).", gridSize); return false; } memset(layer->areas, 0, gridSize); layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->cons) { ctx->log(RC_LOG_ERROR, "SplitAndStoreLayerRegions: Out of memory 'cons' (%d).", gridSize); return false; } memset(layer->cons, 0, gridSize); // Find layer height bounds. int hmin = 0, hmax = 0; for (int j = 0; j < nregs; ++j) { if (regs[j].base && regs[j].layerId == curId) { hmin = (int)regs[j].ymin; hmax = (int)regs[j].ymax; } } layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; // Adjust the bbox to fit the heighfield. rcVcopy(layer->bmin, bmin); rcVcopy(layer->bmax, bmax); layer->bmin[1] = bmin[1] + hmin*chf.ch; layer->bmax[1] = bmin[1] + hmax*chf.ch; layer->hmin = hmin; layer->hmax = hmax; // Update usable data region. layer->minx = layer->width; layer->maxx = 0; layer->miny = layer->height; layer->maxy = 0; // Copy height and area from compact heighfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) { const int cx = borderSize+x; const int cy = borderSize+y; const rcCompactCell& c = chf.cells[cx+cy*w]; for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) { const rcCompactSpan& s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0xffff) continue; // Skip of does nto belong to current layer. unsigned short lid = regs[srcReg[j]].layerId; if (lid != curId) continue; // Update data bounds. layer->minx = rcMin(layer->minx, x); layer->maxx = rcMax(layer->maxx, x); layer->miny = rcMin(layer->miny, y); layer->maxy = rcMax(layer->maxy, y); // Store height and area type. const int idx = x+y*lw; layer->heights[idx] = (unsigned short)(s.y - hmin); layer->areas[idx] = chf.areas[j]; // Check connection. unsigned char portal = 0; unsigned char con = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); unsigned short alid = srcReg[ai] != 0xffff ? regs[srcReg[ai]].layerId : 0xffff; // Portal mask if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { portal |= (unsigned char)(1<<dir); // Update height so that it matches on both sides of the portal. const rcCompactSpan& as = chf.spans[ai]; if (as.y > hmin) layer->heights[idx] = rcMax(layer->heights[idx], (unsigned short)(as.y - hmin)); } // Valid connection mask if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { const int nx = ax - borderSize; const int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) { con |= (unsigned char)(1 << dir); // [UE4: make sure that connections are bidirectional, otherwise contour tracing will stuck in infinite loop] const int nidx = nx + (ny * lw); layer->cons[nidx] |= (unsigned char)(1 << ((dir + 2) % 4)); } } } } layer->cons[idx] |= (portal << 4) | con; } } } if (layer->minx > layer->maxx) layer->minx = layer->maxx = 0; if (layer->miny > layer->maxy) layer->miny = layer->maxy = 0; } return true; }
/// @par /// /// See the #rcConfig documentation for more information on the configuration parameters. /// /// @see rcAllocHeightfieldLayerSet, rcCompactHeightfield, rcHeightfieldLayerSet, rcConfig bool rcBuildHeightfieldLayers(rcContext* ctx, rcCompactHeightfield& chf, const int borderSize, const int walkableHeight, rcHeightfieldLayerSet& lset) { rcAssert(ctx); ctx->startTimer(RC_TIMER_BUILD_LAYERS); rcScopedDelete<unsigned short> spanBuf4 = (unsigned short*)rcAlloc(sizeof(unsigned short)*chf.spanCount*4, RC_ALLOC_TEMP); if (!spanBuf4) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'spanBuf4' (%d).", chf.spanCount*4); return false; } ctx->startTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); unsigned short* srcReg = spanBuf4; if (!rcGatherRegionsNoFilter(ctx, chf, borderSize, spanBuf4)) return false; ctx->stopTimer(RC_TIMER_BUILD_REGIONS_WATERSHED); ctx->startTimer(RC_TIMER_BUILD_REGIONS_FILTER); const int w = chf.width; const int h = chf.height; const int nreg = chf.maxRegions + 1; rcScopedStructArrayDelete<rcLayerRegion> regions(nreg); if (!regions) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'regions' (%d).", nreg); return false; } // Construct regions memset(regions, 0, sizeof(rcLayerRegion)*nreg); for (int i = 0; i < nreg; ++i) { regions[i].layerId = (unsigned short)i; regions[i].ymax = 0; regions[i].ymin = 0xffff; } // Find region neighbours and overlapping regions. for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const rcCompactCell& c = chf.cells[x+y*w]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { const rcCompactSpan& s = chf.spans[i]; const unsigned short ri = srcReg[i]; if (ri == 0 || ri >= nreg) continue; rcLayerRegion& reg = regions[ri]; reg.ymin = rcMin(reg.ymin, s.y); reg.ymax = rcMax(reg.ymax, s.y); reg.hasSpans = true; // Collect all region layers. for (int j = (int)c.index; j < ni; ++j) { unsigned short nri = srcReg[j]; if (nri == 0 || nri >= nreg) continue; if (nri != ri) { addUniqueLayerRegion(reg, nri); } } // Have found contour if (reg.connections.size() > 0) continue; // Check if this cell is next to a border. int ndir = -1; for (int dir = 0; dir < 4; ++dir) { if (isSolidEdge(chf, srcReg, x, y, i, dir)) { ndir = dir; break; } } if (ndir != -1) { // The cell is at border. // Walk around the contour to find all the neighbors. walkContour(x, y, i, ndir, chf, srcReg, reg.connections); } } } } // Create 2D layers from regions. unsigned short layerId = 0; rcIntArray stack(64); for (int i = 0; i < nreg; i++) { rcLayerRegion& reg = regions[i]; if (reg.visited || !reg.hasSpans) continue; reg.layerId = layerId; reg.visited = true; reg.base = true; stack.resize(0); stack.push(i); while (stack.size()) { int ri = stack.pop(); rcLayerRegion& creg = regions[ri]; for (int j = 0; j < creg.connections.size(); j++) { const unsigned short nei = (unsigned short)creg.connections[j]; if (nei & RC_BORDER_REG) continue; rcLayerRegion& regn = regions[nei]; // Skip already visited. if (regn.visited) continue; // Skip if the neighbor is overlapping root region. if (reg.layers.contains(nei)) continue; // Skip if the height range would become too large. const int ymin = rcMin(reg.ymin, regn.ymin); const int ymax = rcMin(reg.ymax, regn.ymax); if ((ymax - ymin) >= 255) continue; // visit stack.push(nei); regn.visited = true; regn.layerId = layerId; // add layers to root for (int k = 0; k < regn.layers.size(); k++) addUniqueLayerRegion(reg, regn.layers[k]); reg.ymin = rcMin(reg.ymin, regn.ymin); reg.ymax = rcMax(reg.ymax, regn.ymax); } } layerId++; } // Merge non-overlapping regions that are close in height. const unsigned short mergeHeight = (unsigned short)walkableHeight * 4; for (int i = 0; i < nreg; i++) { rcLayerRegion& ri = regions[i]; if (!ri.base) continue; unsigned short newId = ri.layerId; for (;;) { unsigned short oldId = 0xffff; for (int j = 0; j < nreg; j++) { if (i == j) continue; rcLayerRegion& rj = regions[j]; if (!rj.base) continue; // Skip if the regions are not close to each other. if (!overlapRange(ri.ymin,ri.ymax+mergeHeight, rj.ymin,rj.ymax+mergeHeight)) continue; // Skip if the height range would become too large. const int ymin = rcMin(ri.ymin, rj.ymin); const int ymax = rcMin(ri.ymax, rj.ymax); if ((ymax - ymin) >= 255) continue; // Make sure that there is no overlap when mergin 'ri' and 'rj'. bool overlap = false; // Iterate over all regions which have the same layerId as 'rj' for (int k = 0; k < nreg; ++k) { if (regions[k].layerId != rj.layerId) continue; // Check if region 'k' is overlapping region 'ri' // Index to 'regs' is the same as region id. if (ri.layers.contains(k)) { overlap = true; break; } } // Cannot merge of regions overlap. if (overlap) continue; // Can merge i and j. oldId = rj.layerId; break; } // Could not find anything to merge with, stop. if (oldId == 0xffff) break; // Merge for (int j = 0; j < nreg; ++j) { rcLayerRegion& rj = regions[j]; if (rj.layerId == oldId) { rj.base = 0; // Remap layerIds. rj.layerId = newId; // Add overlaid layers from 'rj' to 'ri'. for (int k = 0; k < rj.layers.size(); ++k) addUniqueLayerRegion(ri, rj.layers[k]); // Update height bounds. ri.ymin = rcMin(ri.ymin, rj.ymin); ri.ymax = rcMax(ri.ymax, rj.ymax); } } } } // Compress layer Ids. for (int i = 0; i < nreg; ++i) { regions[i].remap = regions[i].hasSpans; if (!regions[i].hasSpans) { regions[i].layerId = 0xffff; } } unsigned short maxLayerId = 0; for (int i = 0; i < nreg; ++i) { if (!regions[i].remap) continue; unsigned short oldId = regions[i].layerId; unsigned short newId = maxLayerId; for (int j = i; j < nreg; ++j) { if (regions[j].layerId == oldId) { regions[j].layerId = newId; regions[j].remap = false; } } maxLayerId++; } ctx->stopTimer(RC_TIMER_BUILD_REGIONS_FILTER); if (maxLayerId == 0) { ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; } // Create layers. rcAssert(lset.layers == 0); const int lw = w - borderSize*2; const int lh = h - borderSize*2; // Build contracted bbox for layers. float bmin[3], bmax[3]; rcVcopy(bmin, chf.bmin); rcVcopy(bmax, chf.bmax); bmin[0] += borderSize*chf.cs; bmin[2] += borderSize*chf.cs; bmax[0] -= borderSize*chf.cs; bmax[2] -= borderSize*chf.cs; lset.nlayers = (int)maxLayerId; lset.layers = (rcHeightfieldLayer*)rcAlloc(sizeof(rcHeightfieldLayer)*lset.nlayers, RC_ALLOC_PERM); if (!lset.layers) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'layers' (%d).", lset.nlayers); return false; } memset(lset.layers, 0, sizeof(rcHeightfieldLayer)*lset.nlayers); // Store layers. for (int i = 0; i < lset.nlayers; ++i) { unsigned short curId = (unsigned short)i; // Allocate memory for the current layer. rcHeightfieldLayer* layer = &lset.layers[i]; memset(layer, 0, sizeof(rcHeightfieldLayer)); const int gridSize = sizeof(unsigned char)*lw*lh; const int gridSize2 = sizeof(unsigned short)*lw*lh; layer->heights = (unsigned short*)rcAlloc(gridSize2, RC_ALLOC_PERM); if (!layer->heights) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'heights' (%d).", gridSize2); return false; } memset(layer->heights, 0xff, gridSize2); layer->areas = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->areas) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'areas' (%d).", gridSize); return false; } memset(layer->areas, 0, gridSize); layer->cons = (unsigned char*)rcAlloc(gridSize, RC_ALLOC_PERM); if (!layer->cons) { ctx->log(RC_LOG_ERROR, "rcBuildHeightfieldLayers: Out of memory 'cons' (%d).", gridSize); return false; } memset(layer->cons, 0, gridSize); // Find layer height bounds. int hmin = 0, hmax = 0; for (int j = 0; j < nreg; ++j) { if (regions[j].base && regions[j].layerId == curId) { hmin = (int)regions[j].ymin; hmax = (int)regions[j].ymax; } } layer->width = lw; layer->height = lh; layer->cs = chf.cs; layer->ch = chf.ch; // Adjust the bbox to fit the heighfield. rcVcopy(layer->bmin, bmin); rcVcopy(layer->bmax, bmax); layer->bmin[1] = bmin[1] + hmin*chf.ch; layer->bmax[1] = bmin[1] + hmax*chf.ch; layer->hmin = hmin; layer->hmax = hmax; // Update usable data region. layer->minx = layer->width; layer->maxx = 0; layer->miny = layer->height; layer->maxy = 0; // Copy height and area from compact heighfield. for (int y = 0; y < lh; ++y) { for (int x = 0; x < lw; ++x) { const int cx = borderSize+x; const int cy = borderSize+y; const rcCompactCell& c = chf.cells[cx+cy*w]; for (int j = (int)c.index, nj = (int)(c.index+c.count); j < nj; ++j) { const rcCompactSpan& s = chf.spans[j]; // Skip unassigned regions. if (srcReg[j] == 0 || srcReg[j] >= nreg) continue; // Skip of does not belong to current layer. unsigned short lid = regions[srcReg[j]].layerId; if (lid != curId) continue; // Update data bounds. layer->minx = rcMin(layer->minx, x); layer->maxx = rcMax(layer->maxx, x); layer->miny = rcMin(layer->miny, y); layer->maxy = rcMax(layer->maxy, y); // Store height and area type. const int idx = x+y*lw; layer->heights[idx] = (unsigned short)(s.y - hmin); layer->areas[idx] = chf.areas[j]; // Check connection. unsigned char portal = 0; unsigned char con = 0; for (int dir = 0; dir < 4; ++dir) { if (rcGetCon(s, dir) != RC_NOT_CONNECTED) { const int ax = cx + rcGetDirOffsetX(dir); const int ay = cy + rcGetDirOffsetY(dir); const int ai = (int)chf.cells[ax+ay*w].index + rcGetCon(s, dir); unsigned short alid = (srcReg[ai] < nreg) ? regions[srcReg[ai]].layerId : 0xffff; // Portal mask if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { portal |= (unsigned char)(1<<dir); // Update height so that it matches on both sides of the portal. const rcCompactSpan& as = chf.spans[ai]; if (as.y > hmin) layer->heights[idx] = rcMax(layer->heights[idx], (unsigned short)(as.y - hmin)); } // Valid connection mask if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { const int nx = ax - borderSize; const int ny = ay - borderSize; if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) con |= (unsigned char)(1<<dir); } } } layer->cons[idx] = (portal << 4) | con; } } } if (layer->minx > layer->maxx) layer->minx = layer->maxx = 0; if (layer->miny > layer->maxy) layer->miny = layer->maxy = 0; } ctx->stopTimer(RC_TIMER_BUILD_LAYERS); return true; }
bool OgreDetourTileCache::configure(InputGeom *inputGeom) { m_geom = inputGeom; // Reuse OgreRecast context for tiled navmesh building m_ctx = m_recast->m_ctx; if (!m_geom || m_geom->isEmpty()) { m_recast->m_pLog->logMessage("ERROR: OgreDetourTileCache::configure: No vertices and triangles."); return false; } if (!m_geom->getChunkyMesh()) { m_recast->m_pLog->logMessage("ERROR: OgreDetourTileCache::configure: Input mesh has no chunkyTriMesh built."); return false; } m_tmproc->init(m_geom); // Init cache bounding box const float* bmin = m_geom->getMeshBoundsMin(); const float* bmax = m_geom->getMeshBoundsMax(); // Navmesh generation params. // Use config from recast module m_cfg = m_recast->m_cfg; // Most params are taken from OgreRecast::configure, except for these: m_cfg.tileSize = m_tileSize; m_cfg.borderSize = (int) (m_cfg.walkableRadius + BORDER_PADDING); // Reserve enough padding. m_cfg.width = m_cfg.tileSize + m_cfg.borderSize*2; m_cfg.height = m_cfg.tileSize + m_cfg.borderSize*2; // Set mesh bounds rcVcopy(m_cfg.bmin, bmin); rcVcopy(m_cfg.bmax, bmax); // Also define navmesh bounds in recast component rcVcopy(m_recast->m_cfg.bmin, bmin); rcVcopy(m_recast->m_cfg.bmax, bmax); // Cell size navmesh generation property is copied from OgreRecast config m_cellSize = m_cfg.cs; // Determine grid size (number of tiles) based on bounding box and grid cell size int gw = 0, gh = 0; rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh); // Calculates total size of voxel grid const int ts = m_tileSize; const int tw = (gw + ts-1) / ts; // Tile width const int th = (gh + ts-1) / ts; // Tile height m_tw = tw; m_th = th; Ogre::LogManager::getSingletonPtr()->logMessage("Total Voxels: "+Ogre::StringConverter::toString(gw) + " x " + Ogre::StringConverter::toString(gh)); Ogre::LogManager::getSingletonPtr()->logMessage("Tilesize: "+Ogre::StringConverter::toString(m_tileSize)+" Cellsize: "+Ogre::StringConverter::toString(m_cellSize)); Ogre::LogManager::getSingletonPtr()->logMessage("Tiles: "+Ogre::StringConverter::toString(m_tw)+" x "+Ogre::StringConverter::toString(m_th)); // Max tiles and max polys affect how the tile IDs are caculated. // There are 22 bits available for identifying a tile and a polygon. int tileBits = rcMin((int)dtIlog2(dtNextPow2(tw*th*EXPECTED_LAYERS_PER_TILE)), 14); if (tileBits > 14) tileBits = 14; int polyBits = 22 - tileBits; m_maxTiles = 1 << tileBits; m_maxPolysPerTile = 1 << polyBits; Ogre::LogManager::getSingletonPtr()->logMessage("Max Tiles: " + Ogre::StringConverter::toString(m_maxTiles)); Ogre::LogManager::getSingletonPtr()->logMessage("Max Polys: " + Ogre::StringConverter::toString(m_maxPolysPerTile)); // Tile cache params. memset(&m_tcparams, 0, sizeof(m_tcparams)); rcVcopy(m_tcparams.orig, bmin); m_tcparams.width = m_tileSize; m_tcparams.height = m_tileSize; m_tcparams.maxTiles = tw*th*EXPECTED_LAYERS_PER_TILE; m_tcparams.maxObstacles = MAX_OBSTACLES; // Max number of temp obstacles that can be added to or removed from navmesh // Copy the rest of the parameters from OgreRecast config m_tcparams.cs = m_cfg.cs; m_tcparams.ch = m_cfg.ch; m_tcparams.walkableHeight = (float) m_cfg.walkableHeight; m_tcparams.walkableRadius = (float) m_cfg.walkableRadius; m_tcparams.walkableClimb = (float) m_cfg.walkableClimb; m_tcparams.maxSimplificationError = m_cfg.maxSimplificationError; return initTileCache(); }
/*! Build a NAVIGATION mesh from an OBJ mesh index. Usually this OBJMESH is either a collision map or a mesh that have been built especially for navigation. \param[in,out] navigation A valid NAVIGATION structure pointer. \param[in] obj A valid OBJ structure pointer. \param[in] mesh_index The mesh index of the OBJMESH to use to create the NAVIGATION mesh. \return Return 1 if the NAVIGATION mesh have been generated successfully, else this function will return 0. */ unsigned char NAVIGATION_build( NAVIGATION *navigation, Object *obj, unsigned int mesh_index ) { unsigned int i = 0, j = 0, k = 0, triangle_count = 0; int *indices = NULL; Mesh *objmesh = obj->meshList[ mesh_index ]; // vec3 *vertex_array = ( vec3 * ) malloc( objmesh->n_objvertexdata * sizeof( vec3 ) ), vec3 *vertex_array = ( vec3 * ) malloc( objmesh->uniqueVertexUVIndexList.size() * sizeof( vec3 ) ), *vertex_start = vertex_array; rcHeightfield *rcheightfield; rcCompactHeightfield *rccompactheightfield; rcContourSet *rccontourset; rcPolyMesh *rcpolymesh; rcPolyMeshDetail *rcpolymeshdetail; while( i != objmesh->uniqueVertexUVIndexList.size() ) { memcpy( vertex_array, &obj->vertexMgr.uniqueVertexList[objmesh->uniqueVertexUVIndexList[i].vertexIndex], sizeof( vec3 ) ); *vertex_array = vertex_array->toRecast(); // vec3_to_recast( vertex_array ); ++vertex_array; ++i; } // while( i != objmesh->n_objvertexdata ) // { // memcpy( vertex_array, // &obj->indexed_vertex[ objmesh->objvertexdata[ i ].vertex_index ], // sizeof( vec3 ) ); // // vec3_to_recast( vertex_array ); // // ++vertex_array; // ++i; // } triangle_count += objmesh->vertexIndexList.size(); indices = ( int * ) realloc( indices, triangle_count * sizeof( int ) ); j = 0; while( j != objmesh->vertexIndexList.size() ) { indices[ k ] = objmesh->vertexIndexList[ j ]; ++k; ++j; } // // i = 0; // while( i != objmesh->n_objtrianglelist ) // { // triangle_count += objmesh->objtrianglelist[ i ].n_indice_array; // // indices = ( int * ) realloc( indices, triangle_count * sizeof( int ) ); // // j = 0; // while( j != objmesh->objtrianglelist[ i ].n_indice_array ) // { // indices[ k ] = objmesh->objtrianglelist[ i ].indice_array[ j ]; // // ++k; // ++j; // } // // ++i; // } triangle_count /= 3; rcConfig rcconfig; memset( &rcconfig, 0, sizeof( rcConfig ) ); rcconfig.cs = navigation->navigationconfiguration.cell_size; rcconfig.ch = navigation->navigationconfiguration.cell_height; rcconfig.walkableHeight = ( int )ceilf ( navigation->navigationconfiguration.agent_height / rcconfig.ch ); rcconfig.walkableRadius = ( int )ceilf ( navigation->navigationconfiguration.agent_radius / rcconfig.cs ); rcconfig.walkableClimb = ( int )floorf( navigation->navigationconfiguration.agent_max_climb / rcconfig.ch ); rcconfig.walkableSlopeAngle = navigation->navigationconfiguration.agent_max_slope; rcconfig.minRegionSize = ( int )rcSqr( navigation->navigationconfiguration.region_min_size ); rcconfig.mergeRegionSize = ( int )rcSqr( navigation->navigationconfiguration.region_merge_size ); rcconfig.maxEdgeLen = ( int )( navigation->navigationconfiguration.edge_max_len / rcconfig.cs ); rcconfig.maxSimplificationError = navigation->navigationconfiguration.edge_max_error; rcconfig.maxVertsPerPoly = ( int )navigation->navigationconfiguration.vert_per_poly; rcconfig.detailSampleDist = rcconfig.cs * navigation->navigationconfiguration.detail_sample_dst; rcconfig.detailSampleMaxError = rcconfig.ch * navigation->navigationconfiguration.detail_sample_max_error; rcCalcBounds( ( float * )vertex_start, (int)objmesh->uniqueVertexUVIndexList.size(), // objmesh->n_objvertexdata, rcconfig.bmin, rcconfig.bmax ); rcCalcGridSize( rcconfig.bmin, rcconfig.bmax, rcconfig.cs, &rcconfig.width, &rcconfig.height ); rcheightfield = rcAllocHeightfield(); rcCreateHeightfield( *rcheightfield, rcconfig.width, rcconfig.height, rcconfig.bmin, rcconfig.bmax, rcconfig.cs, rcconfig.ch ); navigation->triangle_flags = new unsigned char[ triangle_count ]; memset( navigation->triangle_flags, 0, triangle_count * sizeof( unsigned char ) ); rcMarkWalkableTriangles( rcconfig.walkableSlopeAngle, ( float * )vertex_start, (int)objmesh->uniqueVertexUVIndexList.size(), // objmesh->n_objvertexdata, indices, triangle_count, navigation->triangle_flags ); rcRasterizeTriangles( ( float * )vertex_start, (int)objmesh->uniqueVertexUVIndexList.size(), // objmesh->n_objvertexdata, indices, navigation->triangle_flags, triangle_count, *rcheightfield, rcconfig.walkableClimb ); delete []navigation->triangle_flags; navigation->triangle_flags = NULL; free( vertex_start ); free( indices ); rcFilterLowHangingWalkableObstacles( rcconfig.walkableClimb, *rcheightfield ); rcFilterLedgeSpans( rcconfig.walkableHeight, rcconfig.walkableClimb, *rcheightfield ); rcFilterWalkableLowHeightSpans( rcconfig.walkableHeight, *rcheightfield ); rccompactheightfield = rcAllocCompactHeightfield(); rcBuildCompactHeightfield( rcconfig.walkableHeight, rcconfig.walkableClimb, RC_WALKABLE, *rcheightfield, *rccompactheightfield ); rcFreeHeightField( rcheightfield ); rcheightfield = NULL; rcErodeArea( RC_WALKABLE_AREA, rcconfig.walkableRadius, *rccompactheightfield ); rcBuildDistanceField( *rccompactheightfield ); rcBuildRegions( *rccompactheightfield, rcconfig.borderSize, rcconfig.minRegionSize, rcconfig.mergeRegionSize ); rccontourset = rcAllocContourSet(); rcBuildContours( *rccompactheightfield, rcconfig.maxSimplificationError, rcconfig.maxEdgeLen, *rccontourset ); rcpolymesh = rcAllocPolyMesh(); rcBuildPolyMesh( *rccontourset, rcconfig.maxVertsPerPoly, *rcpolymesh ); rcpolymeshdetail = rcAllocPolyMeshDetail(); rcBuildPolyMeshDetail( *rcpolymesh, *rccompactheightfield, rcconfig.detailSampleDist, rcconfig.detailSampleMaxError, *rcpolymeshdetail ); rcFreeCompactHeightfield( rccompactheightfield ); rccompactheightfield = NULL; rcFreeContourSet( rccontourset ); rccontourset = NULL; if( rcconfig.maxVertsPerPoly <= DT_VERTS_PER_POLYGON ) { dtNavMeshCreateParams dtnavmeshcreateparams; unsigned char *nav_data = NULL; int nav_data_size = 0; i = 0; while( i != rcpolymesh->npolys ) { if( rcpolymesh->areas[ i ] == RC_WALKABLE_AREA ) { rcpolymesh->areas[ i ] = 0; rcpolymesh->flags[ i ] = 0x01; } ++i; } memset( &dtnavmeshcreateparams, 0, sizeof( dtNavMeshCreateParams ) ); dtnavmeshcreateparams.verts = rcpolymesh->verts; dtnavmeshcreateparams.vertCount = rcpolymesh->nverts; dtnavmeshcreateparams.polys = rcpolymesh->polys; dtnavmeshcreateparams.polyAreas = rcpolymesh->areas; dtnavmeshcreateparams.polyFlags = rcpolymesh->flags; dtnavmeshcreateparams.polyCount = rcpolymesh->npolys; dtnavmeshcreateparams.nvp = rcpolymesh->nvp; dtnavmeshcreateparams.detailMeshes = rcpolymeshdetail->meshes; dtnavmeshcreateparams.detailVerts = rcpolymeshdetail->verts; dtnavmeshcreateparams.detailVertsCount = rcpolymeshdetail->nverts; dtnavmeshcreateparams.detailTris = rcpolymeshdetail->tris; dtnavmeshcreateparams.detailTriCount = rcpolymeshdetail->ntris; dtnavmeshcreateparams.walkableHeight = navigation->navigationconfiguration.agent_height; dtnavmeshcreateparams.walkableRadius = navigation->navigationconfiguration.agent_radius; dtnavmeshcreateparams.walkableClimb = navigation->navigationconfiguration.agent_max_climb; rcVcopy( dtnavmeshcreateparams.bmin, rcpolymesh->bmin ); rcVcopy( dtnavmeshcreateparams.bmax, rcpolymesh->bmax ); dtnavmeshcreateparams.cs = rcconfig.cs; dtnavmeshcreateparams.ch = rcconfig.ch; dtCreateNavMeshData( &dtnavmeshcreateparams, &nav_data, &nav_data_size ); if( !nav_data ) return 0; navigation->dtnavmesh = dtAllocNavMesh(); navigation->dtnavmesh->init( nav_data, nav_data_size, DT_TILE_FREE_DATA, NAVIGATION_MAX_NODE ); rcFreePolyMesh( rcpolymesh ); rcpolymesh = NULL; rcFreePolyMeshDetail( rcpolymeshdetail ); rcpolymeshdetail = NULL; return 1; } return 0; }
uint8* TileBuilder::Build(bool dbg, dtNavMeshParams& navMeshParams) { _Geometry = new Geometry(); _Geometry->Transform = true; ADT* adt = new ADT(Utils::GetAdtPath(World, X, Y)); adt->Read(); _Geometry->AddAdt(adt); delete adt; if (_Geometry->Vertices.empty() && _Geometry->Triangles.empty()) return NULL; // again, we load everything - wasteful but who cares for (int ty = Y - 2; ty <= Y + 2; ty++) { for (int tx = X - 2; tx <= X + 2; tx++) { // don't load main tile again if (tx == X && ty == Y) continue; ADT* _adt = new ADT(Utils::GetAdtPath(World, tx, ty)); // If this condition is met, it means that this wdt does not contain the ADT if (!_adt->Data->Stream) { delete _adt; continue; } _adt->Read(); _Geometry->AddAdt(_adt); delete _adt; } } if (dbg) { char buff[100]; sprintf(buff, "mmaps/%s_%02u%02u.obj", World.c_str(), Y, X); FILE* debug = fopen(buff, "wb"); for (uint32 i = 0; i < _Geometry->Vertices.size(); ++i) fprintf(debug, "v %f %f %f\n", _Geometry->Vertices[i].x, _Geometry->Vertices[i].y, _Geometry->Vertices[i].z); for (uint32 i = 0; i < _Geometry->Triangles.size(); ++i) fprintf(debug, "f %i %i %i\n", _Geometry->Triangles[i].V0 + 1, _Geometry->Triangles[i].V1 + 1, _Geometry->Triangles[i].V2 + 1); fclose(debug); } uint32 numVerts = _Geometry->Vertices.size(); uint32 numTris = _Geometry->Triangles.size(); float* vertices; int* triangles; uint8* areas; _Geometry->GetRawData(vertices, triangles, areas); _Geometry->Vertices.clear(); _Geometry->Triangles.clear(); rcVcopy(Config.bmin, cBuilder->bmin); rcVcopy(Config.bmax, cBuilder->bmax); // this sets the dimensions of the heightfield - should maybe happen before border padding rcCalcGridSize(Config.bmin, Config.bmax, Config.cs, &Config.width, &Config.height); // Initialize per tile config. rcConfig tileCfg = Config; tileCfg.width = Config.tileSize + Config.borderSize * 2; tileCfg.height = Config.tileSize + Config.borderSize * 2; // merge per tile poly and detail meshes rcPolyMesh** pmmerge = new rcPolyMesh*[Constants::TilesPerMap * Constants::TilesPerMap]; rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[Constants::TilesPerMap * Constants::TilesPerMap]; int nmerge = 0; for (int y = 0; y < Constants::TilesPerMap; ++y) { for (int x = 0; x < Constants::TilesPerMap; ++x) { // Calculate the per tile bounding box. tileCfg.bmin[0] = Config.bmin[0] + float(x * Config.tileSize - Config.borderSize) * Config.cs; tileCfg.bmin[2] = Config.bmin[2] + float(y * Config.tileSize - Config.borderSize) * Config.cs; tileCfg.bmax[0] = Config.bmin[0] + float((x + 1) * Config.tileSize + Config.borderSize) * Config.cs; tileCfg.bmax[2] = Config.bmin[2] + float((y + 1) * Config.tileSize + Config.borderSize) * Config.cs; rcHeightfield* hf = rcAllocHeightfield(); rcCreateHeightfield(Context, *hf, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch); rcClearUnwalkableTriangles(Context, tileCfg.walkableSlopeAngle, vertices, numVerts, triangles, numTris, areas); rcRasterizeTriangles(Context, vertices, numVerts, triangles, areas, numTris, *hf, Config.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(Context, Config.walkableClimb, *hf); rcFilterLedgeSpans(Context, tileCfg.walkableHeight, tileCfg.walkableClimb, *hf); rcFilterWalkableLowHeightSpans(Context, tileCfg.walkableHeight, *hf); // Compact the heightfield so that it is faster to handle from now on. // This will result in more cache coherent data as well as the neighbours // between walkable cells will be calculated. rcCompactHeightfield* chf = rcAllocCompactHeightfield(); rcBuildCompactHeightfield(Context, tileCfg.walkableHeight, tileCfg.walkableClimb, *hf, *chf); rcFreeHeightField(hf); // Erode the walkable area by agent radius. rcErodeWalkableArea(Context, Config.walkableRadius, *chf); // Prepare for region partitioning, by calculating distance field along the walkable surface. rcBuildDistanceField(Context, *chf); // Partition the walkable surface into simple regions without holes. rcBuildRegions(Context, *chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea); // Create contours. rcContourSet* cset = rcAllocContourSet(); rcBuildContours(Context, *chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *cset); // Build polygon navmesh from the contours. rcPolyMesh* pmesh = rcAllocPolyMesh(); rcBuildPolyMesh(Context, *cset, tileCfg.maxVertsPerPoly, *pmesh); // Build detail mesh. rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail(); rcBuildPolyMeshDetail(Context, *pmesh, *chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *dmesh); // Free memory rcFreeCompactHeightfield(chf); rcFreeContourSet(cset); pmmerge[nmerge] = pmesh; dmmerge[nmerge] = dmesh; ++nmerge; } } rcPolyMesh* pmesh = rcAllocPolyMesh(); rcMergePolyMeshes(Context, pmmerge, nmerge, *pmesh); rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail(); rcMergePolyMeshDetails(Context, dmmerge, nmerge, *dmesh); delete[] pmmerge; delete[] dmmerge; printf("[%02i,%02i] Meshes merged!\n", X, Y); // Remove padding from the polymesh data. (Remove this odditity) for (int i = 0; i < pmesh->nverts; ++i) { unsigned short* v = &pmesh->verts[i * 3]; v[0] -= (unsigned short)Config.borderSize; v[2] -= (unsigned short)Config.borderSize; } // Set flags according to area types (e.g. Swim for Water) for (int i = 0; i < pmesh->npolys; i++) { if (pmesh->areas[i] == Constants::POLY_AREA_ROAD || pmesh->areas[i] == Constants::POLY_AREA_TERRAIN) pmesh->flags[i] = Constants::POLY_FLAG_WALK; else if (pmesh->areas[i] == Constants::POLY_AREA_WATER) pmesh->flags[i] = Constants::POLY_FLAG_SWIM; } dtNavMeshCreateParams params; memset(¶ms, 0, sizeof(params)); // PolyMesh data params.verts = pmesh->verts; params.vertCount = pmesh->nverts; params.polys = pmesh->polys; params.polyAreas = pmesh->areas; params.polyFlags = pmesh->flags; params.polyCount = pmesh->npolys; params.nvp = pmesh->nvp; // PolyMeshDetail data params.detailMeshes = dmesh->meshes; params.detailVerts = dmesh->verts; params.detailVertsCount = dmesh->nverts; params.detailTris = dmesh->tris; params.detailTriCount = dmesh->ntris; rcVcopy(params.bmin, pmesh->bmin); rcVcopy(params.bmax, pmesh->bmax); // General settings params.ch = Config.ch; params.cs = Config.cs; params.walkableClimb = Constants::BaseUnitDim * Config.walkableClimb; params.walkableHeight = Constants::BaseUnitDim * Config.walkableHeight; params.walkableRadius = Constants::BaseUnitDim * Config.walkableRadius; params.tileX = (((cBuilder->bmin[0] + cBuilder->bmax[0]) / 2) - navMeshParams.orig[0]) / Constants::TileSize; params.tileY = (((cBuilder->bmin[2] + cBuilder->bmax[2]) / 2) - navMeshParams.orig[2]) / Constants::TileSize; rcVcopy(params.bmin, cBuilder->bmin); rcVcopy(params.bmax, cBuilder->bmax); // Offmesh-connection settings params.offMeshConCount = 0; // none for now params.tileSize = Constants::VertexPerMap; if (!params.polyCount || !params.polys || Constants::TilesPerMap * Constants::TilesPerMap == params.polyCount) { // we have flat tiles with no actual geometry - don't build those, its useless // keep in mind that we do output those into debug info // drop tiles with only exact count - some tiles may have geometry while having less tiles printf("[%02i,%02i] No polygons to build on tile, skipping.\n", X, Y); rcFreePolyMesh(pmesh); rcFreePolyMeshDetail(dmesh); delete areas; delete triangles; delete vertices; return NULL; } int navDataSize; uint8* navData; printf("[%02i,%02i] Creating the navmesh with %i vertices, %i polys, %i triangles!\n", X, Y, pmesh->nverts, pmesh->npolys, dmesh->ntris); bool result = dtCreateNavMeshData(¶ms, &navData, &navDataSize); // Free some memory rcFreePolyMesh(pmesh); rcFreePolyMeshDetail(dmesh); delete areas; delete triangles; delete vertices; if (result) { printf("[%02i,%02i] NavMesh created, size %i!\n", X, Y, navDataSize); DataSize = navDataSize; return navData; } return NULL; }
bool NavMesh::BuildMesh() { dtStatus status; if (!m_geom || !m_geom->getMesh()) return false; m_tmproc->init(m_geom); // Init cache const float* bmin = m_geom->getMeshBoundsMin(); const float* bmax = m_geom->getMeshBoundsMax(); int gw = 0, gh = 0; rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh); const int ts = (int)m_tileSize; const int tw = (gw + ts-1) / ts; const int th = (gh + ts-1) / ts; // Generation params. rcConfig cfg; memset(&cfg, 0, sizeof(cfg)); cfg.cs = m_cellSize; cfg.ch = m_cellHeight; cfg.walkableSlopeAngle = m_agentMaxSlope; cfg.walkableHeight = (int)ceilf(m_agentHeight / cfg.ch); cfg.walkableClimb = (int)floorf(m_agentMaxClimb / cfg.ch); cfg.walkableRadius = (int)ceilf(m_agentRadius / cfg.cs); cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); cfg.maxSimplificationError = m_edgeMaxError; cfg.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size cfg.maxVertsPerPoly = (int)m_vertsPerPoly; cfg.tileSize = (int)m_tileSize; cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding. cfg.width = cfg.tileSize + cfg.borderSize*2; cfg.height = cfg.tileSize + cfg.borderSize*2; cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); // Tile cache params. dtTileCacheParams tcparams; memset(&tcparams, 0, sizeof(tcparams)); rcVcopy(tcparams.orig, bmin); tcparams.cs = m_cellSize; tcparams.ch = m_cellHeight; tcparams.width = (int)m_tileSize; tcparams.height = (int)m_tileSize; tcparams.walkableHeight = m_agentHeight; tcparams.walkableRadius = m_agentRadius; tcparams.walkableClimb = m_agentMaxClimb; tcparams.maxSimplificationError = m_edgeMaxError; tcparams.maxTiles = tw*th*EXPECTED_LAYERS_PER_TILE; tcparams.maxObstacles = 128; dtFreeTileCache(m_tileCache); m_tileCache = dtAllocTileCache(); if (!m_tileCache) return false; status = m_tileCache->init(&tcparams, m_talloc, m_tcomp, m_tmproc); if (dtStatusFailed(status)) return false; dtFreeNavMesh(m_navMesh); m_navMesh = dtAllocNavMesh(); if (!m_navMesh) return false; dtNavMeshParams params; memset(¶ms, 0, sizeof(params)); rcVcopy(params.orig, m_geom->getMeshBoundsMin()); params.tileWidth = m_tileSize*m_cellSize; params.tileHeight = m_tileSize*m_cellSize; params.maxTiles = m_maxTiles; params.maxPolys = m_maxPolysPerTile; status = m_navMesh->init(¶ms); if (dtStatusFailed(status)) return false; status = m_navQuery->init(m_navMesh, 2048); if (dtStatusFailed(status)) return false; for (int y = 0; y < th; ++y) { for (int x = 0; x < tw; ++x) { TileCacheData tiles[MAX_LAYERS]; memset(tiles, 0, sizeof(tiles)); int n = rasterizeTileLayers(m_geom, x, y, cfg, tiles, MAX_LAYERS); for (int i = 0; i < n; ++i) { TileCacheData* tile = &tiles[i]; status = m_tileCache->addTile(tile->data, tile->dataSize, DT_COMPRESSEDTILE_FREE_DATA, 0); if (dtStatusFailed(status)) { dtFree(tile->data); tile->data = 0; continue; } } } } for (int y = 0; y < th; ++y) for (int x = 0; x < tw; ++x) m_tileCache->buildNavMeshTilesAt(x,y, m_navMesh); }
bool rcMergePolyMeshDetails(rcPolyMeshDetail** meshes, const int nmeshes, rcPolyMeshDetail& mesh) { rcTimeVal startTime = rcGetPerformanceTimer(); int maxVerts = 0; int maxTris = 0; int maxMeshes = 0; for (int i = 0; i < nmeshes; ++i) { if (!meshes[i]) continue; maxVerts += meshes[i]->nverts; maxTris += meshes[i]->ntris; maxMeshes += meshes[i]->nmeshes; } mesh.nmeshes = 0; mesh.meshes = (unsigned short*)rcAlloc(sizeof(unsigned short)*maxMeshes*4, RC_ALLOC_PERM); if (!mesh.meshes) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'pmdtl.meshes' (%d).", maxMeshes*4); return false; } mesh.ntris = 0; mesh.tris = (unsigned char*)rcAlloc(sizeof(unsigned char)*maxTris*4, RC_ALLOC_PERM); if (!mesh.tris) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.tris' (%d).", maxTris*4); return false; } mesh.nverts = 0; mesh.verts = (float*)rcAlloc(sizeof(float)*maxVerts*3, RC_ALLOC_PERM); if (!mesh.verts) { if (rcGetLog()) rcGetLog()->log(RC_LOG_ERROR, "rcBuildPolyMeshDetail: Out of memory 'dmesh.verts' (%d).", maxVerts*3); return false; } // Merge datas. for (int i = 0; i < nmeshes; ++i) { rcPolyMeshDetail* dm = meshes[i]; if (!dm) continue; for (int j = 0; j < dm->nmeshes; ++j) { unsigned short* dst = &mesh.meshes[mesh.nmeshes*4]; unsigned short* src = &dm->meshes[j*4]; dst[0] = (unsigned short)mesh.nverts+src[0]; dst[1] = src[1]; dst[2] = (unsigned short)mesh.ntris+src[2]; dst[3] = src[3]; mesh.nmeshes++; } for (int k = 0; k < dm->nverts; ++k) { rcVcopy(&mesh.verts[mesh.nverts*3], &dm->verts[k*3]); mesh.nverts++; } for (int k = 0; k < dm->ntris; ++k) { mesh.tris[mesh.ntris*4+0] = dm->tris[k*4+0]; mesh.tris[mesh.ntris*4+1] = dm->tris[k*4+1]; mesh.tris[mesh.ntris*4+2] = dm->tris[k*4+2]; mesh.tris[mesh.ntris*4+3] = dm->tris[k*4+3]; mesh.ntris++; } } rcTimeVal endTime = rcGetPerformanceTimer(); if (rcGetBuildTimes()) rcGetBuildTimes()->mergePolyMeshDetail += rcGetDeltaTimeUsec(startTime, endTime); return true; }
void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh) { set<uint32>* tiles = getTileList(mapID); // old code for non-statically assigned bitmask sizes: ///*** calculate number of bits needed to store tiles & polys ***/ //int tileBits = dtIlog2(dtNextPow2(tiles->size())); //if (tileBits < 1) tileBits = 1; // need at least one bit! //int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits; int tileBits = STATIC_TILE_BITS; int polyBits = STATIC_POLY_BITS; int maxTiles = tiles->size(); int maxPolysPerTile = 1 << polyBits; /*** calculate bounds of map ***/ uint32 tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0, tileX, tileY; for (set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it) { StaticMapTree::unpackTileID((*it), tileX, tileY); if (tileX > tileXMax) tileXMax = tileX; else if (tileX < tileXMin) tileXMin = tileX; if (tileY > tileYMax) tileYMax = tileY; else if (tileY < tileYMin) tileYMin = tileY; } // use Max because '32 - tileX' is negative for values over 32 float bmin[3], bmax[3]; getTileBounds(tileXMax, tileYMax, NULL, 0, bmin, bmax); /*** now create the navmesh ***/ // navmesh creation params dtNavMeshParams navMeshParams; memset(&navMeshParams, 0, sizeof(dtNavMeshParams)); navMeshParams.tileWidth = GRID_SIZE; navMeshParams.tileHeight = GRID_SIZE; rcVcopy(navMeshParams.orig, bmin); navMeshParams.maxTiles = maxTiles; navMeshParams.maxPolys = maxPolysPerTile; navMesh = dtAllocNavMesh(); printf("Creating navMesh... \r"); if (!navMesh->init(&navMeshParams)) { printf("Failed creating navmesh! \n"); return; } char fileName[25]; sprintf(fileName, "mmaps/%03u.mmap", mapID); FILE* file = fopen(fileName, "wb"); if (!file) { dtFreeNavMesh(navMesh); char message[1024]; sprintf(message, "Failed to open %s for writing!\n", fileName); perror(message); return; } // now that we know navMesh params are valid, we can write them to file fwrite(&navMeshParams, sizeof(dtNavMeshParams), 1, file); fclose(file); }
void ContinentBuilder::Build() { char buff[50]; sprintf(buff, "mmaps/%03u.mmap", MapId); FILE* mmap = fopen(buff, "wb"); if (!mmap) { printf("Could not create file %s. Check that you have write permissions to the destination folder and try again\n", buff); return; } CalculateTileBounds(); dtNavMeshParams params; std::vector<BuilderThread*> Threads; if (TileMap->IsGlobalModel) { printf("Map %s ( %u ) is a WMO. Building with 1 thread.\n", Continent.c_str(), MapId); TileBuilder* builder = new TileBuilder(this, Continent, 0, 0, MapId); builder->AddGeometry(TileMap->Model, TileMap->ModelDefinition); uint8* nav = builder->BuildInstance(params); if (nav) { // Set some params for the navmesh dtMeshHeader* header = (dtMeshHeader*)nav; dtVcopy(params.orig, header->bmin); params.tileWidth = header->bmax[0] - header->bmin[0]; params.tileHeight = header->bmax[2] - header->bmin[2]; params.maxTiles = 1; params.maxPolys = header->polyCount; fwrite(¶ms, sizeof(dtNavMeshParams), 1, mmap); fclose(mmap); char buff[100]; sprintf(buff, "mmaps/%03u%02i%02i.mmtile", MapId, 0, 0); FILE* f = fopen(buff, "wb"); if (!f) { printf("Could not create file %s. Check that you have write permissions to the destination folder and try again\n", buff); return; } MmapTileHeader mheader; mheader.size = builder->DataSize; fwrite(&mheader, sizeof(MmapTileHeader), 1, f); fwrite(nav, sizeof(unsigned char), builder->DataSize, f); fclose(f); } dtFree(nav); delete builder; } else { params.maxPolys = 32768; params.maxTiles = 4096; rcVcopy(params.orig, Constants::Origin); params.tileHeight = Constants::TileSize; params.tileWidth = Constants::TileSize; fwrite(¶ms, sizeof(dtNavMeshParams), 1, mmap); fclose(mmap); for (uint32 i = 0; i < NumberOfThreads; ++i) Threads.push_back(new BuilderThread(this, params)); printf("Map %s ( %u ) has %u tiles. Building them with %u threads\n", Continent.c_str(), MapId, uint32(TileMap->TileTable.size()), NumberOfThreads); for (std::vector<TilePos>::iterator itr = TileMap->TileTable.begin(); itr != TileMap->TileTable.end(); ++itr) { bool next = false; while (!next) { for (std::vector<BuilderThread*>::iterator _th = Threads.begin(); _th != Threads.end(); ++_th) { if ((*_th)->Free) { (*_th)->SetData(itr->X, itr->Y, MapId, Continent); (*_th)->activate(); next = true; break; } } // Wait for 20 seconds ACE_OS::sleep(ACE_Time_Value (0, 20000)); } } } Cache->Clear(); // Free memory for (std::vector<BuilderThread*>::iterator _th = Threads.begin(); _th != Threads.end(); ++_th) { (*_th)->wait(); delete *_th; } }
void MapBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, float bmin[3], float bmax[3], dtNavMesh* navMesh) { // console output char tileString[10]; sprintf(tileString, "[%02i,%02i]: ", tileX, tileY); printf("%s Building movemap tiles... \r", tileString); IntermediateValues iv; float* tVerts = meshData.solidVerts.getCArray(); int tVertCount = meshData.solidVerts.size() / 3; int* tTris = meshData.solidTris.getCArray(); int tTriCount = meshData.solidTris.size() / 3; float* lVerts = meshData.liquidVerts.getCArray(); int lVertCount = meshData.liquidVerts.size() / 3; int* lTris = meshData.liquidTris.getCArray(); int lTriCount = meshData.liquidTris.size() / 3; uint8* lTriFlags = meshData.liquidType.getCArray(); // these are WORLD UNIT based metrics // this are basic unit dimentions // value have to divide GRID_SIZE(533.33333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc ) const static float BASE_UNIT_DIM = m_bigBaseUnit ? 0.533333f : 0.266666f; // All are in UNIT metrics! const static int VERTEX_PER_MAP = int(GRID_SIZE/BASE_UNIT_DIM + 0.5f); const static int VERTEX_PER_TILE = m_bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP const static int TILES_PER_MAP = VERTEX_PER_MAP/VERTEX_PER_TILE; rcConfig config; memset(&config, 0, sizeof(rcConfig)); rcVcopy(config.bmin, bmin); rcVcopy(config.bmax, bmax); config.maxVertsPerPoly = DT_VERTS_PER_POLYGON; config.cs = BASE_UNIT_DIM; config.ch = BASE_UNIT_DIM; config.walkableSlopeAngle = m_maxWalkableAngle; config.tileSize = VERTEX_PER_TILE; config.walkableRadius = m_bigBaseUnit ? 1 : 2; config.borderSize = config.walkableRadius + 3; config.maxEdgeLen = VERTEX_PER_TILE + 1; //anything bigger than tileSize config.walkableHeight = m_bigBaseUnit ? 3 : 6; config.walkableClimb = m_bigBaseUnit ? 2 : 4; // keep less than walkableHeight config.minRegionArea = rcSqr(60); config.mergeRegionArea = rcSqr(50); config.maxSimplificationError = 2.0f; // eliminates most jagged edges (tinny polygons) config.detailSampleDist = config.cs * 64; config.detailSampleMaxError = config.ch * 2; // this sets the dimensions of the heightfield - should maybe happen before border padding rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height); // allocate subregions : tiles Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP]; // Initialize per tile config. rcConfig tileCfg; memcpy(&tileCfg, &config, sizeof(rcConfig)); tileCfg.width = config.tileSize + config.borderSize*2; tileCfg.height = config.tileSize + config.borderSize*2; // build all tiles for (int y = 0; y < TILES_PER_MAP; ++y) { for (int x = 0; x < TILES_PER_MAP; ++x) { Tile& tile = tiles[x + y*TILES_PER_MAP]; // Calculate the per tile bounding box. tileCfg.bmin[0] = config.bmin[0] + (x*config.tileSize - config.borderSize)*config.cs; tileCfg.bmin[2] = config.bmin[2] + (y*config.tileSize - config.borderSize)*config.cs; tileCfg.bmax[0] = config.bmin[0] + ((x+1)*config.tileSize + config.borderSize)*config.cs; tileCfg.bmax[2] = config.bmin[2] + ((y+1)*config.tileSize + config.borderSize)*config.cs; float tbmin[2], tbmax[2]; tbmin[0] = tileCfg.bmin[0]; tbmin[1] = tileCfg.bmin[2]; tbmax[0] = tileCfg.bmax[0]; tbmax[1] = tileCfg.bmax[2]; // build heightfield tile.solid = rcAllocHeightfield(); if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch)) { printf("%sFailed building heightfield! \n", tileString); continue; } // mark all walkable tiles, both liquids and solids unsigned char* triFlags = new unsigned char[tTriCount]; memset(triFlags, NAV_GROUND, tTriCount*sizeof(unsigned char)); rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb); delete [] triFlags; rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid); rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid); rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid); rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb); // compact heightfield spans tile.chf = rcAllocCompactHeightfield(); if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf)) { printf("%sFailed compacting heightfield! \n", tileString); continue; } // build polymesh intermediates if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf)) { printf("%sFailed eroding area! \n", tileString); continue; } if (!rcBuildDistanceField(m_rcContext, *tile.chf)) { printf("%sFailed building distance field! \n", tileString); continue; } if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea)) { printf("%sFailed building regions! \n", tileString); continue; } tile.cset = rcAllocContourSet(); if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset)) { printf("%sFailed building contours! \n", tileString); continue; } // build polymesh tile.pmesh = rcAllocPolyMesh(); if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh)) { printf("%sFailed building polymesh! \n", tileString); continue; } tile.dmesh = rcAllocPolyMeshDetail(); if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg .detailSampleMaxError, *tile.dmesh)) { printf("%sFailed building polymesh detail! \n", tileString); continue; } // free those up // we may want to keep them in the future for debug // but right now, we don't have the code to merge them rcFreeHeightField(tile.solid); tile.solid = NULL; rcFreeCompactHeightfield(tile.chf); tile.chf = NULL; rcFreeContourSet(tile.cset); tile.cset = NULL; } } // merge per tile poly and detail meshes rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP]; if (!pmmerge) { printf("%s alloc pmmerge FIALED! \r", tileString); return; } rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP]; if (!dmmerge) { printf("%s alloc dmmerge FIALED! \r", tileString); return; } int nmerge = 0; for (int y = 0; y < TILES_PER_MAP; ++y) { for (int x = 0; x < TILES_PER_MAP; ++x) { Tile& tile = tiles[x + y*TILES_PER_MAP]; if (tile.pmesh) { pmmerge[nmerge] = tile.pmesh; dmmerge[nmerge] = tile.dmesh; nmerge++; } } } iv.polyMesh = rcAllocPolyMesh(); if (!iv.polyMesh) { printf("%s alloc iv.polyMesh FIALED! \r", tileString); return; } rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh); iv.polyMeshDetail = rcAllocPolyMeshDetail(); if (!iv.polyMeshDetail) { printf("%s alloc m_dmesh FIALED! \r", tileString); return; } rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail); // free things up delete [] pmmerge; delete [] dmmerge; delete [] tiles; // remove padding for extraction for (int i = 0; i < iv.polyMesh->nverts; ++i) { unsigned short* v = &iv.polyMesh->verts[i*3]; v[0] -= (unsigned short)config.borderSize; v[2] -= (unsigned short)config.borderSize; } // set polygons as walkable // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off for (int i = 0; i < iv.polyMesh->npolys; ++i) if (iv.polyMesh->areas[i] & RC_WALKABLE_AREA) iv.polyMesh->flags[i] = iv.polyMesh->areas[i]; // setup mesh parameters dtNavMeshCreateParams params; memset(¶ms, 0, sizeof(params)); params.verts = iv.polyMesh->verts; params.vertCount = iv.polyMesh->nverts; params.polys = iv.polyMesh->polys; params.polyAreas = iv.polyMesh->areas; params.polyFlags = iv.polyMesh->flags; params.polyCount = iv.polyMesh->npolys; params.nvp = iv.polyMesh->nvp; params.detailMeshes = iv.polyMeshDetail->meshes; params.detailVerts = iv.polyMeshDetail->verts; params.detailVertsCount = iv.polyMeshDetail->nverts; params.detailTris = iv.polyMeshDetail->tris; params.detailTriCount = iv.polyMeshDetail->ntris; params.offMeshConVerts = meshData.offMeshConnections.getCArray(); params.offMeshConCount = meshData.offMeshConnections.size()/6; params.offMeshConRad = meshData.offMeshConnectionRads.getCArray(); params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray(); params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray(); params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray(); params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)! params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE; params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE; rcVcopy(params.bmin, bmin); rcVcopy(params.bmax, bmax); params.cs = config.cs; params.ch = config.ch; params.tileSize = VERTEX_PER_MAP; // will hold final navmesh unsigned char* navData = NULL; int navDataSize = 0; do { // these values are checked within dtCreateNavMeshData - handle them here // so we have a clear error message if (params.nvp > DT_VERTS_PER_POLYGON) { printf("%s Invalid verts-per-polygon value! \n", tileString); continue; } if (params.vertCount >= 0xffff) { printf("%s Too many vertices! \n", tileString); continue; } if (!params.vertCount || !params.verts) { // occurs mostly when adjacent tiles have models // loaded but those models don't span into this tile // message is an annoyance //printf("%sNo vertices to build tile! \n", tileString); continue; } if (!params.polyCount || !params.polys || TILES_PER_MAP*TILES_PER_MAP == params.polyCount) { // we have flat tiles with no actual geometry - don't build those, its useless // keep in mind that we do output those into debug info // drop tiles with only exact count - some tiles may have geometry while having less tiles printf("%s No polygons to build on tile! \n", tileString); continue; } if (!params.detailMeshes || !params.detailVerts || !params.detailTris) { printf("%s No detail mesh to build tile! \n", tileString); continue; } printf("%s Building navmesh tile... \r", tileString); if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { printf("%s Failed building navmesh tile! \n", tileString); continue; } dtTileRef tileRef = 0; printf("%s Adding tile to navmesh... \r", tileString); // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile // is removed via removeTile() dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef); if (!tileRef || dtResult != DT_SUCCESS) { printf("%s Failed adding tile to navmesh! \n", tileString); continue; } // file output char fileName[255]; sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX); FILE* file = fopen(fileName, "wb"); if (!file) { char message[1024]; sprintf(message, "Failed to open %s for writing!\n", fileName); perror(message); navMesh->removeTile(tileRef, NULL, NULL); continue; } printf("%s Writing to file... \r", tileString); // write header MmapTileHeader header; header.usesLiquids = m_terrainBuilder->usesLiquids(); header.size = uint32(navDataSize); fwrite(&header, sizeof(MmapTileHeader), 1, file); // write data fwrite(navData, sizeof(unsigned char), navDataSize, file); fclose(file); // now that tile is written to disk, we can unload it navMesh->removeTile(tileRef, NULL, NULL); } while (0); if (m_debugOutput) { // restore padding so that the debug visualization is correct for (int i = 0; i < iv.polyMesh->nverts; ++i) { unsigned short* v = &iv.polyMesh->verts[i*3]; v[0] += (unsigned short)config.borderSize; v[2] += (unsigned short)config.borderSize; } iv.generateObjFile(mapID, tileX, tileY, meshData); iv.writeIV(mapID, tileX, tileY); } }
unsigned char* buildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize, InputGeom* geom, rcConfig cfg, rcContext* ctx) { const float* verts = geom->getMesh()->getVerts(); const int nverts = geom->getMesh()->getVertCount(); const int ntris = geom->getMesh()->getTriCount(); const rcChunkyTriMesh* chunkyMesh = geom->getChunkyMesh(); rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); cfg.bmin[0] -= cfg.borderSize*cfg.cs; cfg.bmin[2] -= cfg.borderSize*cfg.cs; cfg.bmax[0] += cfg.borderSize*cfg.cs; cfg.bmax[2] += cfg.borderSize*cfg.cs; // Reset build times gathering. ctx->resetTimers(); // Start the build process. ctx->startTimer(RC_TIMER_TOTAL); ctx->log(RC_LOG_PROGRESS, "Building navigation:"); ctx->log(RC_LOG_PROGRESS, " - %d x %d cells", cfg.width, cfg.height); ctx->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", nverts/1000.0f, ntris/1000.0f); // all involved objects rcHeightfield* m_solid = 0; unsigned char* m_triareas = 0; rcCompactHeightfield* m_chf = 0; rcContourSet* m_cset = 0; rcPolyMesh* m_pmesh = 0; rcPolyMeshDetail* m_dmesh = 0; // Allocate voxel heightfield where we rasterize our input data to. m_solid = rcAllocHeightfield(); if (!m_solid) { ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); return 0; } if (!rcCreateHeightfield(ctx, *m_solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)) { CleanupAfterTileBuild(); 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. m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; if (!m_triareas) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk); return 0; } float tbmin[2], tbmax[2]; tbmin[0] = cfg.bmin[0]; tbmin[1] = cfg.bmin[2]; tbmax[0] = cfg.bmax[0]; tbmax[1] = cfg.bmax[2]; int cid[512];// TODO: Make grow when returning too many items. const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); if (!ncid) { CleanupAfterTileBuild(); return 0; } 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, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, m_triareas); rcRasterizeTriangles(ctx, verts, nverts, tris, m_triareas, ntris, *m_solid, cfg.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. // Domi edit: Do not filter any triangles #ifndef DOMI_EDIT rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *m_solid); rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *m_solid); #endif // 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 = rcAllocCompactHeightfield(); if (!m_chf) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); return 0; } if (!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, *m_solid, *m_chf)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); return 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(ctx, cfg.walkableRadius, *m_chf)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); return 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, *m_chf); if (0) // m_monotonePartitioning { // Partition the walkable surface into simple regions without holes. if (!rcBuildRegionsMonotone(ctx, *m_chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return 0; } } else { // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(ctx, *m_chf)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); return 0; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(ctx, *m_chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return 0; } } // Create contours. m_cset = rcAllocContourSet(); if (!m_cset) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); return 0; } if (!rcBuildContours(ctx, *m_chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *m_cset)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); return 0; } if (m_cset->nconts == 0) { CleanupAfterTileBuild(); return 0; } // Build polygon navmesh from the contours. m_pmesh = rcAllocPolyMesh(); if (!m_pmesh) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); return 0; } if (!rcBuildPolyMesh(ctx, *m_cset, cfg.maxVertsPerPoly, *m_pmesh)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); return 0; } // Build detail mesh. m_dmesh = rcAllocPolyMeshDetail(); if (!m_dmesh) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); return 0; } if (!rcBuildPolyMeshDetail(ctx, *m_pmesh, *m_chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *m_dmesh)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); return 0; } unsigned char* navData = 0; int navDataSize = 0; if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { if (m_pmesh->nverts >= 0xffff) { CleanupAfterTileBuild(); // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. ctx->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); return 0; } // 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 = (float)cfg.walkableHeight; params.walkableRadius = (float)cfg.walkableRadius; params.walkableClimb = (float)cfg.walkableClimb; params.tileX = tx; params.tileY = ty; params.tileLayer = 0; rcVcopy(params.bmin, m_pmesh->bmin); rcVcopy(params.bmax, m_pmesh->bmax); params.cs = cfg.cs; params.ch = cfg.ch; params.buildBvTree = true; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { CleanupAfterTileBuild(); ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); return 0; } } ctx->stopTimer(RC_TIMER_TOTAL); // Show performance stats. ctx->log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", m_pmesh->nverts, m_pmesh->npolys); dataSize = navDataSize; CleanupAfterTileBuild(); return navData; }
unsigned char* Sample_TileMesh::buildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize) { if (!m_geom || !m_geom->getMesh() || !m_geom->getChunkyMesh()) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified."); return 0; } m_tileMemUsage = 0; m_tileBuildTime = 0; cleanup(); const float* verts = m_geom->getMesh()->getVerts(); const int nverts = m_geom->getMesh()->getVertCount(); const int ntris = m_geom->getMesh()->getTriCount(); const rcChunkyTriMesh* chunkyMesh = m_geom->getChunkyMesh(); // 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; // Expand the heighfield bounding box by border size to find the extents of geometry we need to build this tile. // // This is done in order to make sure that the navmesh tiles connect correctly at the borders, // and the obstacles close to the border work correctly with the dilation process. // No polygons (or contours) will be created on the border area. // // IMPORTANT! // // :''''''''': // : +-----+ : // : | | : // : | |<--- tile to build // : | | : // : +-----+ :<-- geometry needed // :.........: // // You should use this bounding box to query your input geometry. // // For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size // you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8 neighbours, // or use the bounding box below to only pass in a sliver of each of the 8 neighbours. 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; // Reset build times gathering. m_ctx->resetTimers(); // Start the build process. m_ctx->startTimer(RC_TIMER_TOTAL); m_ctx->log(RC_LOG_PROGRESS, "Building navigation:"); m_ctx->log(RC_LOG_PROGRESS, " - %d x %d cells", m_cfg.width, m_cfg.height); m_ctx->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", nverts/1000.0f, ntris/1000.0f); // Allocate voxel heightfield where we rasterize our input data to. m_solid = rcAllocHeightfield(); if (!m_solid) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); return 0; } if (!rcCreateHeightfield(m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { m_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. m_triareas = new AreaType[chunkyMesh->maxTrisPerChunk]; if (!m_triareas) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", 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[512];// TODO: Make grow when returning too many items. const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); if (!ncid) return 0; m_tileTriCount = 0; for (int i = 0; i < ncid; ++i) { const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; const int* ctris = &chunkyMesh->tris[node.i*3]; const int nctris = node.n; m_tileTriCount += nctris; memset(m_triareas, 0, nctris*sizeof(unsigned char)); rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, ctris, nctris, m_triareas); if (!rcRasterizeTriangles(m_ctx, verts, nverts, ctris, m_triareas, nctris, *m_solid, m_cfg.walkableClimb)) return 0; } 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(m_ctx, m_cfg.walkableClimb, *m_solid); rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(m_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. m_chf = rcAllocCompactHeightfield(); if (!m_chf) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); return 0; } if (!rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); return 0; } if (!m_keepInterResults) { rcFreeHeightField(m_solid); m_solid = 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); return 0; } // (Optional) Mark areas. const ConvexVolume* vols = m_geom->getConvexVolumes(); for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); // Partition the heightfield so that we can use simple algorithm later to triangulate the walkable areas. // There are 3 martitioning methods, each with some pros and cons: // 1) Watershed partitioning // - the classic Recast partitioning // - creates the nicest tessellation // - usually slowest // - partitions the heightfield into nice regions without holes or overlaps // - the are some corner cases where this method creates produces holes and overlaps // - holes may appear when a small obstacles is close to large open area (triangulation can handle this) // - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail // * generally the best choice if you precompute the nacmesh, use this if you have large open areas // 2) Monotone partioning // - fastest // - partitions the heightfield into regions without holes and overlaps (guaranteed) // - creates long thin polygons, which sometimes causes paths with detours // * use this if you want fast navmesh generation // 3) Layer partitoining // - quite fast // - partitions the heighfield into non-overlapping regions // - relies on the triangulation code to cope with holes (thus slower than monotone partitioning) // - produces better triangles than monotone partitioning // - does not have the corner cases of watershed partitioning // - can be slow and create a bit ugly tessellation (still better than monotone) // if you have large open areas with small obstacles (not a problem if you use tiles) // * good choice to use for tiled navmesh with medium and small sized tiles if (m_partitionType == SAMPLE_PARTITION_WATERSHED) { // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(m_ctx, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); return 0; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions."); return 0; } } else if (m_partitionType == SAMPLE_PARTITION_MONOTONE) { // Partition the walkable surface into simple regions without holes. // Monotone partitioning does not need distancefield. if (!rcBuildRegionsMonotone(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions."); return 0; } } else // SAMPLE_PARTITION_LAYERS { // Partition the walkable surface into simple regions without holes. if (!rcBuildLayerRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions."); return 0; } } // Create contours. m_cset = rcAllocContourSet(); if (!m_cset) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); return 0; } if (!rcBuildContours(m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); return 0; } if (m_cset->nconts == 0) { return 0; } // Build polygon navmesh from the contours. m_pmesh = rcAllocPolyMesh(); if (!m_pmesh) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); return 0; } if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); return 0; } // Build detail mesh. m_dmesh = rcAllocPolyMeshDetail(); if (!m_dmesh) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); return 0; } if (!rcBuildPolyMeshDetail(m_ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); return 0; } if (!m_keepInterResults) { rcFreeCompactHeightfield(m_chf); m_chf = 0; rcFreeContourSet(m_cset); m_cset = 0; } unsigned char* navData = 0; int navDataSize = 0; 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. m_ctx->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); return 0; } // 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.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 = m_geom->getOffMeshConnectionVerts(); params.offMeshConRad = m_geom->getOffMeshConnectionRads(); params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); params.offMeshConUserID = m_geom->getOffMeshConnectionId(); params.offMeshConCount = m_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; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); return 0; } } m_tileMemUsage = navDataSize/1024.0f; m_ctx->stopTimer(RC_TIMER_TOTAL); // Show performance stats. duLogBuildTimes(*m_ctx, m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)); m_ctx->log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", m_pmesh->nverts, m_pmesh->npolys); m_tileBuildTime = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; dataSize = navDataSize; return navData; }
PDT_NAV_MESH gkRecast::createNavMesh(PMESHDATA meshData, const Config& config) { if (!meshData.get()) return PDT_NAV_MESH(0); rcConfig cfg; cfg.cs = config.CELL_SIZE; cfg.ch = config.CELL_HEIGHT; GK_ASSERT(cfg.ch && "cfg.ch cannot be zero"); GK_ASSERT(cfg.ch && "cfg.ch cannot be zero"); cfg.walkableSlopeAngle = config.AGENT_MAX_SLOPE; cfg.walkableHeight = (int)ceilf(config.AGENT_HEIGHT / cfg.ch); cfg.walkableClimb = (int)ceilf(config.AGENT_MAX_CLIMB / cfg.ch); cfg.walkableRadius = (int)ceilf(config.AGENT_RADIUS / cfg.cs); cfg.maxEdgeLen = (int)(config.EDGE_MAX_LEN / cfg.cs); cfg.maxSimplificationError = config.EDGE_MAX_ERROR; cfg.minRegionSize = (int)rcSqr(config.REGION_MIN_SIZE); cfg.mergeRegionSize = (int)rcSqr(config.REGION_MERGE_SIZE); cfg.maxVertsPerPoly = gkMin(config.VERTS_PER_POLY, DT_VERTS_PER_POLYGON); cfg.tileSize = config.TILE_SIZE; cfg.borderSize = cfg.walkableRadius + 4; // Reserve enough padding. cfg.detailSampleDist = config.DETAIL_SAMPLE_DIST < 0.9f ? 0 : cfg.cs * config.DETAIL_SAMPLE_DIST; cfg.detailSampleMaxError = cfg.ch * config.DETAIL_SAMPLE_ERROR; if (!meshData->getVertCount()) return PDT_NAV_MESH(0); gkScalar bmin[3], bmax[3]; const gkScalar* verts = meshData->getVerts(); int nverts = meshData->getVertCount(); const int* tris = meshData->getTris(); const gkScalar* trinorms = meshData->getNormals(); int ntris = meshData->getTriCount(); rcCalcBounds(verts, nverts, bmin, bmax); // // Step 1. Initialize build config. // // Set the area where the navigation will be build. // Here the bounds of the input mesh are used, but the // area could be specified by an user defined box, etc. rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height); rcBuildTimes m_buildTimes; // Reset build times gathering. memset(&m_buildTimes, 0, sizeof(m_buildTimes)); rcSetBuildTimes(&m_buildTimes); // Start the build process. rcTimeVal totStartTime = rcGetPerformanceTimer(); //gkPrintf("Building navigation:"); //gkPrintf(" - %d x %d cells", cfg.width, cfg.height); //gkPrintf(" - %.1fK verts, %.1fK tris", nverts/1000.0f, ntris/1000.0f); // // Step 2. Rasterize input polygon soup. // // Allocate voxel heighfield where we rasterize our input data to. rcHeightfield heightField; if (!rcCreateHeightfield(heightField, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)) { gkPrintf("buildNavigation: Could not create solid heightfield."); return PDT_NAV_MESH(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. utArray<unsigned char> triflags; triflags.resize(ntris); // Find triangles which are walkable based on their slope and rasterize them. // If your input data is multiple meshes, you can transform them here, calculate // the flags for each of the meshes and rasterize them. memset(triflags.ptr(), 0, ntris * sizeof(unsigned char)); rcMarkWalkableTriangles(cfg.walkableSlopeAngle, verts, nverts, tris, ntris, triflags.ptr()); rcRasterizeTriangles(verts, nverts, tris, triflags.ptr(), ntris, heightField); } // // Step 3. Filter walkables surfaces. // // 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(cfg.walkableHeight, cfg.walkableClimb, heightField); rcFilterWalkableLowHeightSpans(cfg.walkableHeight, heightField); // // Step 4. Partition walkable surface to simple regions. // // 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 chf; if (!rcBuildCompactHeightfield(cfg.walkableHeight, cfg.walkableClimb, RC_WALKABLE, heightField, chf)) { gkPrintf("buildNavigation: Could not build compact data."); return PDT_NAV_MESH(0); } // Erode the walkable area by agent radius. if (!rcErodeArea(RC_WALKABLE_AREA, cfg.walkableRadius, chf)) { gkPrintf("buildNavigation: Could not erode."); return PDT_NAV_MESH(0); } // // Mark areas from objects // gkScene* scene = gkEngine::getSingleton().getActiveScene(); gkGameObjectSet& objects = scene->getInstancedObjects(); gkGameObjectSet::Iterator it = objects.iterator(); while (it.hasMoreElements()) { gkGameObject* obj = it.getNext(); if (!obj->getNavData().isEmpty()) { size_t tBaseIndex = obj->getNavData().triangleBaseIndex; size_t vBaseIndex = tBaseIndex / 2; const float* v = verts + vBaseIndex; const int nVerts = obj->getNavData().nIndex / 3; const gkGameObjectProperties& prop = obj->getProperties(); rcMarkConvexPolyArea(v, nVerts, obj->getNavData().hmin, obj->getNavData().hmax, prop.m_findPathFlag, chf); } } // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(chf)) { gkPrintf("buildNavigation: Could not build distance field."); return PDT_NAV_MESH(0); } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(chf, cfg.borderSize, cfg.minRegionSize, cfg.mergeRegionSize)) { gkPrintf("buildNavigation: Could not build regions."); return PDT_NAV_MESH(0); } // // Step 5. Trace and simplify region contours. // // Create contours. rcContourSet cset; if (!rcBuildContours(chf, cfg.maxSimplificationError, cfg.maxEdgeLen, cset)) { gkPrintf("buildNavigation: Could not create contours."); return PDT_NAV_MESH(0); } // // Step 6. Build polygons mesh from contours. // // Build polygon navmesh from the contours. rcPolyMesh pmesh; if (!rcBuildPolyMesh(cset, cfg.maxVertsPerPoly, pmesh)) { gkPrintf("buildNavigation: Could not triangulate contours."); return PDT_NAV_MESH(0); } // // Step 7. Create detail mesh which allows to access approximate height on each polygon. // rcPolyMeshDetail dmesh; if (!rcBuildPolyMeshDetail(pmesh, chf, cfg.detailSampleDist, cfg.detailSampleMaxError, dmesh)) { gkPrintf("buildNavigation: Could not build detail mesh."); return PDT_NAV_MESH(0); } // At this point the navigation mesh data is ready, you can access it from pmesh. // See rcDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data. // // Step 8. Create Detour data from Recast poly mesh. // PDT_NAV_MESH navMesh; // Update poly flags from areas. for (int i = 0; i < pmesh.npolys; ++i) pmesh.flags[i] = 0xFFFF & pmesh.areas[i]; dtNavMeshCreateParams params; memset(¶ms, 0, sizeof(params)); params.verts = pmesh.verts; params.vertCount = pmesh.nverts; params.polys = pmesh.polys; params.polyAreas = pmesh.areas; params.polyFlags = pmesh.flags; params.polyCount = pmesh.npolys; params.nvp = pmesh.nvp; params.detailMeshes = dmesh.meshes; params.detailVerts = dmesh.verts; params.detailVertsCount = dmesh.nverts; params.detailTris = dmesh.tris; params.detailTriCount = dmesh.ntris; /* params.offMeshConVerts = m_geom->getOffMeshConnectionVerts(); params.offMeshConRad = m_geom->getOffMeshConnectionRads(); params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); params.offMeshConCount = m_geom->getOffMeshConnectionCount(); */ params.walkableHeight = cfg.walkableHeight * cfg.ch; params.walkableRadius = cfg.walkableRadius * cfg.cs;; params.walkableClimb = cfg.walkableClimb * cfg.ch; rcVcopy(params.bmin, pmesh.bmin); rcVcopy(params.bmax, pmesh.bmax); params.cs = cfg.cs; params.ch = cfg.ch; unsigned char* navData = 0; int navDataSize = 0; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { gkPrintf("Could not build Detour navmesh."); return PDT_NAV_MESH(0); } navMesh = PDT_NAV_MESH(new gkDetourNavMesh(new dtNavMesh)); if (!navMesh->m_p->init(navData, navDataSize, DT_TILE_FREE_DATA, 2048)) { delete [] navData; gkPrintf("Could not init Detour navmesh"); return PDT_NAV_MESH(0); } rcTimeVal totEndTime = rcGetPerformanceTimer(); gkPrintf("Navigation mesh created: %.1fms", rcGetDeltaTimeUsec(totStartTime, totEndTime) / 1000.0f); return navMesh; }
bool Sample_SoloMeshSimple::handleBuild() { if (!m_geom || !m_geom->getMesh()) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified."); return false; } cleanup(); const float* bmin = m_geom->getMeshBoundsMin(); const float* bmax = m_geom->getMeshBoundsMax(); const float* verts = m_geom->getMesh()->getVerts(); const int nverts = m_geom->getMesh()->getVertCount(); const int* tris = m_geom->getMesh()->getTris(); const int ntris = m_geom->getMesh()->getTriCount(); // // Step 1. Initialize build config. // // 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.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; m_cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; // Set the area where the navigation will be build. // Here the bounds of the input mesh are used, but the // area could be specified by an user defined box, etc. rcVcopy(m_cfg.bmin, bmin); rcVcopy(m_cfg.bmax, bmax); rcCalcGridSize(m_cfg.bmin, m_cfg.bmax, m_cfg.cs, &m_cfg.width, &m_cfg.height); // Reset build times gathering. m_ctx->resetTimers(); // Start the build process. m_ctx->startTimer(RC_TIMER_TOTAL); m_ctx->log(RC_LOG_PROGRESS, "Building navigation:"); m_ctx->log(RC_LOG_PROGRESS, " - %d x %d cells", m_cfg.width, m_cfg.height); m_ctx->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", nverts/1000.0f, ntris/1000.0f); // // Step 2. Rasterize input polygon soup. // // Allocate voxel heightfield where we rasterize our input data to. m_solid = rcAllocHeightfield(); if (!m_solid) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); return false; } if (!rcCreateHeightfield(m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create solid heightfield."); return false; } // Allocate array that can hold triangle area types. // 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_triareas = new unsigned char[ntris]; if (!m_triareas) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", ntris); return false; } // Find triangles which are walkable based on their slope and rasterize them. // If your input data is multiple meshes, you can transform them here, calculate // the are type for each of the meshes and rasterize them. memset(m_triareas, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, tris, ntris, m_triareas); rcRasterizeTriangles(m_ctx, verts, nverts, tris, m_triareas, ntris, *m_solid, m_cfg.walkableClimb); if (!m_keepInterResults) { delete [] m_triareas; m_triareas = 0; } // // Step 3. Filter walkables surfaces. // // 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. rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, *m_solid); rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(m_ctx, m_cfg.walkableHeight, *m_solid); // // Step 4. Partition walkable surface to simple regions. // // 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 = rcAllocCompactHeightfield(); if (!m_chf) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); return false; } if (!rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); return false; } if (!m_keepInterResults) { rcFreeHeightField(m_solid); m_solid = 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); return false; } // (Optional) Mark areas. const ConvexVolume* vols = m_geom->getConvexVolumes(); for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(m_ctx, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); return false; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return false; } // // Step 5. Trace and simplify region contours. // // Create contours. m_cset = rcAllocContourSet(); if (!m_cset) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); return false; } if (!rcBuildContours(m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); return false; } // // Step 6. Build polygons mesh from contours. // // Build polygon navmesh from the contours. m_pmesh = rcAllocPolyMesh(); if (!m_pmesh) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); return false; } if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); return false; } // // Step 7. Create detail mesh which allows to access approximate height on each polygon. // m_dmesh = rcAllocPolyMeshDetail(); if (!m_dmesh) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmdtl'."); return false; } if (!rcBuildPolyMeshDetail(m_ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build detail mesh."); return false; } if (!m_keepInterResults) { rcFreeCompactHeightfield(m_chf); m_chf = 0; rcFreeContourSet(m_cset); m_cset = 0; } // At this point the navigation mesh data is ready, you can access it from m_pmesh. // See duDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data. // // (Optional) Step 8. Create Detour data from Recast poly mesh. // // The GUI may allow more max points per polygon than Detour can handle. // Only build the detour navmesh if we do not exceed the limit. if (m_cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { unsigned char* navData = 0; int navDataSize = 0; // 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 = m_geom->getOffMeshConnectionVerts(); params.offMeshConRad = m_geom->getOffMeshConnectionRads(); params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); params.offMeshConUserID = m_geom->getOffMeshConnectionId(); params.offMeshConCount = m_geom->getOffMeshConnectionCount(); params.walkableHeight = m_agentHeight; params.walkableRadius = m_agentRadius; params.walkableClimb = m_agentMaxClimb; rcVcopy(params.bmin, m_pmesh->bmin); rcVcopy(params.bmax, m_pmesh->bmax); params.cs = m_cfg.cs; params.ch = m_cfg.ch; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); return false; } m_navMesh = dtAllocNavMesh(); if (!m_navMesh) { dtFree(navData); m_ctx->log(RC_LOG_ERROR, "Could not create Detour navmesh"); return false; } if (m_navMesh->init(navData, navDataSize, DT_TILE_FREE_DATA) != DT_SUCCESS) { dtFree(navData); m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh"); return false; } if (m_navQuery->init(m_navMesh, 2048) != DT_SUCCESS) { m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh query"); return false; } } m_ctx->stopTimer(RC_TIMER_TOTAL); // Show performance stats. duLogBuildTimes(*m_ctx, m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)); m_ctx->log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", m_pmesh->nverts, m_pmesh->npolys); m_totalBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; if (m_tool) m_tool->init(this); return true; }
unsigned char* Sample_TileMesh::buildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize) { if (!m_geom || !m_geom->getMesh() || !m_geom->getChunkyMesh()) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified."); return 0; } m_tileMemUsage = 0; m_tileBuildTime = 0; cleanup(); const float* verts = m_geom->getMesh()->getVerts(); const int nverts = m_geom->getMesh()->getVertCount(); const int ntris = m_geom->getMesh()->getTriCount(); const rcChunkyTriMesh* chunkyMesh = m_geom->getChunkyMesh(); // 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; // Reset build times gathering. m_ctx->resetTimers(); // Start the build process. m_ctx->startTimer(RC_TIMER_TOTAL); m_ctx->log(RC_LOG_PROGRESS, "Building navigation:"); m_ctx->log(RC_LOG_PROGRESS, " - %d x %d cells", m_cfg.width, m_cfg.height); m_ctx->log(RC_LOG_PROGRESS, " - %.1fK verts, %.1fK tris", nverts/1000.0f, ntris/1000.0f); // Allocate voxel heightfield where we rasterize our input data to. m_solid = rcAllocHeightfield(); if (!m_solid) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'solid'."); return 0; } if (!rcCreateHeightfield(m_ctx, *m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { m_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. m_triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; if (!m_triareas) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", 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[512];// TODO: Make grow when returning too many items. const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); if (!ncid) return 0; 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(m_ctx, m_cfg.walkableSlopeAngle, verts, nverts, tris, ntris, m_triareas); rcRasterizeTriangles(m_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(m_ctx, m_cfg.walkableClimb, *m_solid); rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(m_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. m_chf = rcAllocCompactHeightfield(); if (!m_chf) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); return 0; } if (!rcBuildCompactHeightfield(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); return 0; } if (!m_keepInterResults) { rcFreeHeightField(m_solid); m_solid = 0; } // Erode the walkable area by agent radius. if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); return 0; } // (Optional) Mark areas. const ConvexVolume* vols = m_geom->getConvexVolumes(); for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); // Prepare for region partitioning, by calculating distance field along the walkable surface. if (!rcBuildDistanceField(m_ctx, *m_chf)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); return 0; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(m_ctx, *m_chf, m_cfg.borderSize, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return 0; } // Create contours. m_cset = rcAllocContourSet(); if (!m_cset) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); return 0; } if (!rcBuildContours(m_ctx, *m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); return 0; } if (m_cset->nconts == 0) { return 0; } // Build polygon navmesh from the contours. m_pmesh = rcAllocPolyMesh(); if (!m_pmesh) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); return 0; } if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); return 0; } // Build detail mesh. m_dmesh = rcAllocPolyMeshDetail(); if (!m_dmesh) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); return 0; } if (!rcBuildPolyMeshDetail(m_ctx, *m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); return 0; } if (!m_keepInterResults) { rcFreeCompactHeightfield(m_chf); m_chf = 0; rcFreeContourSet(m_cset); m_cset = 0; } unsigned char* navData = 0; int navDataSize = 0; if (m_cfg.maxVertsPerPoly <= DT_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 (m_pmesh->nverts >= 0xffff) { // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. m_ctx->log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", m_pmesh->nverts, 0xffff); return 0; } // 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 = m_geom->getOffMeshConnectionVerts(); params.offMeshConRad = m_geom->getOffMeshConnectionRads(); params.offMeshConDir = m_geom->getOffMeshConnectionDirs(); params.offMeshConAreas = m_geom->getOffMeshConnectionAreas(); params.offMeshConFlags = m_geom->getOffMeshConnectionFlags(); params.offMeshConUserID = m_geom->getOffMeshConnectionId(); params.offMeshConCount = m_geom->getOffMeshConnectionCount(); params.walkableHeight = m_agentHeight; params.walkableRadius = m_agentRadius; params.walkableClimb = m_agentMaxClimb; params.tileX = tx; params.tileY = ty; rcVcopy(params.bmin, bmin); rcVcopy(params.bmax, bmax); params.cs = m_cfg.cs; params.ch = m_cfg.ch; params.tileSize = m_cfg.tileSize; if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); return 0; } // Restore padding so that the debug visualization is correct. 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; } } m_tileMemUsage = navDataSize/1024.0f; m_ctx->stopTimer(RC_TIMER_TOTAL); // Show performance stats. duLogBuildTimes(*m_ctx, m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)); m_ctx->log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", m_pmesh->nverts, m_pmesh->npolys); m_tileBuildTime = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; dataSize = navDataSize; return navData; }
static void rasterizeTri(const float* v0, const float* v1, const float* v2, const unsigned char area, rcHeightfield& hf, const float* bmin, const float* bmax, const float cs, const float ics, const float ich, const int flagMergeThr) { const int w = hf.width; const int h = hf.height; float tmin[3], tmax[3]; const float by = bmax[1] - bmin[1]; // Calculate the bounding box of the triangle. rcVcopy(tmin, v0); rcVcopy(tmax, v0); rcVmin(tmin, v1); rcVmin(tmin, v2); rcVmax(tmax, v1); rcVmax(tmax, v2); // If the triangle does not touch the bbox of the heightfield, skip the triagle. if (!overlapBounds(bmin, bmax, tmin, tmax)) return; // Calculate the footpring of the triangle on the grid. int x0 = (int)((tmin[0] - bmin[0])*ics); int y0 = (int)((tmin[2] - bmin[2])*ics); int x1 = (int)((tmax[0] - bmin[0])*ics); int y1 = (int)((tmax[2] - bmin[2])*ics); x0 = rcClamp(x0, 0, w-1); y0 = rcClamp(y0, 0, h-1); x1 = rcClamp(x1, 0, w-1); y1 = rcClamp(y1, 0, h-1); // Clip the triangle into all grid cells it touches. float in[7*3], out[7*3], inrow[7*3]; for (int y = y0; y <= y1; ++y) { // Clip polygon to row. rcVcopy(&in[0], v0); rcVcopy(&in[1*3], v1); rcVcopy(&in[2*3], v2); int nvrow = 3; const float cz = bmin[2] + y*cs; nvrow = clipPoly(in, nvrow, out, 0, 1, -cz); if (nvrow < 3) continue; nvrow = clipPoly(out, nvrow, inrow, 0, -1, cz+cs); if (nvrow < 3) continue; for (int x = x0; x <= x1; ++x) { // Clip polygon to column. int nv = nvrow; const float cx = bmin[0] + x*cs; nv = clipPoly(inrow, nv, out, 1, 0, -cx); if (nv < 3) continue; nv = clipPoly(out, nv, in, -1, 0, cx+cs); if (nv < 3) continue; // Calculate min and max of the span. float smin = in[1], smax = in[1]; for (int i = 1; i < nv; ++i) { smin = rcMin(smin, in[i*3+1]); smax = rcMax(smax, in[i*3+1]); } smin -= bmin[1]; smax -= bmin[1]; // Skip the span if it is outside the heightfield bbox if (smax < 0.0f) continue; if (smin > by) continue; // Clamp the span to the heightfield bbox. if (smin < 0.0f) smin = 0; if (smax > by) smax = by; // Snap the span to the heightfield height grid. unsigned short ismin = (unsigned short)rcClamp((int)floorf(smin * ich), 0, RC_SPAN_MAX_HEIGHT); unsigned short ismax = (unsigned short)rcClamp((int)ceilf(smax * ich), (int)ismin+1, RC_SPAN_MAX_HEIGHT); rcAddSpan(hf, x, y, ismin, ismax, area, flagMergeThr); } } }
void rcMarkConvexPolyArea(rcContext* ctx, const float* verts, const int nverts, const float hmin, const float hmax, unsigned char areaId, rcCompactHeightfield& chf) { rcAssert(ctx); ctx->startTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); float bmin[3], bmax[3]; rcVcopy(bmin, verts); rcVcopy(bmax, verts); for (int i = 1; i < nverts; ++i) { rcVmin(bmin, &verts[i*3]); rcVmax(bmax, &verts[i*3]); } bmin[1] = hmin; bmax[1] = hmax; int minx = (int)((bmin[0]-chf.bmin[0])/chf.cs); int miny = (int)((bmin[1]-chf.bmin[1])/chf.ch); int minz = (int)((bmin[2]-chf.bmin[2])/chf.cs); int maxx = (int)((bmax[0]-chf.bmin[0])/chf.cs); int maxy = (int)((bmax[1]-chf.bmin[1])/chf.ch); int maxz = (int)((bmax[2]-chf.bmin[2])/chf.cs); if (maxx < 0) return; if (minx >= chf.width) return; if (maxz < 0) return; if (minz >= chf.height) return; if (minx < 0) minx = 0; if (maxx >= chf.width) maxx = chf.width-1; if (minz < 0) minz = 0; if (maxz >= chf.height) maxz = chf.height-1; // TODO: Optimize. for (int z = minz; z <= maxz; ++z) { for (int x = minx; x <= maxx; ++x) { const rcCompactCell& c = chf.cells[x+z*chf.width]; for (int i = (int)c.index, ni = (int)(c.index+c.count); i < ni; ++i) { rcCompactSpan& s = chf.spans[i]; if ((int)s.y >= miny && (int)s.y <= maxy) { float p[3]; p[0] = chf.bmin[0] + (x+0.5f)*chf.cs; p[1] = 0; p[2] = chf.bmin[2] + (z+0.5f)*chf.cs; if (pointInPoly(nverts, verts, p)) { chf.areas[i] = areaId; } } } } } ctx->stopTimer(RC_TIMER_MARK_CONVEXPOLY_AREA); }
void NavigationMesh::SetNavigationDataAttr(PODVector<unsigned char> value) { ReleaseNavigationMesh(); if (value.Empty()) return; MemoryBuffer buffer(value); boundingBox_ = buffer.ReadBoundingBox(); numTilesX_ = buffer.ReadInt(); numTilesZ_ = buffer.ReadInt(); dtNavMeshParams params; rcVcopy(params.orig, &boundingBox_.min_.x_); params.tileWidth = buffer.ReadFloat(); params.tileHeight = buffer.ReadFloat(); params.maxTiles = buffer.ReadInt(); params.maxPolys = buffer.ReadInt(); navMesh_ = dtAllocNavMesh(); if (!navMesh_) { LOGERROR("Could not allocate navigation mesh"); return; } if (dtStatusFailed(navMesh_->init(¶ms))) { LOGERROR("Could not initialize navigation mesh"); ReleaseNavigationMesh(); return; } unsigned numTiles = 0; while (!buffer.IsEof()) { /*int x =*/ buffer.ReadInt(); /*int z =*/ buffer.ReadInt(); /*dtTileRef tileRef =*/ buffer.ReadUInt(); unsigned navDataSize = buffer.ReadUInt(); unsigned char* navData = (unsigned char*)dtAlloc(navDataSize, DT_ALLOC_PERM); if (!navData) { LOGERROR("Could not allocate data for navigation mesh tile"); return; } buffer.Read(navData, navDataSize); if (dtStatusFailed(navMesh_->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, 0))) { LOGERROR("Failed to add navigation mesh tile"); dtFree(navData); return; } else ++numTiles; } LOGDEBUG("Created navigation mesh with " + String(numTiles) + " tiles from serialized data"); }