Help me with auto generation offmesh links, in my implementation it finds links undermap or where player can't jump or climb: void NavMeshBuilder::FindClimbingPoints(float maxClimbHeight) { const float* verts = m_World->GetStaticWorldMesh().getVerts(); const int* tris = m_World->GetStaticWorldMesh().getTris(); const int nTris = m_World->GetStaticWorldMesh().getTriCount(); unsigned int threadCount = std::max(1u, std::thread::hardware_concurrency()); std::vector<std::thread> threads; std::atomic<int> progress(0); std::mutex printMutex; std::mutex worldMutex; std::mutex coutMutex; auto worker = [&](int start, int end) { for (int i = start; i < end; ++i) { float normal[3]; { std::lock_guard<std::mutex> lock(worldMutex); m_World->GetStaticWorldMesh().calculateNormal(i, normal); } if (normal[1] > 0.8f) { int vertA = tris[i * 3]; int vertB = tris[i * 3 + 1]; int vertC = tris[i * 3 + 2]; float edgeAB[3], edgeAC[3]; rcVsub(edgeAB, &verts[vertB * 3], &verts[vertA * 3]); rcVsub(edgeAC, &verts[vertC * 3], &verts[vertA * 3]); { std::lock_guard<std::mutex> lock(worldMutex); CheckEdgeForClimbing(edgeAB, &verts[vertA * 3], maxClimbHeight); CheckEdgeForClimbing(edgeAC, &verts[vertA * 3], maxClimbHeight); } } int currentProgress = ++progress; if (currentProgress % (nTris / 100) == 0) { float percentage = (float)currentProgress / nTris * 100; std::lock_guard<std::mutex> lock(coutMutex); std::cout << "\r" << "Climbing" << " points: " << std::fixed << std::setprecision(1) << percentage << "% " << std::flush; if (getOffMeshConnectionCount() > 10) return; } } }; int trisPerThread = nTris / threadCount; for (unsigned int i = 0; i < threadCount; ++i) { int start = i * trisPerThread; int end = (i == threadCount - 1) ? nTris : (i + 1) * trisPerThread; threads.emplace_back(worker, start, end); } for (auto& thread : threads) { thread.join(); } { std::lock_guard<std::mutex> lock(coutMutex); std::cout << std::endl; } } void NavMeshBuilder::CheckEdgeForClimbing(const float* edge, const float* startPoint, float maxClimbHeight) { float edgeLength = rcVlen(edge); const float stepSize = 0.5f; // Check every 0.5 units along the edge for (float t = 0; t <= 1.0f; t += stepSize / edgeLength) { float point[3]; rcVmad(point, startPoint, edge, t); float groundPoint[3]; rcVcopy(groundPoint, point); groundPoint[1] -= maxClimbHeight; // Raycast from the edge point down to find the ground glm::vec3 start(point[0], point[1], point[2]); glm::vec3 end(groundPoint[0], groundPoint[1], groundPoint[2]); auto hitPoint = m_World->Raycast(start, end); if (hitPoint) { float height = point[1] - (*hitPoint).y; // Additional validation: Raycast in several directions to ensure it's not inside an enclosed area bool isInOpenSpace = ValidateOpenSpace(*hitPoint); if (height > 0.5f && height <= maxClimbHeight && isInOpenSpace) { AddOffMeshConnection(&(*hitPoint).x, point, 0.3f, 1, SAMPLE_POLYAREA_CLIMB, SAMPLE_POLYFLAGS_CLIMB); } } } } void NavMeshBuilder::FindJumpingPoints(float maxJumpHeight, float maxJumpDistance) { const float* verts = m_World->GetStaticWorldMesh().getVerts(); const int* tris = m_World->GetStaticWorldMesh().getTris(); const int nTris = m_World->GetStaticWorldMesh().getTriCount(); unsigned int threadCount = std::max(1u, std::thread::hardware_concurrency()); std::vector<std::thread> threads; std::atomic<int> progress(0); std::mutex printMutex; std::mutex worldMutex; std::mutex coutMutex; auto worker = [&](int start, int end) { for (int i = start; i < end; ++i) { float normal[3]; { std::lock_guard<std::mutex> lock(worldMutex); m_World->GetStaticWorldMesh().calculateNormal(i, normal); } if (normal[1] > 0.8f) { int vertA = tris[i * 3]; int vertB = tris[i * 3 + 1]; int vertC = tris[i * 3 + 2]; float center[3]; rcVadd(center, &verts[vertA * 3], &verts[vertB * 3]); rcVadd(center, center, &verts[vertC * 3]); rcVscale(center, center, 1.0f / 3.0f); { std::lock_guard<std::mutex> lock(worldMutex); CheckJumpConnections(center, maxJumpHeight, maxJumpDistance); } } int currentProgress = ++progress; if (currentProgress % (nTris / 100) == 0) { float percentage = (float)currentProgress / nTris * 100; std::lock_guard<std::mutex> lock(coutMutex); std::cout << "\r" << "Jumping" << " points: " << std::fixed << std::setprecision(1) << percentage << "% " << std::flush; if (getOffMeshConnectionCount() > 10) return; } } }; int trisPerThread = nTris / threadCount; for (unsigned int i = 0; i < threadCount; ++i) { int start = i * trisPerThread; int end = (i == threadCount - 1) ? nTris : (i + 1) * trisPerThread; threads.emplace_back(worker, start, end); } for (auto& thread : threads) { thread.join(); } { std::lock_guard<std::mutex> lock(coutMutex); std::cout << std::endl; } } void NavMeshBuilder::CheckJumpConnections(const float* startPoint, float maxJumpHeight, float maxJumpDistance) { const int angleStep = 30; // Check every 30 degrees around the point for (int angle = 0; angle < 360; angle += angleStep) { float rad = angle * 3.14159f / 180.0f; float endPoint[3]; endPoint[0] = startPoint[0] + cosf(rad) * maxJumpDistance; endPoint[1] = startPoint[1] - maxJumpHeight; endPoint[2] = startPoint[2] + sinf(rad) * maxJumpDistance; glm::vec3 start(startPoint[0], startPoint[1], startPoint[2]); glm::vec3 end(endPoint[0], endPoint[1], endPoint[2]); auto hitPoint = m_World->Raycast(start, end); if (hitPoint) { float jumpDist = rcVdist(startPoint, &(*hitPoint).x); float heightDiff = startPoint[1] - (*hitPoint).y; if (jumpDist <= maxJumpDistance && heightDiff <= maxJumpHeight) { AddOffMeshConnection(startPoint, &(*hitPoint).x, 0.3f, 0, SAMPLE_POLYAREA_JUMP, SAMPLE_POLYFLAGS_JUMP); } } } } void NavMeshBuilder::AddOffMeshConnection(const float* start, const float* end, float radius, unsigned char bidir, unsigned char area, unsigned short flags) { //std::cout << "AddOffMeshConnection: start {" << start[0] << " " << // start[1] << " " << start[2] << "} end: { " << end[0] << " " << // end[1] << " " << end[2] << std::endl; OffMeshConnection conn; std::memcpy(conn.startPos, start, sizeof(float) * 3); std::memcpy(conn.endPos, end, sizeof(float) * 3); conn.radius = radius; conn.bidir = bidir; conn.area = area; conn.flags = flags; std::lock_guard<std::mutex> lock(m_offMeshDataMutex); conn.id = m_nextOffMeshConId++; m_offMeshConnections.push_back(conn); // Update the data vectors m_offMeshConVerts.insert(m_offMeshConVerts.end(), start, start + 3); m_offMeshConVerts.insert(m_offMeshConVerts.end(), end, end + 3); m_offMeshConRads.push_back(radius); m_offMeshConDirs.push_back(bidir); m_offMeshConAreas.push_back(area); m_offMeshConFlags.push_back(flags); m_offMeshConId.push_back(conn.id); }