bool Visualization::initializeCamera() { if (m_scene) { const float* max = m_scene->getMeshBoundsMax(); const float* min = m_scene->getMeshBoundsMin(); m_zFar = sqrtf(rcSqr(max[0]-min[0]) + rcSqr(max[1]-min[1]) + rcSqr(max[2]-min[2])); m_cameraPosition[0] = (max[0] + min[0]) / 2.f; m_cameraPosition[1] = (max[1] + min[1]) / 2.f + m_zFar; m_cameraPosition[2] = (max[2] + min[2]) / 2.f; m_zFar *= 2.f; m_cameraOrientation[0] = 90.f; m_cameraOrientation[1] = 0.f; m_cameraOrientation[2] = 0.f; } else { m_cameraPosition[0] = m_cameraPosition[1] = m_cameraPosition[2] = 0.f; m_cameraOrientation[0] = 45.f; m_cameraOrientation[1] = -45.f; m_cameraOrientation[2] = 0.f; } m_cameraVelocity[0] = m_cameraVelocity[1] = m_cameraVelocity[2] = 0.f; return true; }
void RecastToolKit::ResetCameraAndFog(const std::unique_ptr<InputGeom>& geom, const std::shared_ptr<Sample>& sample, float & camx, float & camy, float & camz, float & camr, float & rx, float & ry) { if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0] - bmin[0]) + rcSqr(bmax[1] - bmin[1]) + rcSqr(bmax[2] - bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.1f); glFogf(GL_FOG_END, camr*1.25f); } }
TileBuilder::TileBuilder(ContinentBuilder* _cBuilder, std::string world, int x, int y, uint32 mapId) : World(world), X(x), Y(y), MapId(mapId), _Geometry(NULL), DataSize(0), cBuilder(_cBuilder) { /* Test, non-working values // Cell Size = TileSize / TileVoxelSize // 1800 = TileVoxelSize Config.cs = Constants::TileSize / 1800; // Cell Height Config.ch = 0.4f; // Min Region Area = 20^2 Config.minRegionArea = 20*20; // Merge Region Area = 40^2 Config.mergeRegionArea = 40*40; Config.tileSize = Constants::TileSize / 4; Config.walkableSlopeAngle = 50.0f; Config.detailSampleDist = 3.0f; Config.detailSampleMaxError = 1.25f; Config.walkableClimb = floorf(1.0f / Config.ch); Config.walkableHeight = ceilf(1.652778f / Config.ch); Config.walkableRadius = ceilf(0.2951389f / Config.cs); Config.maxEdgeLen = Config.walkableRadius * 8; Config.borderSize = Config.walkableRadius + 4; Config.width = 1800 + Config.borderSize * 2; Config.height = 1800 + Config.borderSize * 2; Config.maxVertsPerPoly = 6; Config.maxSimplificationError = 1.3f; */ // All are in UNIT metrics! memset(&Config, 0, sizeof(rcConfig)); Config.maxVertsPerPoly = DT_VERTS_PER_POLYGON; Config.cs = Constants::BaseUnitDim; Config.ch = Constants::BaseUnitDim; Config.walkableSlopeAngle = 60.0f; Config.tileSize = Constants::VertexPerTile; Config.walkableRadius = 1; Config.borderSize = Config.walkableRadius + 3; Config.maxEdgeLen = Constants::VertexPerTile + 1; //anything bigger than tileSize Config.walkableHeight = 3; Config.walkableClimb = 2; // 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; Context = new rcContext; }
RecastTileBuilder::RecastTileBuilder(float waterTableHeight, float x, float y, const AABB& bounds, const rcChunkyTriMesh* mesh, const RecastSettings& settings) : m_keepInterResults(false), m_buildAll(true), m_totalBuildTimeMs(0), m_triareas(0), m_solid(0), m_chf(0), m_cset(0), m_pmesh(0), m_dmesh(0), m_maxTiles(0), m_maxPolysPerTile(0), bounds(Vector3(0, 0, 0), Vector3(0, 0, 0)), m_tileTriCount(0), waterTableHeight(waterTableHeight), lastTileBounds(bounds) { this->settings = settings; // Init build configuration from GUI memset(&m_cfg, 0, sizeof(m_cfg)); m_cfg.cs = settings.m_cellSize; m_cfg.ch = settings.m_cellHeight; m_cfg.walkableSlopeAngle = settings.m_agentMaxSlope; m_cfg.walkableHeight = (int) ceilf(settings.m_agentHeight / m_cfg.ch); m_cfg.walkableClimb = (int) floorf(settings.m_agentMaxClimb / m_cfg.ch); m_cfg.walkableRadius = (int) ceilf(settings.m_agentRadius / m_cfg.cs); m_cfg.maxEdgeLen = (int) (settings.m_edgeMaxLen / settings.m_cellSize); m_cfg.maxSimplificationError = settings.m_edgeMaxError; m_cfg.minRegionArea = (int) rcSqr(settings.m_regionMinSize); // Note: area = size*size m_cfg.mergeRegionArea = (int) rcSqr(settings.m_regionMergeSize); // Note: area = size*size m_cfg.maxVertsPerPoly = (int) settings.m_vertsPerPoly; m_cfg.tileSize = (int) settings.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 = settings.m_detailSampleDist < 0.9f ? 0 : settings.m_cellSize * settings.m_detailSampleDist; m_cfg.detailSampleMaxError = settings.m_cellHeight * settings.m_detailSampleMaxError; tileX = x; tileY = y; m_ctx = new rcContext(); chunkyMesh = mesh; }
static unsigned char getEdgeFlags(const float* va, const float* vb, const float* vpoly, const int npoly) { // Return true if edge (va,vb) is part of the polygon. static const float thrSqr = rcSqr(0.001f); for (int i = 0, j = npoly-1; i < npoly; j=i++) { if (distancePtSeg2d(va, &vpoly[j*3], &vpoly[i*3]) < thrSqr && distancePtSeg2d(vb, &vpoly[j*3], &vpoly[i*3]) < thrSqr) return 1; } return 0; }
static bool buildPolyDetail(rcContext* ctx, 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 = 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 = 0; for (int i = 0; i < nin; ++i) rcVcopy(&verts[i*3], &in[i*3]); nverts = nin; 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, 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 thing 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, 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; }
void ConvexVolumeTool::handleClick(const float* /*s*/, const float* p, bool shift) { if (!m_sample) return; InputGeom* geom = m_sample->getInputGeom(); if (!geom) return; if (shift) { // Delete int nearestIndex = -1; const ConvexVolume* vols = geom->getConvexVolumes(); for (int i = 0; i < geom->getConvexVolumeCount(); ++i) { if (pointInPoly(vols[i].nverts, vols[i].verts, p) && p[1] >= vols[i].hmin && p[1] <= vols[i].hmax) { nearestIndex = i; } } // If end point close enough, delete it. if (nearestIndex != -1) { geom->deleteConvexVolume(nearestIndex); } } else { // Create // If clicked on that last pt, create the shape. if (m_npts && rcVdistSqr(p, &m_pts[(m_npts-1)*3]) < rcSqr(0.2f)) { if (m_nhull > 2) { // Create shape. float verts[MAX_PTS*3]; for (int i = 0; i < m_nhull; ++i) rcVcopy(&verts[i*3], &m_pts[m_hull[i]*3]); float minh = FLT_MAX, maxh = 0; for (int i = 0; i < m_nhull; ++i) minh = rcMin(minh, verts[i*3+1]); minh -= m_boxDescent; maxh = minh + m_boxHeight; geom->addConvexVolume(verts, m_nhull, minh, maxh, (unsigned char)m_areaType); } m_npts = 0; m_nhull = 0; } else { // Add new point if (m_npts < MAX_PTS) { rcVcopy(&m_pts[m_npts*3], p); m_npts++; // Update hull. if (m_npts > 1) m_nhull = convexhull(m_pts, m_npts, m_hull); else m_nhull = 0; } } } }
bool Sample_SoloMesh::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); // 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 false; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build watershed regions."); return false; } } 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, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build monotone regions."); return false; } } else // SAMPLE_PARTITION_LAYERS { // Partition the walkable surface into simple regions without holes. if (!rcBuildLayerRegions(m_ctx, *m_chf, 0, m_cfg.minRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build layer 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; params.buildBvTree = true; 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; } dtStatus status; status = m_navMesh->init(navData, navDataSize, DT_TILE_FREE_DATA); if (dtStatusFailed(status)) { dtFree(navData); m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh"); return false; } status = m_navQuery->init(m_navMesh, 2048); if (dtStatusFailed(status)) { 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); initToolStates(this); return true; }
void CMaNGOS_Map::handleSettings() { if (m_MapInfos->IsEmpty()) return; if (m_SelectedTile) { bool tileFound = false; imguiLabel("Tile commands"); std::string bText; if (m_MapInfos->GetTileRef(m_SelectedTile->tx, m_SelectedTile->ty)) { tileFound = true; bText = "Clear selected tile navmesh"; if (imguiButton(bText.c_str())) { setTool(NULL); m_MapInfos->ClearNavMeshOfTile(m_SelectedTile->tx, m_SelectedTile->ty); } } else { bText = "Load selected tile navmesh"; if (imguiButton(bText.c_str())) { if (m_MapInfos->LoadNavMeshOfTile(m_SelectedTile->tx, m_SelectedTile->ty)) setTool(new NavMeshTesterTool); } bText = "Build navmesh for selected tile"; if (imguiButton(bText.c_str())) { rcConfig cfg; cfg.cs = m_cellSize; cfg.ch = m_cellHeight; cfg.walkableSlopeAngle = m_agentMaxSlope; cfg.walkableHeight = (int)ceilf(m_agentHeight);// (int)ceilf(m_agentHeight / m_cfg.ch); cfg.walkableClimb = (int)floorf(m_agentMaxClimb);// (int)floorf(m_agentMaxClimb / m_cfg.ch); cfg.walkableRadius = (int)ceilf(m_agentRadius);// (int)ceilf(m_agentRadius / m_cfg.cs); cfg.maxEdgeLen = (int)m_edgeMaxLen;// (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.detailSampleDist = m_cellSize * m_detailSampleDist; cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; m_MapInfos->BuildNavMeshOfTile(m_SelectedTile->tx, m_SelectedTile->ty, &cfg, m_partitionType); setTool(new NavMeshTesterTool); } } if (tileFound) return; char tmpStr[50]; imguiLabel("Rasterization"); snprintf(tmpStr, sizeof(tmpStr), "Cell Size = %4.3f", m_cellSize); imguiValue(tmpStr); snprintf(tmpStr, sizeof(tmpStr), "Cell Height = %4.3f", m_cellHeight); imguiValue(tmpStr); if (!m_MapInfos->GetGeomsMap()->empty()) { int gw = 0, gh = 0; rcCalcGridSize(m_MapInfos->BMin(), m_MapInfos->BMax(), m_cellSize, &gw, &gh); char text[64]; snprintf(text, 64, "Voxels %d x %d", gw, gh); imguiValue(text); } imguiSeparator(); imguiLabel("Agent"); imguiSlider("Height", &m_agentHeight, 0.1f, 5.0f, 0.1f); imguiSlider("Radius", &m_agentRadius, 0.0f, 5.0f, 0.1f); imguiSlider("Max Climb", &m_agentMaxClimb, 0.1f, 5.0f, 0.1f); imguiSlider("Max Slope", &m_agentMaxSlope, 0.0f, 90.0f, 1.0f); imguiSeparator(); imguiLabel("Region"); imguiSlider("Min Region Size", &m_regionMinSize, 0.0f, 150.0f, 1.0f); imguiSlider("Merged Region Size", &m_regionMergeSize, 0.0f, 150.0f, 1.0f); imguiSeparator(); imguiLabel("Partitioning"); if (imguiCheck("Watershed", m_partitionType == SAMPLE_PARTITION_WATERSHED)) m_partitionType = SAMPLE_PARTITION_WATERSHED; if (imguiCheck("Monotone", m_partitionType == SAMPLE_PARTITION_MONOTONE)) m_partitionType = SAMPLE_PARTITION_MONOTONE; if (imguiCheck("Layers", m_partitionType == SAMPLE_PARTITION_LAYERS)) m_partitionType = SAMPLE_PARTITION_LAYERS; imguiSeparator(); imguiLabel("Polygonization"); imguiSlider("Max Edge Length", &m_edgeMaxLen, 0.0f, 100.0f, 1.0f); imguiSlider("Max Edge Error", &m_edgeMaxError, 0.1f, 3.0f, 0.1f); imguiSlider("Verts Per Poly", &m_vertsPerPoly, 3.0f, 12.0f, 1.0f); imguiSeparator(); imguiLabel("Detail Mesh"); imguiSlider("Sample Distance", &m_detailSampleDist, 0.0f, 100.0f, 1.0f); imguiSlider("Max Sample Error", &m_detailSampleMaxError, 0.0f, 10.0f, 1.0f); imguiSeparator(); char text[64]; int gw = 0, gh = 0; rcCalcGridSize(m_MapInfos->BMin(), m_MapInfos->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; snprintf(text, 64, "Tiles %d x %d", tw, th); imguiValue(text); imguiSeparator(); } }
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); }
//----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CRecastMesh::Build( CMapMesh *pMapMesh ) { double fStartTime = Plat_FloatTime(); Reset(); // Clean any existing data BuildContext ctx; ctx.enableLog( true ); dtStatus status; V_memset(&m_cfg, 0, sizeof(m_cfg)); // Init cache rcCalcBounds( pMapMesh->GetVerts(), pMapMesh->GetNumVerts(), m_cfg.bmin, m_cfg.bmax ); rcCalcGridSize(m_cfg.bmin, m_cfg.bmax, m_cfg.cs, &m_cfg.width, &m_cfg.height); int gw = 0, gh = 0; rcCalcGridSize(m_cfg.bmin, m_cfg.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; // 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; // Generation params. 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; // Tile cache params. dtTileCacheParams tcparams; memset(&tcparams, 0, sizeof(tcparams)); rcVcopy(tcparams.orig, m_cfg.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 = 2048; dtFreeTileCache(m_tileCache); m_tileCache = dtAllocTileCache(); if (!m_tileCache) { 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)) { ctx.log(RC_LOG_ERROR, "buildTiledNavigation: Could not init tile cache."); return false; } dtFreeNavMesh(m_navMesh); m_navMesh = dtAllocNavMesh(); if (!m_navMesh) { ctx.log(RC_LOG_ERROR, "buildTiledNavigation: Could not allocate navmesh."); return false; } dtNavMeshParams params; memset(¶ms, 0, sizeof(params)); rcVcopy(params.orig, m_cfg.bmin); 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)) { ctx.log(RC_LOG_ERROR, "buildTiledNavigation: Could not init navmesh."); return false; } status = m_navQuery->init( m_navMesh, RECAST_NAVQUERY_MAXNODES ); if (dtStatusFailed(status)) { ctx.log(RC_LOG_ERROR, "buildTiledNavigation: Could not init Detour navmesh query"); return false; } // Preprocess tiles. 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(&ctx, pMapMesh, x, y, m_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 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); ctx.stopTimer(RC_TIMER_TOTAL); m_cacheBuildTimeMs = ctx.getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; m_cacheBuildMemUsage = ((LinearAllocator *)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; } DevMsg( "CRecastMesh: Generated navigation mesh %s in %f seconds\n", m_Name.Get(), Plat_FloatTime() - fStartTime ); return true; }
bool NavMesher::Build() { // ******* Only for OBJ Loading **** cleanup(); const char * filepath = "../../media/models/"; if (!m_geom || !m_geom->loadMesh(filepath)) { delete m_geom; m_geom = 0; m_ctx->log(RC_LOG_ERROR, "Geom load log %s:"); } assert(m_geom); if (!m_geom || !m_geom->getMesh()) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Input mesh is not specified."); return false; } if(m_geom->getMesh()->getTriCount() <= 0 || m_geom->getMesh()->getVertCount()<=0) Ogre::Exception(0,Ogre::String("Bad verts or Triangle count. Verts: "+ StringConverter::toString( m_geom->getMesh()->getVertCount()) + "/n" + "Triangles :" +StringConverter::toString(m_geom->getMesh()->getTriCount())),"NavMesher::Build"); //reset timer Ogre::Timer tm; tm.reset(); unsigned long stime = tm.getMicroseconds(); //clear existing Clear(); // ******* Only for OBJ Loading **** 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(); if(sizeof(tris) <= 0 || ntris <= 0) { return false; } // // 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); if (m_monotonePartitioning) { // Partition the walkable surface into simple regions without holes. // Monotone partitioning does not need distancefield. if (!rcBuildRegionsMonotone(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) { m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build regions."); return false; } } else { // 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, 0, 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 rcDebugDrawPolyMesh 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. 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; } } memset(&m_params, 0, sizeof(m_params)); m_params.verts = m_pmesh->verts; m_params.vertCount = m_pmesh->nverts; m_params.polys = m_pmesh->polys; m_params.polyAreas = m_pmesh->areas; m_params.polyFlags = m_pmesh->flags; m_params.polyCount = m_pmesh->npolys; m_params.nvp = m_pmesh->nvp; m_params.detailMeshes = m_dmesh->meshes; m_params.detailVerts = m_dmesh->verts; m_params.detailVertsCount = m_dmesh->nverts; m_params.detailTris = m_dmesh->tris; m_params.detailTriCount = m_dmesh->ntris; m_params.walkableHeight = m_agentHeight; m_params.walkableRadius = m_agentRadius; m_params.walkableClimb = m_agentMaxClimb; rcVcopy(m_params.bmin, m_pmesh->bmin); rcVcopy(m_params.bmax, m_pmesh->bmax); m_params.cs = m_cfg.cs; m_params.ch = m_cfg.ch; m_params.buildBvTree = true; if (!dtCreateNavMeshData(&m_params, &navData, &navDataSize)) { m_ctx->log(RC_LOG_ERROR, "Could not build Detour navmesh."); return false; } m_navMesh = dtAllocNavMesh(); if (!m_navMesh) { delete [] navData; m_ctx->log(RC_LOG_ERROR, "Could not create Detour navmesh"); return false; } m_navQuery = dtAllocNavMeshQuery(); dtStatus status = m_navQuery->init(m_navMesh, 2048); if (dtStatusFailed(status)) { m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh query"); return false; } if (!m_navMesh->init(navData, navDataSize, true)) { delete [] navData; m_ctx->log(RC_LOG_ERROR, "Could not init Detour navmesh"); return false; } //take time stime = tm.getMicroseconds() - stime; DrawDebug(); return true; }
/*! 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, OBJ *obj, unsigned int mesh_index ) { unsigned int i = 0, j = 0, k = 0, triangle_count = 0; int *indices = NULL; OBJMESH *objmesh = &obj->objmesh[ mesh_index ]; vec3 *vertex_array = ( vec3 * ) malloc( objmesh->n_objvertexdata * sizeof( vec3 ) ), *vertex_start = vertex_array; rcHeightfield *rcheightfield; rcCompactHeightfield *rccompactheightfield; rcContourSet *rccontourset; rcPolyMesh *rcpolymesh; rcPolyMeshDetail *rcpolymeshdetail; 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; } 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, 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, objmesh->n_objvertexdata, indices, triangle_count, navigation->triangle_flags ); rcRasterizeTriangles( ( float * )vertex_start, 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; }
static bool buildPolyDetail(const float* in, const int nin, unsigned short reg, const float sampleDist, const float sampleMaxError, const rcCompactHeightfield& chf, const rcHeightPatch& hp, float* verts, int& nverts, rcIntArray& tris, rcIntArray& edges, rcIntArray& idx, rcIntArray& samples) { static const int MAX_VERTS = 256; static const int MAX_EDGE = 64; float edge[(MAX_EDGE+1)*3]; nverts = 0; for (int i = 0; i < nin; ++i) vcopy(&verts[i*3], &in[i*3]); nverts = nin; const float ics = 1.0f/chf.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]; // 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); } else { if (vj[0] > vi[0]) rcSwap(vj,vi); } // 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] = chf.bmin[1] + getHeight(pos, chf.bmin, ics, 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; } } // Add new vertices. for (int k = 1; k < nidx-1; ++k) { vcopy(&verts[nverts*3], &edge[idx[k]*3]); nverts++; } } } // Tesselate the base mesh. edges.resize(0); tris.resize(0); idx.resize(0); delaunay(nverts, verts, idx, tris, edges); if (sampleDist > 0) { // Create sample locations in a grid. float bmin[3], bmax[3]; vcopy(bmin, in); vcopy(bmax, in); for (int i = 1; i < nin; ++i) { vmin(bmin, &in[i*3]); vmax(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[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, chf.bmin, ics, 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]; float bestd = 0; for (int i = 0; i < nsamples; ++i) { float pt[3]; pt[0] = samples[i*3+0]*sampleDist; pt[1] = chf.bmin[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; vcopy(bestpt,pt); } } // If the max error is within accepted threshold, stop tesselating. if (bestd <= sampleMaxError) break; // Add the new sample point. vcopy(&verts[nverts*3],bestpt); nverts++; // Create new triangulation. // TODO: Incremental add instead of full rebuild. edges.resize(0); tris.resize(0); idx.resize(0); delaunay(nverts, verts, idx, tris, edges); if (nverts >= MAX_VERTS) break; } } return true; }
// Based on Paul Bourke's triangulate.c // http://astronomy.swin.edu.au/~pbourke/terrain/triangulate/triangulate.c static void delaunay(const int nv, float *verts, rcIntArray& idx, rcIntArray& tris, rcIntArray& edges) { // Sort vertices idx.resize(nv); for (int i = 0; i < nv; ++i) idx[i] = i; #ifdef WIN32 qsort_s(&idx[0], idx.size(), sizeof(int), ptcmp, verts); #else qsort_r(&idx[0], idx.size(), sizeof(int), verts, ptcmp); #endif // Find the maximum and minimum vertex bounds. // This is to allow calculation of the bounding triangle float xmin = verts[0]; float ymin = verts[2]; float xmax = xmin; float ymax = ymin; for (int i = 1; i < nv; ++i) { xmin = rcMin(xmin, verts[i*3+0]); xmax = rcMax(xmax, verts[i*3+0]); ymin = rcMin(ymin, verts[i*3+2]); ymax = rcMax(ymax, verts[i*3+2]); } float dx = xmax - xmin; float dy = ymax - ymin; float dmax = (dx > dy) ? dx : dy; float xmid = (xmax + xmin) / 2.0f; float ymid = (ymax + ymin) / 2.0f; // Set up the supertriangle // This is a triangle which encompasses all the sample points. // The supertriangle coordinates are added to the end of the // vertex list. The supertriangle is the first triangle in // the triangle list. float sv[3*3]; sv[0] = xmid - 20 * dmax; sv[1] = 0; sv[2] = ymid - dmax; sv[3] = xmid; sv[4] = 0; sv[5] = ymid + 20 * dmax; sv[6] = xmid + 20 * dmax; sv[7] = 0; sv[8] = ymid - dmax; tris.push(-3); tris.push(-2); tris.push(-1); tris.push(0); // not completed for (int i = 0; i < nv; ++i) { const float xp = verts[idx[i]*3+0]; const float yp = verts[idx[i]*3+2]; edges.resize(0); // Set up the edge buffer. // If the point (xp,yp) lies inside the circumcircle then the // three edges of that triangle are added to the edge buffer // and that triangle is removed. for (int j = 0; j < tris.size()/4; ++j) { int* t = &tris[j*4]; if (t[3]) // completed? continue; const float* v1 = t[0] < 0 ? &sv[(t[0]+3)*3] : &verts[idx[t[0]]*3]; const float* v2 = t[1] < 0 ? &sv[(t[1]+3)*3] : &verts[idx[t[1]]*3]; const float* v3 = t[2] < 0 ? &sv[(t[2]+3)*3] : &verts[idx[t[2]]*3]; float xc,yc,rsqr; int inside = circumCircle(xp,yp, v1[0],v1[2], v2[0],v2[2], v3[0],v3[2], xc,yc,rsqr); if (xc < xp && rcSqr(xp-xc) > rsqr) t[3] = 1; if (inside) { // Collect triangle edges. edges.push(t[0]); edges.push(t[1]); edges.push(t[1]); edges.push(t[2]); edges.push(t[2]); edges.push(t[0]); // Remove triangle j. t[0] = tris[tris.size()-4]; t[1] = tris[tris.size()-3]; t[2] = tris[tris.size()-2]; t[3] = tris[tris.size()-1]; tris.resize(tris.size()-4); j--; } } // Remove duplicate edges. const int ne = edges.size()/2; for (int j = 0; j < ne-1; ++j) { for (int k = j+1; k < ne; ++k) { // Dupe?, make null. if ((edges[j*2+0] == edges[k*2+1]) && (edges[j*2+1] == edges[k*2+0])) { edges[j*2+0] = 0; edges[j*2+1] = 0; edges[k*2+0] = 0; edges[k*2+1] = 0; } } } // Form new triangles for the current point // Skipping over any null. // All edges are arranged in clockwise order. for (int j = 0; j < ne; ++j) { if (edges[j*2+0] == edges[j*2+1]) continue; tris.push(edges[j*2+0]); tris.push(edges[j*2+1]); tris.push(i); tris.push(0); // not completed } } // Remove triangles with supertriangle vertices // These are triangles which have a vertex number greater than nv for (int i = 0; i < tris.size()/4; ++i) { int* t = &tris[i*4]; if (t[0] < 0 || t[1] < 0 || t[2] < 0) { t[0] = tris[tris.size()-4]; t[1] = tris[tris.size()-3]; t[2] = tris[tris.size()-2]; t[3] = tris[tris.size()-1]; tris.resize(tris.size()-4); i--; } } // Triangle vertices are pointing to sorted vertices, remap indices. for (int i = 0; i < tris.size(); ++i) tris[i] = idx[tris[i]]; }
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; }
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; }
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 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 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 false; } // 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; }
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->getNavMeshBoundsMin(); const float* bmax = m_geom->getNavMeshBoundsMax(); 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) { 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, bmin); 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; }
int main(int /*argc*/, char** /*argv*/) { // Init SDL if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { printf("Could not initialise SDL\n"); return -1; } // Center window char env[] = "SDL_VIDEO_CENTERED=1"; putenv(env); // Init OpenGL SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); //#ifndef WIN32 SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); //#endif const SDL_VideoInfo* vi = SDL_GetVideoInfo(); bool presentationMode = false; int width, height; SDL_Surface* screen = 0; if (presentationMode) { width = 1700; height = 1000; screen = SDL_SetVideoMode(width, height, 0, SDL_OPENGL|SDL_FULLSCREEN); } else { width = 1700; height = 1000; screen = SDL_SetVideoMode(width, height, 0, SDL_OPENGL); } if (!screen) { printf("Could not initialise SDL opengl\n"); return -1; } glEnable(GL_MULTISAMPLE); SDL_WM_SetCaption("Recast Demo", 0); if (!imguiRenderGLInit("DroidSans.ttf")) { printf("Could not init GUI renderer.\n"); SDL_Quit(); return -1; } float t = 0.0f; float timeAcc = 0.0f; Uint32 lastTime = SDL_GetTicks(); int mx = 0, my = 0; float rx = 45; float ry = -45; float moveW = 0, moveS = 0, moveA = 0, moveD = 0; float camx = 0, camy = 0, camz = 0, camr = 1000; float origrx = 0, origry = 0; int origx = 0, origy = 0; float scrollZoom = 0; bool rotate = false; bool movedDuringRotate = false; float rays[3], raye[3]; bool mouseOverMenu = false; bool showMenu = !presentationMode; bool showLog = false; bool showTools = true; bool showLevels = false; bool showSample = false; bool showTestCases = false; int propScroll = 0; int logScroll = 0; int toolsScroll = 0; char sampleName[64] = "Choose Sample..."; FileList files; char meshName[128] = "Choose Mesh..."; float mpos[3] = {0,0,0}; bool mposSet = false; SlideShow slideShow; slideShow.init("slides/"); InputGeom* geom = 0; Sample* sample = 0; TestCase* test = 0; BuildContext ctx; glEnable(GL_CULL_FACE); float fogCol[4] = { 0.32f, 0.31f, 0.30f, 1.0f }; glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, camr*0.1f); glFogf(GL_FOG_END, camr*1.25f); glFogfv(GL_FOG_COLOR, fogCol); glDepthFunc(GL_LEQUAL); bool done = false; while(!done) { // Handle input events. int mscroll = 0; bool processHitTest = false; bool processHitTestShift = false; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: // Handle any key presses here. if (event.key.keysym.sym == SDLK_ESCAPE) { done = true; } else if (event.key.keysym.sym == SDLK_t) { showLevels = false; showSample = false; showTestCases = true; scanDirectory("Tests", ".txt", files); } else if (event.key.keysym.sym == SDLK_TAB) { showMenu = !showMenu; } else if (event.key.keysym.sym == SDLK_SPACE) { if (sample) sample->handleToggle(); } else if (event.key.keysym.sym == SDLK_1) { if (sample) sample->handleStep(); } else if (event.key.keysym.sym == SDLK_9) { if (geom) geom->save("geomset.txt"); } else if (event.key.keysym.sym == SDLK_0) { delete geom; geom = new InputGeom; if (!geom || !geom->load(&ctx, "geomset.txt")) { delete geom; geom = 0; showLog = true; logScroll = 0; ctx.dumpLog("Geom load log %s:", meshName); } if (sample && geom) { sample->handleMeshChanged(geom); } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); } } else if (event.key.keysym.sym == SDLK_RIGHT) { slideShow.nextSlide(); } else if (event.key.keysym.sym == SDLK_LEFT) { slideShow.prevSlide(); } break; case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_RIGHT) { if (!mouseOverMenu) { // Rotate view rotate = true; movedDuringRotate = false; origx = mx; origy = my; origrx = rx; origry = ry; } } else if (event.button.button == SDL_BUTTON_WHEELUP) { if (mouseOverMenu) mscroll--; else scrollZoom -= 1.0f; } else if (event.button.button == SDL_BUTTON_WHEELDOWN) { if (mouseOverMenu) mscroll++; else scrollZoom += 1.0f; } break; case SDL_MOUSEBUTTONUP: // Handle mouse clicks here. if (event.button.button == SDL_BUTTON_RIGHT) { rotate = false; if (!mouseOverMenu) { if (!movedDuringRotate) { processHitTest = true; processHitTestShift = true; } } } else if (event.button.button == SDL_BUTTON_LEFT) { if (!mouseOverMenu) { processHitTest = true; processHitTestShift = (SDL_GetModState() & KMOD_SHIFT) ? true : false; } } break; case SDL_MOUSEMOTION: mx = event.motion.x; my = height-1 - event.motion.y; if (rotate) { int dx = mx - origx; int dy = my - origy; rx = origrx - dy*0.25f; ry = origry + dx*0.25f; if (dx*dx+dy*dy > 3*3) movedDuringRotate = true; } break; case SDL_QUIT: done = true; break; default: break; } } unsigned char mbut = 0; if (SDL_GetMouseState(0,0) & SDL_BUTTON_LMASK) mbut |= IMGUI_MBUT_LEFT; if (SDL_GetMouseState(0,0) & SDL_BUTTON_RMASK) mbut |= IMGUI_MBUT_RIGHT; Uint32 time = SDL_GetTicks(); float dt = (time - lastTime) / 1000.0f; lastTime = time; t += dt; // Hit test mesh. if (processHitTest && geom && sample) { float hitt; bool hit = geom->raycastMesh(rays, raye, hitt); if (hit) { if (SDL_GetModState() & KMOD_CTRL) { // Marker mposSet = true; mpos[0] = rays[0] + (raye[0] - rays[0])*hitt; mpos[1] = rays[1] + (raye[1] - rays[1])*hitt; mpos[2] = rays[2] + (raye[2] - rays[2])*hitt; } else { float pos[3]; pos[0] = rays[0] + (raye[0] - rays[0])*hitt; pos[1] = rays[1] + (raye[1] - rays[1])*hitt; pos[2] = rays[2] + (raye[2] - rays[2])*hitt; sample->handleClick(rays, pos, processHitTestShift); } } else { if (SDL_GetModState() & KMOD_CTRL) { // Marker mposSet = false; } } } // Update sample simulation. const float SIM_RATE = 20; const float DELTA_TIME = 1.0f/SIM_RATE; timeAcc = rcClamp(timeAcc+dt, -1.0f, 1.0f); int simIter = 0; while (timeAcc > DELTA_TIME) { timeAcc -= DELTA_TIME; if (simIter < 5) { if (sample) sample->handleUpdate(DELTA_TIME); } simIter++; } // Clamp the framerate so that we do not hog all the CPU. const float MIN_FRAME_TIME = 1.0f/40.0f; if (dt < MIN_FRAME_TIME) { int ms = (int)((MIN_FRAME_TIME - dt)*1000.0f); if (ms > 10) ms = 10; if (ms >= 0) SDL_Delay(ms); } // Update and render glViewport(0, 0, width, height); glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_TEXTURE_2D); // Render 3d glEnable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50.0f, (float)width/(float)height, 1.0f, camr); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(rx,1,0,0); glRotatef(ry,0,1,0); glTranslatef(-camx, -camy, -camz); // Get hit ray position and direction. GLdouble proj[16]; GLdouble model[16]; GLint view[4]; glGetDoublev(GL_PROJECTION_MATRIX, proj); glGetDoublev(GL_MODELVIEW_MATRIX, model); glGetIntegerv(GL_VIEWPORT, view); GLdouble x, y, z; gluUnProject(mx, my, 0.0f, model, proj, view, &x, &y, &z); rays[0] = (float)x; rays[1] = (float)y; rays[2] = (float)z; gluUnProject(mx, my, 1.0f, model, proj, view, &x, &y, &z); raye[0] = (float)x; raye[1] = (float)y; raye[2] = (float)z; // Handle keyboard movement. Uint8* keystate = SDL_GetKeyState(NULL); moveW = rcClamp(moveW + dt * 4 * (keystate[SDLK_w] ? 1 : -1), 0.0f, 1.0f); moveS = rcClamp(moveS + dt * 4 * (keystate[SDLK_s] ? 1 : -1), 0.0f, 1.0f); moveA = rcClamp(moveA + dt * 4 * (keystate[SDLK_a] ? 1 : -1), 0.0f, 1.0f); moveD = rcClamp(moveD + dt * 4 * (keystate[SDLK_d] ? 1 : -1), 0.0f, 1.0f); float keybSpeed = 22.0f; if (SDL_GetModState() & KMOD_SHIFT) keybSpeed *= 4.0f; float movex = (moveD - moveA) * keybSpeed * dt; float movey = (moveS - moveW) * keybSpeed * dt; movey += scrollZoom * 2.0f; scrollZoom = 0; camx += movex * (float)model[0]; camy += movex * (float)model[4]; camz += movex * (float)model[8]; camx += movey * (float)model[2]; camy += movey * (float)model[6]; camz += movey * (float)model[10]; glEnable(GL_FOG); if (sample) sample->handleRender(); if (test) test->handleRender(); glDisable(GL_FOG); // Render GUI glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, width, 0, height); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); mouseOverMenu = false; imguiBeginFrame(mx,my,mbut,mscroll); if (sample) { sample->handleRenderOverlay((double*)proj, (double*)model, (int*)view); } if (test) { if (test->handleRenderOverlay((double*)proj, (double*)model, (int*)view)) mouseOverMenu = true; } // Help text. if (showMenu) { const char msg[] = "W/S/A/D: Move RMB: Rotate"; imguiDrawText(280, height-20, IMGUI_ALIGN_LEFT, msg, imguiRGBA(255,255,255,128)); } if (showMenu) { if (imguiBeginScrollArea("Properties", width-250-10, 10, 250, height-20, &propScroll)) mouseOverMenu = true; if (imguiCheck("Show Log", showLog)) showLog = !showLog; if (imguiCheck("Show Tools", showTools)) showTools = !showTools; imguiSeparator(); imguiLabel("Sample"); if (imguiButton(sampleName)) { if (showSample) { showSample = false; } else { showSample = true; showLevels = false; showTestCases = false; } } imguiSeparator(); imguiLabel("Input Mesh"); if (imguiButton(meshName)) { if (showLevels) { showLevels = false; } else { showSample = false; showTestCases = false; showLevels = true; scanDirectory("Meshes", ".obj", files); } } if (geom) { char text[64]; snprintf(text, 64, "Verts: %.1fk Tris: %.1fk", geom->getMesh()->getVertCount()/1000.0f, geom->getMesh()->getTriCount()/1000.0f); imguiValue(text); } imguiSeparator(); if (geom && sample) { imguiSeparatorLine(); sample->handleSettings(); if (imguiButton("Build")) { ctx.resetLog(); if (!sample->handleBuild()) { showLog = true; logScroll = 0; } ctx.dumpLog("Build log %s:", meshName); // Clear test. delete test; test = 0; } imguiSeparator(); } if (sample) { imguiSeparatorLine(); sample->handleDebugMode(); } imguiEndScrollArea(); } // Sample selection dialog. if (showSample) { static int levelScroll = 0; if (imguiBeginScrollArea("Choose Sample", width-10-250-10-200, height-10-250, 200, 250, &levelScroll)) mouseOverMenu = true; Sample* newSample = 0; for (int i = 0; i < g_nsamples; ++i) { if (imguiItem(g_samples[i].name)) { newSample = g_samples[i].create(); if (newSample) strcpy(sampleName, g_samples[i].name); } } if (newSample) { delete sample; sample = newSample; sample->setContext(&ctx); if (geom && sample) { sample->handleMeshChanged(geom); } showSample = false; } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.1f); glFogf(GL_FOG_END, camr*1.25f); } imguiEndScrollArea(); } // Level selection dialog. if (showLevels) { static int levelScroll = 0; if (imguiBeginScrollArea("Choose Level", width-10-250-10-200, height-10-450, 200, 450, &levelScroll)) mouseOverMenu = true; int levelToLoad = -1; for (int i = 0; i < files.size; ++i) { if (imguiItem(files.files[i])) levelToLoad = i; } if (levelToLoad != -1) { strncpy(meshName, files.files[levelToLoad], sizeof(meshName)); meshName[sizeof(meshName)-1] = '\0'; showLevels = false; delete geom; geom = 0; char path[256]; strcpy(path, "Meshes/"); strcat(path, meshName); geom = new InputGeom; if (!geom || !geom->loadMesh(&ctx, path)) { delete geom; geom = 0; showLog = true; logScroll = 0; ctx.dumpLog("Geom load log %s:", meshName); } if (sample && geom) { sample->handleMeshChanged(geom); } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.1f); glFogf(GL_FOG_END, camr*1.25f); } } imguiEndScrollArea(); } // Test cases if (showTestCases) { static int testScroll = 0; if (imguiBeginScrollArea("Choose Test To Run", width-10-250-10-200, height-10-450, 200, 450, &testScroll)) mouseOverMenu = true; int testToLoad = -1; for (int i = 0; i < files.size; ++i) { if (imguiItem(files.files[i])) testToLoad = i; } if (testToLoad != -1) { char path[256]; strcpy(path, "Tests/"); strcat(path, files.files[testToLoad]); test = new TestCase; if (test) { // Load the test. if (!test->load(path)) { delete test; test = 0; } // Create sample Sample* newSample = 0; for (int i = 0; i < g_nsamples; ++i) { if (strcmp(g_samples[i].name, test->getSampleName()) == 0) { newSample = g_samples[i].create(); if (newSample) strcpy(sampleName, g_samples[i].name); } } if (newSample) { delete sample; sample = newSample; sample->setContext(&ctx); showSample = false; } // Load geom. strcpy(meshName, test->getGeomFileName()); meshName[sizeof(meshName)-1] = '\0'; delete geom; geom = 0; strcpy(path, "Meshes/"); strcat(path, meshName); geom = new InputGeom; if (!geom || !geom->loadMesh(&ctx, path)) { delete geom; geom = 0; showLog = true; logScroll = 0; ctx.dumpLog("Geom load log %s:", meshName); } if (sample && geom) { sample->handleMeshChanged(geom); } // This will ensure that tile & poly bits are updated in tiled sample. if (sample) sample->handleSettings(); ctx.resetLog(); if (sample && !sample->handleBuild()) { ctx.dumpLog("Build log %s:", meshName); } if (geom || sample) { const float* bmin = 0; const float* bmax = 0; if (sample) { bmin = sample->getBoundsMin(); bmax = sample->getBoundsMax(); } else if (geom) { bmin = geom->getMeshBoundsMin(); bmax = geom->getMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0]-bmin[0]) + rcSqr(bmax[1]-bmin[1]) + rcSqr(bmax[2]-bmin[2])) / 2; camx = (bmax[0] + bmin[0]) / 2 + camr; camy = (bmax[1] + bmin[1]) / 2 + camr; camz = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } rx = 45; ry = -45; glFogf(GL_FOG_START, camr*0.2f); glFogf(GL_FOG_END, camr*1.25f); } // Do the tests. if (sample) test->doTests(sample->getNavMesh(), sample->getNavMeshQuery()); } } imguiEndScrollArea(); } // Log if (showLog && showMenu) { if (imguiBeginScrollArea("Log", 250+20, 10, width - 300 - 250, 200, &logScroll)) mouseOverMenu = true; for (int i = 0; i < ctx.getLogCount(); ++i) imguiLabel(ctx.getLogText(i)); imguiEndScrollArea(); } // Tools if (!showTestCases && showTools && showMenu) // && geom && sample) { if (imguiBeginScrollArea("Tools", 10, 10, 250, height-20, &toolsScroll)) mouseOverMenu = true; if (sample) sample->handleTools(); imguiEndScrollArea(); } slideShow.updateAndDraw(dt, (float)width, (float)height); // Marker if (mposSet && gluProject((GLdouble)mpos[0], (GLdouble)mpos[1], (GLdouble)mpos[2], model, proj, view, &x, &y, &z)) { // Draw marker circle glLineWidth(5.0f); glColor4ub(240,220,0,196); glBegin(GL_LINE_LOOP); const float r = 25.0f; for (int i = 0; i < 20; ++i) { const float a = (float)i / 20.0f * RC_PI*2; const float fx = (float)x + cosf(a)*r; const float fy = (float)y + sinf(a)*r; glVertex2f(fx,fy); } glEnd(); glLineWidth(1.0f); } imguiEndFrame(); imguiRenderGLDraw(); glEnable(GL_DEPTH_TEST); SDL_GL_SwapBuffers(); } imguiRenderGLDestroy(); SDL_Quit(); delete sample; delete geom; return 0; }
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 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* 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. if (m_filterLowHangingObstacles) rcFilterLowHangingWalkableObstacles(m_ctx, m_cfg.walkableClimb, *m_solid); if (m_filterLedgeSpans) rcFilterLedgeSpans(m_ctx, m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); if (m_filterWalkableLowHeightSpans) 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.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; 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; }
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; }
void MapBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, float bmin[3], float bmax[3], dtNavMesh* navMesh) { // console output std::string tileString = Trinity::StringFormat("[Map %04u] [%02i,%02i]: ", mapID, tileX, tileY); printf("%s Building movemap tiles...\n", tileString.c_str()); 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.3333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc ) const static float BASE_UNIT_DIM = m_bigBaseUnit ? 0.5333333f : 0.2666666f; // 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; // a value >= 3|6 allows npcs to walk over some fences // a value >= 4|8 allows npcs to walk over all fences config.walkableClimb = m_bigBaseUnit ? 4 : 8; config.minRegionArea = rcSqr(60); config.mergeRegionArea = rcSqr(50); config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny 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 = 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*[TILES_PER_MAP * TILES_PER_MAP]; rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP]; int nmerge = 0; // 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] + 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; // 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("%s Failed building heightfield! \n", tileString.c_str()); 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("%s Failed compacting heightfield! \n", tileString.c_str()); continue; } // build polymesh intermediates if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf)) { printf("%s Failed eroding area! \n", tileString.c_str()); continue; } if (!rcBuildDistanceField(m_rcContext, *tile.chf)) { printf("%s Failed building distance field! \n", tileString.c_str()); continue; } if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea)) { printf("%s Failed building regions! \n", tileString.c_str()); continue; } tile.cset = rcAllocContourSet(); if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset)) { printf("%s Failed building contours! \n", tileString.c_str()); continue; } // build polymesh tile.pmesh = rcAllocPolyMesh(); if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh)) { printf("%s Failed building polymesh! \n", tileString.c_str()); continue; } tile.dmesh = rcAllocPolyMeshDetail(); if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh)) { printf("%s Failed building polymesh detail! \n", tileString.c_str()); 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; pmmerge[nmerge] = tile.pmesh; dmmerge[nmerge] = tile.dmesh; nmerge++; } } iv.polyMesh = rcAllocPolyMesh(); if (!iv.polyMesh) { printf("%s alloc iv.polyMesh FAILED!\n", tileString.c_str()); delete[] pmmerge; delete[] dmmerge; delete[] tiles; return; } rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh); iv.polyMeshDetail = rcAllocPolyMeshDetail(); if (!iv.polyMeshDetail) { printf("%s alloc m_dmesh FAILED!\n", tileString.c_str()); delete[] pmmerge; delete[] dmmerge; delete[] tiles; return; } rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail); // free things up delete[] pmmerge; delete[] dmmerge; delete[] tiles; // 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.tileLayer = 0; params.buildBvTree = true; // 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.c_str()); break; } if (params.vertCount >= 0xffff) { printf("%s Too many vertices! \n", tileString.c_str()); break; } 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.c_str()); break; } 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.c_str()); break; } if (!params.detailMeshes || !params.detailVerts || !params.detailTris) { printf("%s No detail mesh to build tile! \n", tileString.c_str()); break; } printf("%s Building navmesh tile...\n", tileString.c_str()); if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { printf("%s Failed building navmesh tile! \n", tileString.c_str()); break; } dtTileRef tileRef = 0; printf("%s Adding tile to navmesh...\n", tileString.c_str()); // 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.c_str()); break; } // file output char fileName[255]; sprintf(fileName, "mmaps/%04u%02i%02i.mmtile", mapID, tileY, tileX); FILE* file = fopen(fileName, "wb"); if (!file) { char message[1024]; sprintf(message, "[Map %04u] Failed to open %s for writing!\n", mapID, fileName); perror(message); navMesh->removeTile(tileRef, NULL, NULL); break; } printf("%s Writing to file...\n", tileString.c_str()); // 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); } }
/* * Tries to load vmap and tilemap for a gridtile and creates a navmesh for it. * */ bool ModelContainerView::generateMoveMapForTile (int pMapId, int x, int y) { bool result = iVMapManager.loadMap (gVMapDataDir.c_str (), pMapId, x, y) == VMAP_LOAD_RESULT_OK; if (result == VMAP_LOAD_RESULT_OK) { //VMap loaded. Add data from vmap to global Triangle-Array parseVMap (pMapId, x, y); } // Add data from Height-Map to global Triangle-Array generateHeightMap(pMapId,x,y); // We will now add all triangles inside the given zone to the vectormap. // We could also do additional checks here. double x_max = (32-x)*SIZE_OF_GRIDS + 50; double y_max = (32-y)*SIZE_OF_GRIDS + 50; double x_min = x_max - SIZE_OF_GRIDS - 100; double y_min = y_max - SIZE_OF_GRIDS - 100; Vector3 low = Vector3(x_min,y_min,-inf()); Vector3 high = Vector3(x_max,y_max,inf()); AABox checkBox = AABox(low,high); AABox check; Triangle t; //each triangle has mangos format. for (int i = 0; i < globalTriangleArray.size(); i++) { t = globalTriangleArray[i]; t.getBounds(check); if (checkBox.contains(check)) { // Write it down in detour format. iGlobArray.append(t.vertex(0).y,t.vertex(0).z,t.vertex(0).x); iGlobArray.append(t.vertex(1).y,t.vertex(1).z,t.vertex(1).x); iGlobArray.append(t.vertex(2).y,t.vertex(2).z,t.vertex(2).x); } } if (iGlobArray.size() == 0) { printf("No models - check your mmap.datadir in your config"); return true; } if(gMakeObjFile) debugGenerateObjFile(); // create obj file for Recast Demo viewer //return true; float bmin[3], bmax[3]; /* * The format looks like this * Verticle = float[3] * Triangle = Verticle[3] * So there are * array.size() floats * that means there are * nverts = array.size()/3 Verticles * that means there are * ntris = nverts/3 */ //array/3 verticles const int nverts = iGlobArray.size()/3; // because 1 vert is 3 float. // -> vert = float[3] const float* verts = iGlobArray.getCArray(); rcCalcBounds(verts,nverts,bmin,bmax); // nverts/3 triangles // -> Triangle = vert[3] = float[9] int* tris = new int[nverts];// because 1 triangle is 3 verts for (int i = 0; i< nverts; i++) tris[i] = i; /* tris[i] = 1,2,3;4,5,6;7,8,9; * */ const int ntris = (nverts/3); rcConfig m_cfg; // // Step 1. Initialize build config. // // Init build configuration from GUI memset(&m_cfg, 0, sizeof(m_cfg)); // Change config settings here! m_cfg.cs = 0.3f; m_cfg.ch = 0.2f; m_cfg.walkableSlopeAngle = 50.0f; m_cfg.walkableHeight = 10; m_cfg.walkableClimb = 4; m_cfg.walkableRadius = 2; m_cfg.maxEdgeLen = (int)(12 / 0.3f); m_cfg.maxSimplificationError = 1.3f; m_cfg.minRegionSize = (int)rcSqr(50); m_cfg.mergeRegionSize = (int)rcSqr(20); m_cfg.maxVertsPerPoly = (int)6; m_cfg.detailSampleDist = 1.8f; m_cfg.detailSampleMaxError = 0.2f * 1; bool m_keepInterResults = false; printf("CellSize : %.2f\n",m_cfg.cs); printf("CellHeight : %.2f\n",m_cfg.ch); printf("WalkableSlope : %.2f\n",m_cfg.walkableSlopeAngle); printf("WalkableHeight : %i\n",m_cfg.walkableHeight); printf("walkableClimb : %i\n",m_cfg.walkableClimb); printf("walkableRadius : %i\n",m_cfg.walkableRadius); printf("maxEdgeLen : %i\n",m_cfg.maxEdgeLen); printf("maxSimplific.Er.: %.2f\n",m_cfg.maxSimplificationError); printf("minRegionSize : %i\n",m_cfg.minRegionSize); printf("mergedRegSize : %i\n",m_cfg.mergeRegionSize); printf("maxVertsPerPoly : %i\n",m_cfg.maxVertsPerPoly); printf("detailSampledist: %.2f\n",m_cfg.detailSampleDist); printf("det.Samp.max.err: %.2f\n",m_cfg.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. vcopy(m_cfg.bmin, bmin); vcopy(m_cfg.bmax, bmax); rcCalcGridSize(m_cfg.bmin, m_cfg.bmax, m_cfg.cs, &m_cfg.width, &m_cfg.height); // // Step 2. Rasterize input polygon soup. // // Allocate voxel heighfield where we rasterize our input data to. rcHeightfield* m_solid = new rcHeightfield; if (!m_solid) { printf("buildNavigation: Out of memory 'solid'.\n"); return false; } if (!rcCreateHeightfield(*m_solid, m_cfg.width, m_cfg.height, m_cfg.bmin, m_cfg.bmax, m_cfg.cs, m_cfg.ch)) { printf("buildNavigation: Could not create solid heightfield.\n"); return false; } // Allocate array that can hold triangle flags. // If you have multiple meshes you need to process, allocate // and array which can hold the max number of triangles you need to process. unsigned char* m_triflags = new unsigned char[ntris]; if (!m_triflags) { printf("buildNavigation: Out of memory 'triangleFlags' (%d).\n", 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 flags for each of the meshes and rasterize them. memset(m_triflags, 0, ntris*sizeof(unsigned char)); rcMarkWalkableTriangles(m_cfg.walkableSlopeAngle, verts, nverts, tris, ntris, m_triflags); rcRasterizeTriangles(verts, nverts, tris, m_triflags, ntris, *m_solid, m_cfg.walkableClimb); // should delete [] verts? - probably not, this is just pointer to data in a G3D Array // should delete [] tris? if (!m_keepInterResults) { delete [] m_triflags; m_triflags = 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_cfg.walkableClimb, *m_solid); rcFilterLedgeSpans(m_cfg.walkableHeight, m_cfg.walkableClimb, *m_solid); rcFilterWalkableLowHeightSpans(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. rcCompactHeightfield* m_chf = new rcCompactHeightfield; if (!m_chf) { printf("buildNavigation: Out of memory 'chf'.\n"); return false; } if (!rcBuildCompactHeightfield(m_cfg.walkableHeight, m_cfg.walkableClimb, RC_WALKABLE, *m_solid, *m_chf)) { printf( "buildNavigation: Could not build compact data.\n"); return false; } if (!m_keepInterResults) { delete m_solid; m_solid = 0; } // Erode the walkable area by agent radius. if (!rcErodeArea(RC_WALKABLE_AREA, m_cfg.walkableRadius, *m_chf)) { printf("buildNavigation: Could not erode.\n"); return false; } // (Optional) Mark areas. //const ConvexVolume* vols = m_geom->getConvexVolumes(); //for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) // rcMarkConvexPolyArea(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_chf)) { printf("buildNavigation: Could not build distance field.\n"); return false; } // Partition the walkable surface into simple regions without holes. if (!rcBuildRegions(*m_chf, m_cfg.borderSize, m_cfg.minRegionSize, m_cfg.mergeRegionSize)) { printf("buildNavigation: Could not build regions.\n"); } // // Step 5. Trace and simplify region contours. // // Create contours. rcContourSet* m_cset = new rcContourSet; if (!m_cset) { printf("buildNavigation: Out of memory 'cset'.\n"); return false; } if (!rcBuildContours(*m_chf, m_cfg.maxSimplificationError, m_cfg.maxEdgeLen, *m_cset)) { printf("buildNavigation: Could not create contours.\n"); return false; } // // Step 6. Build polygons mesh from contours. // // Build polygon navmesh from the contours. rcPolyMesh* m_pmesh = new rcPolyMesh; if (!m_pmesh) { printf("buildNavigation: Out of memory 'pmesh'.\n"); return false; } if (!rcBuildPolyMesh(*m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) { printf( "buildNavigation: Could not triangulate contours.\n"); return false; } // // Step 7. Create detail mesh which allows to access approximate height on each polygon. // rcPolyMeshDetail* m_dmesh = new rcPolyMeshDetail; if (!m_dmesh) { printf("buildNavigation: Out of memory 'pmdtl'.\n"); return false; } if (!rcBuildPolyMeshDetail(*m_pmesh, *m_chf, m_cfg.detailSampleDist, m_cfg.detailSampleMaxError, *m_dmesh)) { printf("buildNavigation: Could not build detail mesh.\n"); } if (!m_keepInterResults) { delete m_chf; m_chf = 0; delete 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) { // for now all generated navmesh is walkable by everyone. // else there will be no pathfinding at all! m_pmesh->flags[i] = RC_WALKABLE_AREA; } 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 = 0; params.offMeshConRad = 0; params.offMeshConDir = 0; params.offMeshConAreas = 0; params.offMeshConFlags = 0; params.offMeshConCount = 0; params.walkableHeight = 2.0f; params.walkableRadius = 0.6f; params.walkableClimb = 0.9f; vcopy(params.bmin, m_pmesh->bmin); vcopy(params.bmax, m_pmesh->bmax); params.cs = m_cfg.cs; params.ch = m_cfg.ch; printf("vertcount : %05u\n",params.vertCount); printf("polycount : %05u\n",params.polyCount); printf("detailVertsCount: %05u\n",params.detailVertsCount); printf("detailTriCount : %05u\n",params.detailTriCount); printf("walkableClimb : %.2f\n",params.walkableClimb); printf("walkableRadius : %.2f\n",params.walkableRadius); printf("walkableHeight : %.2f\n",params.walkableHeight); if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { printf("Could not build Detour navmesh.\n"); return false; } // navData now contains the MoveMap printf("Generated Navigation Mesh! Size: %i bytes/ %i kB / %i MB\n",navDataSize,navDataSize/1024,navDataSize/(1024*1024)); char tmp[14]; sprintf(tmp, "%03u%02u%02u.mmap",iMap,ix,iy); std::string savefilepath = gMMapDataDir + "/" + tmp; ofstream inf( savefilepath.c_str(),ofstream::binary ); if( inf ) { inf.write( (char*)( &navData[0] ), navDataSize ) ; } printf("MoveMap saved under %s\n", savefilepath.c_str()); delete [] navData; } // debugLoadNavMesh(); return (result); }