Help me with mesh building. My meshes is all z-up, how to build recast with that? void Mesh::addVertex(float x, float y, float z) { m_verts.push_back(x * m_scale); m_verts.push_back(y * m_scale); m_verts.push_back(z * m_scale); m_vertCount++; } void Mesh::addTriangle(int a, int b, int c) { m_tris.push_back(a); m_tris.push_back(b); m_tris.push_back(c); m_triCount++; } void Mesh::addBox(float centerX, float centerY, float centerZ, float width, float height, float depth) { float halfWidth = width * 0.5f; float halfHeight = height * 0.5f; float halfDepth = depth * 0.5f; // Add vertices int v0 = m_vertCount; addVertex(centerX - halfWidth, centerY - halfHeight, centerZ - halfDepth); addVertex(centerX + halfWidth, centerY - halfHeight, centerZ - halfDepth); addVertex(centerX + halfWidth, centerY + halfHeight, centerZ - halfDepth); addVertex(centerX - halfWidth, centerY + halfHeight, centerZ - halfDepth); addVertex(centerX - halfWidth, centerY - halfHeight, centerZ + halfDepth); addVertex(centerX + halfWidth, centerY - halfHeight, centerZ + halfDepth); addVertex(centerX + halfWidth, centerY + halfHeight, centerZ + halfDepth); addVertex(centerX - halfWidth, centerY + halfHeight, centerZ + halfDepth); // Add triangles addTriangle(v0, v0 + 1, v0 + 2); addTriangle(v0, v0 + 2, v0 + 3); addTriangle(v0 + 1, v0 + 5, v0 + 6); addTriangle(v0 + 1, v0 + 6, v0 + 2); addTriangle(v0 + 5, v0 + 4, v0 + 7); addTriangle(v0 + 5, v0 + 7, v0 + 6); addTriangle(v0 + 4, v0 + 0, v0 + 3); addTriangle(v0 + 4, v0 + 3, v0 + 7); addTriangle(v0 + 3, v0 + 2, v0 + 6); addTriangle(v0 + 3, v0 + 6, v0 + 7); addTriangle(v0 + 4, v0 + 5, v0 + 1); addTriangle(v0 + 4, v0 + 1, v0 + 0); } bool MeshLoader::loadMeshes() { std::vector<IPLObject> modelPlacements; std::unordered_map<uint16_t, MeshObject> meshesObjs; if (!ReadMapMeshes("output_new.cadf", modelPlacements, meshesObjs)) { modelPlacements.clear(); meshesObjs.clear(); return false; } m_Meshes.reserve(meshesObjs.size()); for (const auto& [modelId, meshObj] : meshesObjs) { Mesh& mesh = m_Meshes[modelId]; // Process spheres for (const auto& sphere : meshObj.spheres) { mesh.addSphere(sphere.radius, 16, 8, sphere.center.x, sphere.center.y, sphere.center.z); } // Process boxes for (const auto& box : meshObj.boxes) { float corners[8][3] = { {box.min.x, box.min.y, box.min.z}, {box.max.x, box.min.y, box.min.z}, {box.min.x, box.max.y, box.min.z}, {box.max.x, box.max.y, box.min.z}, {box.min.x, box.min.y, box.max.z}, {box.max.x, box.min.y, box.max.z}, {box.min.x, box.max.y, box.max.z}, {box.max.x, box.max.y, box.max.z} }; int v0 = mesh.getVertCount(); for (int i = 0; i < 8; ++i) { mesh.addVertex(corners[i][0], corners[i][1], corners[i][2]); } // Add triangles for each face of the box mesh.addTriangle(v0 + 0, v0 + 1, v0 + 2); mesh.addTriangle(v0 + 1, v0 + 3, v0 + 2); mesh.addTriangle(v0 + 4, v0 + 6, v0 + 5); mesh.addTriangle(v0 + 5, v0 + 6, v0 + 7); mesh.addTriangle(v0 + 0, v0 + 2, v0 + 4); mesh.addTriangle(v0 + 2, v0 + 6, v0 + 4); mesh.addTriangle(v0 + 1, v0 + 5, v0 + 3); mesh.addTriangle(v0 + 5, v0 + 7, v0 + 3); mesh.addTriangle(v0 + 2, v0 + 3, v0 + 6); mesh.addTriangle(v0 + 3, v0 + 7, v0 + 6); mesh.addTriangle(v0 + 4, v0 + 5, v0 + 0); mesh.addTriangle(v0 + 5, v0 + 1, v0 + 0); } // Process faces for (const auto& face : meshObj.faces) { int v0 = mesh.getVertCount(); mesh.addVertex(face.a.x, face.a.y, face.a.z); mesh.addVertex(face.b.x, face.b.y, face.b.z); mesh.addVertex(face.c.x, face.c.y, face.c.z); mesh.addTriangle(v0, v0 + 1, v0 + 2); } } static uint32_t uid = 0; // Store placements as instances m_MeshPlacementData.reserve(modelPlacements.size()); for (const auto& placement : modelPlacements) { if (m_Meshes.find(placement.ModelID) != m_Meshes.end()) { m_MeshPlacementData.push_back({ placement.ModelID, uid++, placement }); } } modelPlacements.clear(); meshesObjs.clear(); return true; } m_MeshLoader = new MeshLoader(); if (!m_MeshLoader->loadMeshes()) { printf("VictoriaPhysics: Meshes loading error!"); return false; } printf("VictoriaPhysics: Meshes loaded! Meshes: %d, Placements: %d\n", m_MeshLoader->getMeshes().size(), m_MeshLoader->getMeshPlacementData().size()); std::vector<float> combinedVerts; std::vector<int> combinedTris; int vertexOffset = 0; for (const auto& placement : m_MeshLoader->getMeshPlacementData()) { const auto& meshIt = m_MeshLoader->getMeshes().find(placement.MeshID); if (meshIt == m_MeshLoader->getMeshes().end()) continue;  const float* verts = meshIt->second.getVerts(); const int nverts = meshIt->second.getVertCount(); const int* tris = meshIt->second.getTris(); const int ntris = meshIt->second.getTriCount();  // Transform and add vertices for (int i = 0; i < nverts * 3; i += 3) { glm::vec4 vertex(verts[i], verts[i + 1], verts[i + 2], 1.0f); glm::mat4 modelMatrix = glm::mat4(1.0f); modelMatrix = glm::translate(modelMatrix, placement.Transform.Position); modelMatrix *= glm::mat4_cast(placement.Transform.Rotation); vertex = modelMatrix * vertex;  combinedVerts.push_back(vertex.x); combinedVerts.push_back(vertex.y); combinedVerts.push_back(vertex.z); }  // Add triangles with offset for (int i = 0; i < ntris * 3; i++) { combinedTris.push_back(tris[i] + vertexOffset); }  vertexOffset += nverts; } handleBuild(combinedVerts, combinedTris); bool VictoriaPhysics::handleBuild(const std::vector<float>& combinedVerts, const std::vector<int>& combinedTris) { rcContext* m_ctx = new BuildContext(); // Step 1. Initialize build config rcConfig m_cfg; memset(&m_cfg, 0, sizeof(m_cfg)); // Set cell size and height m_cfg.cs = 0.3f; // 30cm per cell m_cfg.ch = 0.2f; // 20cm height per cell // Agent properties m_cfg.walkableSlopeAngle = 45.0f; // Maximum slope angle (in degrees) m_cfg.walkableHeight = (int)ceilf(2.0f / m_cfg.ch); // Minimum ceiling height (2 meters) m_cfg.walkableClimb = (int)floorf(0.9f / m_cfg.ch); // Maximum climbable height (90cm) m_cfg.walkableRadius = (int)ceilf(0.6f / m_cfg.cs); // Agent radius (60cm) // Region properties m_cfg.minRegionArea = (int)rcSqr(8); // Minimum region size of 8x8 cells m_cfg.mergeRegionArea = (int)rcSqr(20); // Merge regions smaller than 20x20 cells // Polygonization properties m_cfg.maxEdgeLen = (int)(12 / m_cfg.cs); // Maximum edge length of 12 meters m_cfg.maxSimplificationError = 1.3f; // Maximum distance error for simplification m_cfg.maxVertsPerPoly = 6; // Maximum vertices per polygon // Detail mesh properties m_cfg.detailSampleDist = m_cfg.cs * 6.0f; // Sample distance for detail mesh m_cfg.detailSampleMaxError = m_cfg.ch * 1.0f; // Maximum error for detail mesh sampling // Calculate bounds float bmin[3], bmax[3]; rcCalcBounds(combinedVerts.data(), combinedVerts.size() / 3, bmin, bmax); 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); // Step 2. Rasterize input polygon soup rcHeightfield* m_solid = rcAllocHeightfield(); if (!m_solid) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: 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, "handleBuild: Could not create solid heightfield.");  return false; } unsigned char* m_triareas = new unsigned char[combinedTris.size() / 3]; if (!m_triareas) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: Out of memory 'm_triareas' (%d).", combinedTris.size() / 3);  return false; } memset(m_triareas, 0, combinedTris.size() / 3 * sizeof(unsigned char)); rcMarkWalkableTriangles(m_ctx, m_cfg.walkableSlopeAngle, combinedVerts.data(), combinedVerts.size() / 3,  combinedTris.data(), combinedTris.size() / 3, m_triareas); rcRasterizeTriangles(m_ctx, combinedVerts.data(), combinedVerts.size() / 3, combinedTris.data(), m_triareas,  combinedTris.size() / 3, *m_solid, m_cfg.walkableClimb); delete[] m_triareas; // Step 3. Filter walkable surfaces 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 rcCompactHeightfield* m_chf = rcAllocCompactHeightfield(); if (!m_chf) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: 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, "handleBuild: Could not build compact data.");  return false; } rcFreeHeightField(m_solid); m_solid = 0; if (!rcErodeWalkableArea(m_ctx, m_cfg.walkableRadius, *m_chf)) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: Could not erode.");  return false; } if (!rcBuildDistanceField(m_ctx, *m_chf)) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: Could not build distance field.");  return false; } if (!rcBuildRegions(m_ctx, *m_chf, 0, m_cfg.minRegionArea, m_cfg.mergeRegionArea)) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: Could not build regions.");  return false; } // Step 5. Trace and simplify region contours rcContourSet* m_cset = rcAllocContourSet(); if (!m_cset) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: 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, "handleBuild: Could not create contours.");  return false; } // Step 6. Build polygons mesh from contours rcPolyMesh* m_pmesh = rcAllocPolyMesh(); if (!m_pmesh) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: Out of memory 'pmesh'.");  return false; } if (!rcBuildPolyMesh(m_ctx, *m_cset, m_cfg.maxVertsPerPoly, *m_pmesh)) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: Could not triangulate contours.");  return false; } // Step 7. Create detail mesh rcPolyMeshDetail* m_dmesh = rcAllocPolyMeshDetail(); if (!m_dmesh) {  m_ctx->log(RC_LOG_ERROR, "handleBuild: 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, "handleBuild: Could not build detail mesh.");  return false; } rcFreeCompactHeightfield(m_chf); m_chf = 0; rcFreeContourSet(m_cset); m_cset = 0; // Step 8. Create Detour data from Recast poly mesh unsigned char* navData = 0; int navDataSize = 0; static const unsigned char SAMPLE_POLYAREA_GROUND = 0; static const unsigned short SAMPLE_POLYFLAGS_WALK = 0x01; // Update poly flags from areas for (int i = 0; i < m_pmesh->npolys; ++i) {  m_pmesh->areas[i] = SAMPLE_POLYAREA_GROUND;  m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; } dtNavMeshCreateParams params; memset(&params, 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; // Set agent parameters params.walkableHeight = 2.0f; // 2 meters params.walkableRadius = 0.6f; // 60 cm params.walkableClimb = 0.9f; // 90 cm 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(&params, &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; } dtNavMeshQuery* m_navQuery = dtAllocNavMeshQuery(); 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->log(RC_LOG_PROGRESS, "NavMesh created successfully."); delete m_ctx; return true; } typedef struct {  uint16_t ModelID;  glm::vec3 Position;  glm::quat Rotation;  std::string Modelname; } IPLObject;  struct MeshObject {  uint16_t sphereCount, boxCount, faceCount;  std::vector<ColSphere> spheres;  std::vector<ColBox> boxes;  std::vector<ColFace> faces; };  class Mesh {  float m_scale;  std::vector<float> m_verts;  std::vector<int> m_tris;  int m_vertCount;  int m_triCount;  int id; public:  Mesh() : m_scale(1.0f), m_vertCount(0), m_triCount(0) {}  void addVertex(float x, float y, float z);  void addTriangle(int a, int b, int c);  void addBox(float centerX, float centerY, float centerZ,  float width, float height, float depth);  void addSphere(float radius, int segments, int rings, float centerX, float centerY, float centerZ); const float* getVerts() const { return m_verts.data(); }  const int* getTris() const { return m_tris.data(); }  int getVertCount() const { return m_vertCount; }  int getTriCount() const { return m_triCount; }  void calculateNormal(int triIndex, float* normal) const;  int getId() { return id; } };  struct IPLData {  uint16_t MeshID;  uint32_t UniqueID;  IPLObject Transform; //item placement };  class MeshLoader {  ankerl::unordered_dense::map<uint16_t, Mesh> m_Meshes;  std::vector<IPLData> m_MeshPlacementData; public:  std::vector<IPLData>& getMeshPlacementData() { return m_MeshPlacementData; }  ankerl::unordered_dense::map<uint16_t, Mesh>& getMeshes() { return m_Meshes; }  MeshLoader() {};  ~MeshLoader() { m_MeshPlacementData.clear(); m_Meshes.clear(); }; bool loadMeshes(); private:  bool ReadMapMeshes(const std::string& name,  std::vector<IPLObject>& modelPlacements, std::unordered_map<uint16_t, MeshObject>& meshes);  void transformVertexToWorld(float& x, float& y, float& z, const IPLObject& transform); };