diff --git a/src/tools/pcb_reader/cache.h b/src/tools/pcb_reader/cache.h index 771ef2dc..a6cbced7 100644 --- a/src/tools/pcb_reader/cache.h +++ b/src/tools/pcb_reader/cache.h @@ -75,7 +75,19 @@ private: { try { - return std::make_shared< T >( &buf[0] ); + auto pFile = std::make_shared< T >( &buf[0] ); + + m_totalFiles++; + if( m_totalFiles % 1000 == 0 ) + { + m_lgbCache.clear(); + m_sgbCache.clear(); + m_pcbCache.clear(); + std::cout << "Purged PCB/SGB/PCB cache \n"; + m_totalFiles = 1; + } + + return pFile; } catch( std::exception& e ) { @@ -102,11 +114,13 @@ private: return empty; } } + std::mutex m_mutex; xiv::dat::GameData* m_pData; std::map< std::string, std::shared_ptr< LGB_FILE > > m_lgbCache; std::map< std::string, std::shared_ptr< SGB_FILE > > m_sgbCache; std::map< std::string, std::shared_ptr< PCB_FILE > > m_pcbCache; + int m_totalFiles{0}; }; #endif \ No newline at end of file diff --git a/src/tools/pcb_reader/navmesh_exporter.h b/src/tools/pcb_reader/navmesh_exporter.h index ca4763d5..e2727eb2 100644 --- a/src/tools/pcb_reader/navmesh_exporter.h +++ b/src/tools/pcb_reader/navmesh_exporter.h @@ -9,12 +9,17 @@ #include #include "exporter.h" - +/* #include #include #include #include - +#include +#include +#include +#include +#include +*/ class NavmeshExporter { public: @@ -23,7 +28,7 @@ public: auto start = std::chrono::high_resolution_clock::now(); auto fileName = zone.name + ".obj"; - + auto end = std::chrono::high_resolution_clock::now(); printf( "[Navmesh] Finished exporting %s in %u ms\n", fileName.c_str(), @@ -43,9 +48,335 @@ public: std::chrono::duration_cast< std::chrono::milliseconds >( end - start ).count() ); } private: - static void exportGroup( const ExportedGroup& group, std::ofstream& of, int& indicesOffset, int& modelCount ) + /*/ + static unsigned char* buildTileMesh( const ExportedGroup& group, int tx, int ty ) { + unsigned char* navData; + rcConfig cfg; + cfg.ch = 0.2f; + cfg.cs = 0.2f; + cfg.walkableHeight = 2.f; + cfg.walkableRadius = 0.5; + cfg.walkableClimb = 0.6; + cfg.walkableSlopeAngle = 58.f; + cfg.minRegionArea = 8.0f; + cfg.mergeRegionArea = 20.f; + cfg.maxEdgeLen = 12.f; + cfg.maxSimplificationError = 1.4f; + cfg.maxVertsPerPoly = 6.f; + cfg.detailSampleDist = 6.f; + cfg.detailSampleMaxError = 1.f; + cfg.tileSize = 160.f; + + cfg.walkableHeight = (int)ceilf( cfg.walkableHeight / cfg.ch ); + cfg.walkableClimb = (int)floorf( cfg.walkableClimb / cfg.ch ); + cfg.walkableRadius = (int)ceilf( cfg.walkableRadius / cfg.cs ); + cfg.maxEdgeLen = (int)( cfg.maxEdgeLen / cfg.cs ); + cfg.minRegionArea = (int)rcSqr( cfg.minRegionArea ); // Note: area = size*size + cfg.mergeRegionArea = (int)rcSqr( cfg.mergeRegionArea ); // Note: area = size*size + cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding. + cfg.width = cfg.tileSize + cfg.borderSize*2; + cfg.height = cfg.tileSize + cfg.borderSize*2; + cfg.detailSampleDist = cfg.detailSampleDist < 0.9f ? 0 : cfg.cs * cfg.detailSampleDist; + cfg.detailSampleMaxError = cfg.ch * cfg.detailSampleMaxError; + + rcContext ctx; + auto hf = rcAllocHeightfield(); + auto chf = rcAllocCompactHeightfield(); + auto cs = rcAllocContourSet(); + auto pmesh = rcAllocPolyMesh(); + auto pdetailmesh = rcAllocPolyMeshDetail(); + + std::vector< float > verts; + std::vector< int > indices; + + int i = 0; + int numIndices = 0; + for( const auto& model : group.models ) + { + for( const auto& mesh : model.second.meshes ) + { + auto size = mesh.verts.size(); + rcCalcBounds( mesh.verts.data(), size / 3, &cfg.bmin[0], &cfg.bmax[0] ); + verts.reserve( verts.size() + size ); + memcpy( &verts[i], mesh.verts.data(), size ); + i += size; + + size = mesh.indices.size(); + indices.reserve( indices.size() + size ); + for( auto j = 0; j < mesh.indices.size(); j += 3 ) + { + indices[j] = mesh.indices[j] + numIndices; + indices[j + 1] = mesh.indices[j + 1] + numIndices; + indices[j + 2] = mesh.indices[j + 2] + numIndices; + } + numIndices += size; + } + } + + auto chunkyMesh = new rcChunkyTriMesh; + rcCreateChunkyTriMesh( &verts[0], &indices[0], verts.size() / 3, 256, chunkyMesh ); + if( !rcCreateHeightfield( &ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch ) ) + { + + } + float tbmin[2], tbmax[2]; + tbmin[0] = cfg.bmin[0]; + tbmin[1] = cfg.bmin[2]; + tbmax[0] = cfg.bmax[0]; + tbmax[1] = cfg.bmax[2]; + int cid[512];// TODO: Make grow when returning too many items. + const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); + if (!ncid) + return 0; + + auto tileTriCount = 0; + auto triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; + 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; + + tileTriCount += nctris; + + memset(triareas, 0, nctris*sizeof(unsigned char)); + rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, + &verts[0], verts.size() / 3, ctris, nctris, triareas); + + if (!rcRasterizeTriangles(&ctx, &verts[0], verts.size() / 3, ctris, triareas, nctris, *hf, cfg.walkableClimb)) + return 0; + } + + { + delete [] triareas; + triareas = 0; + } + + // Once all geometry is rasterized, we do initial pass of filtering to + // remove unwanted overhangs caused by the conservative rasterization + // as well as filter spans where the character cannot possibly stand. + + rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf); + + rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf); + rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *hf); + + // 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. + chf = rcAllocCompactHeightfield(); + if (!chf) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); + return 0; + } + if (!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf)) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); + return 0; + } + + + { + rcFreeHeightField(hf); + hf = 0; + } + + // Erode the walkable area by agent radius. + if (!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf)) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Could not erode."); + return 0; + } + + + // 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(&ctx, *chf)) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build distance field."); + return 0; + } + + // Partition the walkable surface into simple regions without holes. + if (!rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + { + 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(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + // { + // 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(&ctx, *chf, cfg.borderSize, cfg.minRegionArea)) + // { + // ctx.log(RC_LOG_ERROR, "buildNavigation: Could not build layer regions."); + // return 0; + // } + //} + + // Create contours. + cs = rcAllocContourSet(); + if (!cs) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'cset'."); + return 0; + } + if (!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cs)) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Could not create contours."); + return 0; + } + + if (cs->nconts == 0) + { + return 0; + } + + // Build polygon navmesh from the contours. + pmesh = rcAllocPolyMesh(); + if (!pmesh) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'pmesh'."); + return 0; + } + if (!rcBuildPolyMesh(&ctx, *cs, cfg.maxVertsPerPoly, *pmesh)) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Could not triangulate contours."); + return 0; + } + + // Build detail mesh. + pdetailmesh = rcAllocPolyMeshDetail(); + if (!pdetailmesh) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Out of memory 'dmesh'."); + return 0; + } + + if (!rcBuildPolyMeshDetail(&ctx, *pmesh, *chf, + cfg.detailSampleDist, cfg.detailSampleMaxError, + *pdetailmesh)) + { + ctx.log(RC_LOG_ERROR, "buildNavigation: Could build polymesh detail."); + return 0; + } + + { + rcFreeCompactHeightfield(chf); + chf = 0; + rcFreeContourSet(cs); + cs = 0; + } + + unsigned char* navData = 0; + int navDataSize = 0; + if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) + { + if (pmesh->nverts >= 0xffff) + { + // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. + ctx.log(RC_LOG_ERROR, "Too many vertices per tile %d (max: %d).", pmesh->nverts, 0xffff); + return 0; + } + + // Update poly flags from areas. + for (int i = 0; i < pmesh->npolys; ++i) + { + //pmesh->flags[i] = sampleAreaToFlags(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 = pdetailmesh->meshes; + params.detailVerts = pdetailmesh->verts; + params.detailVertsCount = pdetailmesh->nverts; + params.detailTris = pdetailmesh->tris; + params.detailTriCount = pdetailmesh->ntris; + params.offMeshConVerts = 0; + params.offMeshConRad = 0; + params.offMeshConDir = 0; + params.offMeshConAreas = 0; + params.offMeshConFlags = 0; + params.offMeshConUserID = 0; + params.offMeshConCount = 0; + params.walkableHeight = cfg.walkableHeight; + params.walkableRadius = cfg.walkableRadius; + params.walkableClimb = cfg.walkableClimb; + params.tileX = 0; + params.tileY = 0; + params.tileLayer = 0; + rcVcopy(params.bmin, pmesh->bmin); + rcVcopy(params.bmax, pmesh->bmax); + params.cs = cfg.cs; + params.ch = cfg.ch; + params.buildBvTree = true; + + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + { + ctx.log(RC_LOG_ERROR, "Could not build Detour navmesh."); + return 0; + } + } + auto tileMemUsage = navDataSize/1024.0f; + + ctx.stopTimer(RC_TIMER_TOTAL); + + // Show performance stats. + //duLogBuildTimes(*&ctx, ctx.getAccumulatedTime(RC_TIMER_TOTAL)); + ctx.log(RC_LOG_PROGRESS, ">> Polymesh: %d vertices %d polygons", pmesh->nverts, pmesh->npolys); + + auto tileBuildTime = ctx.getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; + + auto dataSize = navDataSize; + return navData; } + //*/ }; #endif // !OBJ_EXPORTER_H diff --git a/src/tools/pcb_reader/threadpool.h b/src/tools/pcb_reader/threadpool.h index 7ae96891..ebf4243d 100644 --- a/src/tools/pcb_reader/threadpool.h +++ b/src/tools/pcb_reader/threadpool.h @@ -25,6 +25,9 @@ public: void addWorkers( unsigned int num ) { + + std::unique_lock lock( m_mutex ); + m_runFlag = true; if( num == 0 ) num = std::thread::hardware_concurrency() - 1; @@ -52,20 +55,23 @@ public: { std::unique_lock lock( m_mutex ); m_pendingJobs.clear(); + for( auto&& worker : m_workers ) + { + m_pendingJobs.emplace( {} ); + } } - complete(); + m_cv.notify_all(); + m_workers.clear(); } bool complete() { - { - std::scoped_lock lock( m_mutex ); - for( auto&& worker : m_workers ) - { - m_pendingJobs.push_back( {} ); - } - } m_cv.notify_all(); + { + std::unique_lock lock( m_mutex ); + m_runFlag = false; + m_cv.wait( lock, [&]{ return m_pendingJobs.empty(); } ); + } m_workers.clear(); return true; } @@ -76,9 +82,14 @@ private: { std::packaged_task< void() > func; { - std::unique_lock< std::mutex > lock( m_mutex ); + std::unique_lock lock( m_mutex ); if( m_pendingJobs.empty() ) { + if( !m_runFlag ) + { + m_cv.notify_all(); + return; + } m_cv.wait( lock, [&](){ return !m_pendingJobs.empty(); } ); } func = std::move( m_pendingJobs.front() ); @@ -92,6 +103,7 @@ private: } } + bool m_runFlag{ true }; std::mutex m_mutex; std::condition_variable m_cv; std::deque< std::packaged_task< void() > > m_pendingJobs;