diff --git a/python_bindings/cte_bindings.cpp b/python_bindings/cte_bindings.cpp index dc13f31..bcc3ead 100644 --- a/python_bindings/cte_bindings.cpp +++ b/python_bindings/cte_bindings.cpp @@ -446,7 +446,7 @@ void init_crashteameditor(py::module_& m) .def("is_ready", &Path::IsReady) .def("set_index", &Path::SetIndex) .def("update_dist", &Path::UpdateDist, py::arg("dist"), py::arg("ref_point"), py::arg("checkpoints")) - .def("generate_path", &Path::GeneratePath, py::arg("path_start_index"), py::arg("quadblocks")) + .def("generate_path", &Path::GeneratePath, py::arg("path_start_index"), py::arg("quadblocks"), py::arg("overlap")) .def_property("color", [](const Path& p) { return p.GetColor(); }, [](Path& p, const Color& color) { p.SetColor(color); }) diff --git a/src/level.cpp b/src/level.cpp index 5be77a8..29f74cd 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -202,12 +202,18 @@ bool Level::GenerateCheckpoints() for (const Path& path : m_checkpointPaths) { if (!path.IsReady()) { return false; } } + ResetFilter(); + for (size_t i = 0; i < m_quadblocks.size(); i++) + { + m_quadblocks[i].SetCheckpoint(-1); + } size_t checkpointIndex = 0; std::vector linkNodeIndexes; std::vector> pathCheckpoints; + bool overlap = false; for (Path& path : m_checkpointPaths) { - pathCheckpoints.push_back(path.GeneratePath(checkpointIndex, m_quadblocks)); + pathCheckpoints.push_back(path.GeneratePath(checkpointIndex, m_quadblocks, overlap)); checkpointIndex += pathCheckpoints.back().size(); linkNodeIndexes.push_back(path.GetStart()); linkNodeIndexes.push_back(path.GetEnd()); @@ -246,6 +252,20 @@ bool Level::GenerateCheckpoints() } } + for (Path& path : m_checkpointPaths) + { + const Checkpoint& middleStart = m_checkpoints[path.GetStart()]; + const Checkpoint& middleEnd = m_checkpoints[path.GetEnd()]; + + Path* sides[2] = { path.GetLeft(), path.GetRight() }; + for (Path* side : sides) + { + if (!side) { continue; } + m_checkpoints[side->GetStart()].UpdateDown(middleStart.GetDown()); + m_checkpoints[side->GetEnd()].UpdateUp(middleEnd.GetUp()); + } + } + // Cap the number of checkpoints to 255 const size_t MAX_CHECKPOINTS = 255; if (m_checkpoints.size() > MAX_CHECKPOINTS) @@ -390,7 +410,7 @@ bool Level::GenerateCheckpoints() } UpdateRenderCheckpointData(); - return true; + return !overlap; } diff --git a/src/levelui.cpp b/src/levelui.cpp index 177c684..6207aea 100644 --- a/src/levelui.cpp +++ b/src/levelui.cpp @@ -724,10 +724,12 @@ void Level::RenderUI(Renderer& renderer) m_checkpointPaths.push_back(Path(m_checkpointPaths.size())); } ImGui::SameLine(); + ImGui::BeginDisabled(m_checkpointPaths.empty()); if (ImGui::Button("Delete Path")) { m_checkpointPaths.pop_back(); } + ImGui::EndDisabled(); bool ready = !m_checkpointPaths.empty(); for (const Path& path : m_checkpointPaths) @@ -736,9 +738,23 @@ void Level::RenderUI(Renderer& renderer) } ImGui::BeginDisabled(!ready); static ButtonUI generateButton; + static bool showWarning = false; + static std::chrono::time_point warningStart; if (generateButton.Show("Generate", "Checkpoints successfully generated.", false)) { - GenerateCheckpoints(); + if (!GenerateCheckpoints()) + { + showWarning = true; + warningStart = std::chrono::steady_clock::now(); + } + } + if (showWarning) + { + auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - warningStart).count(); + if (elapsed < 5) + ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Warning: some paths are overlapping.\nCheck console for details."); + else + showWarning = false; } ImGui::EndDisabled(); ImGui::TreePop(); diff --git a/src/path.cpp b/src/path.cpp index 0c92b62..6a0e5b3 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -88,6 +88,17 @@ size_t Path::GetEnd() const return m_end; } +Path* Path::GetLeft() +{ + return m_left; +} + +Path* Path::GetRight() +{ + return m_right; +} + + std::vector& Path::GetStartIndexes() { return m_quadIndexesStart; @@ -130,24 +141,96 @@ void Path::UpdateDist(float dist, const Vec3& refPoint, std::vector& if (m_right) { m_right->UpdateDist(dist, checkpoints[m_end].GetPos(), checkpoints); } } -std::vector Path::GeneratePath(size_t pathStartIndex, std::vector& quadblocks) +std::vector Path::GeneratePath(size_t pathStartIndex, std::vector& quadblocks, bool& overlap) { /* Begin from the start point, find all neighbour quadblocks. Find the midpoint of the quad group, then find the closest vertex to this midpoint. Repeat this process until you're neighboring the end path. */ + + // Build Neighbours list + std::vector> neighbours(quadblocks.size()); + for (size_t i = 0; i < quadblocks.size(); i++) + { + if (!quadblocks[i].GetCheckpointStatus()) { continue; } + for (size_t j = i + 1; j < quadblocks.size(); j++) + { + if (!quadblocks[j].GetCheckpointStatus()) { continue; } + if (quadblocks[i].Neighbours(quadblocks[j])) + { + neighbours[i].push_back(j); + neighbours[j].push_back(i); + } + } + } + + // Build Connected Component : All quad of this path, starting from start. + std::vector connectedComponent(quadblocks.size(), false); + std::vector visitedQuadblocks(quadblocks.size(), false); + std::vector toVisit = m_quadIndexesStart; + + for (const size_t index : m_quadIndexesStart) + { + visitedQuadblocks[index] = true; + } + for (const size_t index : m_quadIndexesIgnore) + { + visitedQuadblocks[index] = true; + } + for (size_t i = 0; i < quadblocks.size(); i++) + { + if (!quadblocks[i].GetCheckpointStatus()) + { + visitedQuadblocks[i] = true; + } + } + while (!toVisit.empty()) + { + size_t currQuadID = toVisit.back(); + toVisit.pop_back(); + connectedComponent[currQuadID] = true; + for (const size_t neighbourID : neighbours[currQuadID]) + { + if (!visitedQuadblocks[neighbourID]) + { + if (quadblocks[neighbourID].GetCheckpoint() != -1) + { + printf("WARNING: %s (green) from path %zu is neighbour with %s (red) from a previous path\n", quadblocks[currQuadID].GetName().c_str(), m_index, quadblocks[neighbourID].GetName().c_str()); + overlap = true; + quadblocks[neighbourID].SetFilter(true); + quadblocks[neighbourID].SetFilterColor(Color(static_cast(255), static_cast(0), static_cast(0))); + quadblocks[currQuadID].SetFilter(true); + quadblocks[currQuadID].SetFilterColor(Color(static_cast(0), static_cast(255), static_cast(0))); + } + else + { + visitedQuadblocks[neighbourID] = true; + toVisit.push_back(neighbourID); + } + } + } + } + bool endQuadFound = true; + for (const size_t index : m_quadIndexesEnd) + { + if (!connectedComponent[index]) + { + endQuadFound = false; + printf("WARNING : %s not found in path %zu from the beginning quads\n", quadblocks[index].GetName().c_str(), m_index); + } + } + // First pass : Build the main path size_t visitedCount = 0; std::vector startEndIndexes; - std::vector visitedQuadblocks(quadblocks.size(), false); + visitedQuadblocks.assign(quadblocks.size(), false); GetStartEndIndexes(startEndIndexes); for (const size_t index : startEndIndexes) { visitedQuadblocks[index] = true; visitedCount++; } - for (size_t i = 0; i < quadblocks.size(); i++) { if (!(quadblocks[i].GetCheckpointStatus() && quadblocks[i].GetCheckpointPathable())) @@ -159,21 +242,19 @@ std::vector Path::GeneratePath(size_t pathStartIndex, std::vector currQuadblocks = m_quadIndexesStart; std::vector> quadIndexesPerChunk; - - // First pass : only look at the pathable quads while (true) { std::vector nextQuadblocks; if (visitedCount < quadblocks.size()) { - for (const size_t index : currQuadblocks) + for (const size_t currID : currQuadblocks) { - for (size_t i = 0; i < quadblocks.size(); i++) + for (size_t neighboorID : neighbours[currID]) { - if (!visitedQuadblocks[i] && quadblocks[index].Neighbours(quadblocks[i])) + if (!visitedQuadblocks[neighboorID]) { - nextQuadblocks.push_back(i); - visitedQuadblocks[i] = true; + nextQuadblocks.push_back(neighboorID); + visitedQuadblocks[neighboorID] = true; visitedCount++; } } @@ -236,65 +317,71 @@ std::vector Path::GeneratePath(size_t pathStartIndex, std::vector toVisit = m_quadIndexesStart; - while (!toVisit.empty()) + // Second pass : Look at all checkpoints quads, and set checkpoint ID of non pathable ones + // The checkpoint ID is set with the ID of the pathable quad the nearest in term of neighbours distance, then XYZ distance for tiebreak + for (size_t currQuadID = 0; currQuadID < quadblocks.size(); currQuadID++) { - size_t currQuadID = toVisit.back(); - toVisit.pop_back(); - Quadblock& currQuad = quadblocks[currQuadID]; - if (currQuad.GetCheckpointStatus() && !currQuad.GetCheckpointPathable()) + if (connectedComponent[currQuadID] && !quadblocks[currQuadID].GetCheckpointPathable()) { - // Assign to currQuad the closest checkpoint (only if currQuad isn't pathable) - float closestDist = std::numeric_limits::max(); - Vec3 quadCenter = currQuad.GetCenter(); - for (const Checkpoint& ckpt : checkpoints) + Quadblock& currQuad = quadblocks[currQuadID]; + visitedQuadblocks.assign(quadblocks.size(), false); + visitedQuadblocks[currQuadID] = true; + std::vector currentChunk = { currQuadID }; + while (!currentChunk.empty()) { - float dist = (ckpt.GetPos() - quadCenter).Length(); - if (dist < closestDist) + std::vector nextChunck; + std::vector candidateCheckpoints; + for (const size_t chunkQuadID : currentChunk) { - closestDist = dist; - currQuad.SetCheckpoint(ckpt.GetIndex()); + for (const size_t neighborID : neighbours[chunkQuadID]) + { + if (!visitedQuadblocks[neighborID]) + { + visitedQuadblocks[neighborID] = true; + if (quadblocks[neighborID].GetCheckpointPathable() && quadblocks[neighborID].GetCheckpoint() != -1) + { + candidateCheckpoints.push_back(quadblocks[neighborID].GetCheckpoint()); + } + else + { + nextChunck.push_back(neighborID); + } + } + } } - } - } - - //Find neighboor to visit (all neighboor, not only pathable ones) - for (size_t i = 0; i < quadblocks.size(); i++) - { - if (!visitedQuadblocks[i] && currQuad.Neighbours(quadblocks[i])) - { - toVisit.push_back(i); - visitedQuadblocks[i] = true; + if (!candidateCheckpoints.empty()) + { + float closestDist = std::numeric_limits::max(); + Vec3 quadCenter = currQuad.GetCenter(); + for (int ckpt_id : candidateCheckpoints) + { + Checkpoint& ckpt = checkpoints[ckpt_id - static_cast(pathStartIndex)]; + float dist = (ckpt.GetPos() - quadCenter).Length(); + if (dist < closestDist) + { + closestDist = dist; + currQuad.SetCheckpoint(ckpt_id); + } + } + break; + } + currentChunk = nextChunck; } } } - // Make sure to give start checkpoints start indexes (was overwritten in 2nd pass) + m_start = pathStartIndex; + m_end = m_start + checkpoints.size() - 1; + + // Make sure to give start/end checkpoints start/end indexes (was overwritten in 2nd pass) for (const size_t index : m_quadIndexesStart) { quadblocks[index].SetCheckpoint(static_cast(pathStartIndex)); } - - - - m_start = pathStartIndex; - m_end = m_start + checkpoints.size() - 1; + for (const size_t index : m_quadIndexesEnd) + { + quadblocks[index].SetCheckpoint(static_cast(m_end)); + } const size_t ckptCount = checkpoints.size(); for (size_t i = 0; i < ckptCount; i++) @@ -310,7 +397,7 @@ std::vector Path::GeneratePath(size_t pathStartIndex, std::vector leftCheckpoints, rightCheckpoints; if (m_left) { - leftCheckpoints = m_left->GeneratePath(pathStartIndex, quadblocks); + leftCheckpoints = m_left->GeneratePath(pathStartIndex, quadblocks, overlap); checkpoints.back().UpdateLeft(leftCheckpoints.back().GetIndex()); checkpoints.front().UpdateLeft(leftCheckpoints.front().GetIndex()); leftCheckpoints.back().UpdateRight(checkpoints.back().GetIndex()); @@ -319,7 +406,7 @@ std::vector Path::GeneratePath(size_t pathStartIndex, std::vectorGeneratePath(pathStartIndex, quadblocks); + rightCheckpoints = m_right->GeneratePath(pathStartIndex, quadblocks, overlap); checkpoints.back().UpdateRight(rightCheckpoints.back().GetIndex()); checkpoints.front().UpdateRight(rightCheckpoints.front().GetIndex()); rightCheckpoints.back().UpdateLeft(checkpoints.back().GetIndex()); diff --git a/src/path.h b/src/path.h index d87a0bd..45e04eb 100644 --- a/src/path.h +++ b/src/path.h @@ -17,6 +17,8 @@ class Path size_t GetIndex() const; size_t GetStart() const; size_t GetEnd() const; + Path* GetLeft(); + Path* GetRight(); std::vector& GetStartIndexes(); std::vector& GetEndIndexes(); std::vector& GetIgnoreIndexes(); @@ -25,7 +27,7 @@ class Path void SetColor(const Color& color); void SetIndex(size_t index); void UpdateDist(float dist, const Vec3& refPoint, std::vector& checkpoints); - std::vector GeneratePath(size_t pathStartIndex, std::vector& quadblocks); + std::vector GeneratePath(size_t pathStartIndex, std::vector& quadblocks, bool& overlap); void RenderUI(const std::string& title, const std::vector& quadblocks, const std::string& searchQuery, bool& insertAbove, bool& removePath, const std::vector& selectedIndexes, bool mainPath); void ToJson(nlohmann::json& json, const std::vector& quadblocks) const; void FromJson(const nlohmann::json& json, const std::vector& quadblocks);