From c285aae85300e401ab282a4bb2c5b41e9da6b618 Mon Sep 17 00:00:00 2001 From: Sirac Ozmen Date: Thu, 5 Nov 2026 12:16:34 +0300 Subject: [PATCH] build(deps)!: move Maul Nav to 1.2.0 The engine's navigation module takes Maul Nav 1.2.0's names, argument orders and defs: a navmesh made from its own def that holds the bake def, and avoidance velocities in binary32. Tiles bake under a later generator and tile format, so the navmesh fingerprint moves. BREAKING CHANGE: a navmesh's tile bytes and fingerprint change, and avoidance velocities are rounded to binary32, so a game's agents that avoid each other move by different amounts. --- docs/operations/dependencies.md | 2 +- modules/navigation/src/avoidance.cpp | 25 +- modules/navigation/src/navmesh.cpp | 59 +- modules/navigation/tests/navmesh_test.cpp | 2 +- third_party/maul-nav/CMakeLists.txt | 4 +- .../maul-nav/cmake/CompilerFlags.cmake | 5 +- third_party/maul-nav/cmake/ReadVersion.cmake | 5 +- third_party/maul-nav/cmake/Testing.cmake | 17 +- .../maul-nav/include/maul-nav/avoidance.h | 125 ++-- third_party/maul-nav/include/maul-nav/bake.h | 425 ++++++----- third_party/maul-nav/include/maul-nav/base.h | 41 +- third_party/maul-nav/include/maul-nav/debug.h | 54 +- third_party/maul-nav/include/maul-nav/draw.h | 12 +- .../maul-nav/include/maul-nav/flight.h | 364 ++++++---- third_party/maul-nav/include/maul-nav/flow.h | 130 ++-- .../maul-nav/include/maul-nav/hierarchy.h | 86 ++- .../maul-nav/include/maul-nav/linkgen.h | 80 +- .../maul-nav/include/maul-nav/navflow.h | 114 ++- .../maul-nav/include/maul-nav/navmesh.h | 244 +++++-- third_party/maul-nav/include/maul-nav/query.h | 681 ++++++++++-------- third_party/maul-nav/src/avoidance.c | 66 +- third_party/maul-nav/src/avoidance.h | 172 ++++- third_party/maul-nav/src/avoidance3.c | 78 +- third_party/maul-nav/src/bake_def.c | 97 +-- third_party/maul-nav/src/bake_input.c | 109 ++- third_party/maul-nav/src/bake_input.h | 13 +- third_party/maul-nav/src/baker.c | 103 +-- third_party/maul-nav/src/base.c | 2 +- third_party/maul-nav/src/border_vertices.c | 16 +- third_party/maul-nav/src/commit.c | 2 +- third_party/maul-nav/src/contour.c | 295 +++++++- third_party/maul-nav/src/contour.h | 2 +- third_party/maul-nav/src/corridor.c | 263 +++++-- third_party/maul-nav/src/crowd.c | 6 +- third_party/maul-nav/src/debug.c | 58 +- third_party/maul-nav/src/delaunay.c | 6 +- third_party/maul-nav/src/detail.c | 101 ++- third_party/maul-nav/src/detail.h | 14 +- third_party/maul-nav/src/draw.c | 48 +- third_party/maul-nav/src/draw.h | 11 +- third_party/maul-nav/src/erode.c | 3 +- third_party/maul-nav/src/filter.c | 11 +- third_party/maul-nav/src/fingerprint.c | 2 +- third_party/maul-nav/src/flight.c | 24 +- third_party/maul-nav/src/flight.h | 6 +- third_party/maul-nav/src/flight_baker.c | 44 +- third_party/maul-nav/src/flight_debug.c | 13 +- third_party/maul-nav/src/flight_def.c | 1 - third_party/maul-nav/src/flight_frame.c | 4 +- third_party/maul-nav/src/flight_probe.c | 34 +- third_party/maul-nav/src/flight_search.c | 61 +- third_party/maul-nav/src/flight_space.c | 18 +- third_party/maul-nav/src/flight_tile.c | 17 +- third_party/maul-nav/src/flight_volume.c | 137 +++- third_party/maul-nav/src/flight_volume.h | 5 +- third_party/maul-nav/src/flow.c | 128 ++-- third_party/maul-nav/src/flow.h | 28 +- third_party/maul-nav/src/flow_repair.c | 53 +- third_party/maul-nav/src/funnel.c | 4 +- third_party/maul-nav/src/grid.c | 31 +- third_party/maul-nav/src/hash.c | 4 +- third_party/maul-nav/src/height_patch.c | 2 +- third_party/maul-nav/src/height_patch.h | 4 +- third_party/maul-nav/src/heightfield.c | 2 +- third_party/maul-nav/src/hierarchy.c | 75 +- third_party/maul-nav/src/hierarchy.h | 18 +- third_party/maul-nav/src/hierarchy_path.c | 186 ++--- third_party/maul-nav/src/holes.c | 13 +- third_party/maul-nav/src/host_code.h | 13 +- third_party/maul-nav/src/input.c | 70 +- third_party/maul-nav/src/input.h | 8 + third_party/maul-nav/src/linkgen.c | 100 ++- third_party/maul-nav/src/linkgen.h | 13 +- third_party/maul-nav/src/modifiers.c | 13 +- third_party/maul-nav/src/navflow.c | 68 +- third_party/maul-nav/src/navmesh.c | 203 +++++- third_party/maul-nav/src/navmesh.h | 11 +- third_party/maul-nav/src/nearest.c | 59 +- third_party/maul-nav/src/nearest.h | 4 + third_party/maul-nav/src/obstacle.c | 42 +- third_party/maul-nav/src/offmesh.c | 93 ++- third_party/maul-nav/src/orca.c | 8 +- third_party/maul-nav/src/orca.h | 7 + third_party/maul-nav/src/orca3.c | 19 +- third_party/maul-nav/src/orca3.h | 7 + third_party/maul-nav/src/outline.h | 5 +- third_party/maul-nav/src/outputs.h | 111 +++ third_party/maul-nav/src/path.c | 89 ++- third_party/maul-nav/src/planar.c | 4 +- third_party/maul-nav/src/planar.h | 21 +- third_party/maul-nav/src/query.c | 137 +++- third_party/maul-nav/src/query.h | 53 +- third_party/maul-nav/src/query_filter.c | 2 +- third_party/maul-nav/src/query_filter.h | 4 +- third_party/maul-nav/src/raster.c | 5 +- third_party/maul-nav/src/raster.h | 13 +- third_party/maul-nav/src/raycast.c | 27 +- third_party/maul-nav/src/region.c | 2 +- third_party/maul-nav/src/scalar.h | 5 +- third_party/maul-nav/src/shortest.c | 236 +++--- third_party/maul-nav/src/shortest_geometry.h | 80 ++ third_party/maul-nav/src/spatial.c | 201 ++++-- third_party/maul-nav/src/steer.c | 87 ++- third_party/maul-nav/src/surface.c | 31 +- third_party/maul-nav/src/terrain.c | 11 +- third_party/maul-nav/src/terrain.h | 8 +- third_party/maul-nav/src/tile.c | 30 +- third_party/maul-nav/src/tile_cache.c | 6 +- third_party/maul-nav/src/tile_cache.h | 2 +- third_party/maul-nav/src/tile_index.c | 15 +- third_party/maul-nav/src/tile_index.h | 12 +- third_party/maul-nav/src/triangulate.c | 6 +- third_party/maul-nav/src/volume.c | 6 +- 113 files changed, 4476 insertions(+), 2336 deletions(-) create mode 100644 third_party/maul-nav/src/outputs.h create mode 100644 third_party/maul-nav/src/shortest_geometry.h diff --git a/docs/operations/dependencies.md b/docs/operations/dependencies.md index 0a0f23623..fda7b4b5e 100644 --- a/docs/operations/dependencies.md +++ b/docs/operations/dependencies.md @@ -20,7 +20,7 @@ Every third-party library is vendored under `third_party/` at an exact revision | Maul RHI 0.15.0 | `Rawframe-Project/maul-rhi` release archive `maul-rhi-0.15.0.tar.gz`, SHA-256 `ef0e1ec5214cdfbdaa2bc3b31c1f5558cbfbfef07889192eda79f456bbb729fa` | `57f11cf50da9380dfa6af75eb49ff6612e7d3387` (`v0.15.0`) | MIT; Khronos headers Apache-2.0 or MIT; DirectX headers MIT | `include/`, `src/`, `khronos/`, `directx/`, `cmake/`, the container, MSL and DXIL tools, `contract/mrhi.json`, `tools/gen_web_glue.py`, `CMakeLists.txt`, `LICENSE` | | Maul UI 1.3.2 | `Rawframe-Project/maul-ui` release archive `maul-ui-1.3.2.tar.gz`, SHA-256 `64fb2c13279c087b1352fa4cb4caf86cb1951e4aca0af47976d970659cd3b806` | `ea8e2c6fe328eb3c94071e5bbe3dde08c95374a3` (`v1.3.2`) | MIT | `include/`, `src/`, `cmake/`, `java/`, `tools/gen_web_glue.py`, `CMakeLists.txt`, `LICENSE`, `docs/user/licenses.md` | | Maul Tween 1.0.0 | `Rawframe-Project/maul-tween` release archive `maul-tween-1.0.0.tar.gz`, SHA-256 `8c3776f79cf482adc98a9b220e5405d6c5dde0e48342c9ab06d89f74d72e10f4` | `990fc0ef60ffb672b968043125f7fcb23a8388fa` (`v1.0.0`) | MIT | `include/`, `src/`, `cmake/`, `CMakeLists.txt`, `LICENSE` | -| Maul Nav 0.2.0 | `Rawframe-Project/maul-nav` release archive `maul-nav-0.2.0.tar.gz`, SHA-256 `e5867312f2c981a198f774cead5692e4fce15f412af2af9e7330957d663dc8fd` | `db1e65197b809af34689aeca4e6a6da37f95f08f` (`v0.2.0`) | MIT | `include/`, `src/`, `cmake/`, `CMakeLists.txt`, `LICENSE` | +| Maul Nav 1.2.0 | `Rawframe-Project/maul-nav` release archive `maul-nav-1.2.0.tar.gz`, SHA-256 `11ea9be1f7773ba892da9a4c607f90b9e9052fa7c22fb319e9a0750e5b508ec3` | `13c05ac0f2f2a14a771bfd34921e72b8561335af` (`v1.2.0`) | MIT | `include/`, `src/`, `cmake/`, `CMakeLists.txt`, `LICENSE` | | Maul Audio 0.4.0 | `Rawframe-Project/maul-audio` release archive `maul-audio-0.4.0.tar.gz`, SHA-256 `572bf30ba89f56b6a32452585bf46265ece3a1ec285b5233c04cff110ff4926d` | `fbad1b28250657a2b5704830454c8bc097dbb590` (`v0.4.0`) | MIT | `include/`, `src/`, `cmake/`, `CMakeLists.txt`, `LICENSE` | | FreeType 2.14.3 | release archive, SHA-256 `36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f` | 2.14.3 | FreeType License | the modules Maul UI builds, `LICENSE.TXT`, `docs/FTL.TXT` | | HarfBuzz 14.5.1 | release archive, SHA-256 `7e2fa4e8c7c98e8d8140671f5772542afaaa6acccfbd746506886b6d85f7f8d6` | 14.5.1 | MIT | `src/` without build files, scripts and generators, `COPYING` | diff --git a/modules/navigation/src/avoidance.cpp b/modules/navigation/src/avoidance.cpp index 687114f00..9c2f5e1b0 100644 --- a/modules/navigation/src/avoidance.cpp +++ b/modules/navigation/src/avoidance.cpp @@ -34,13 +34,18 @@ mnavPos2 groundOf(const Ground& ground) noexcept { return mnavPos2{.x = ground.x, .y = ground.y}; } +/// A velocity as Maul Nav takes it: binary32, each part rounded once. +mnavVec2 velocityOf(const Ground& velocity) noexcept { + return mnavVec2{.x = static_cast(velocity.x), .y = static_cast(velocity.y)}; +} + } // namespace struct Avoidance::State { mnavAvoidance* set = nullptr; std::size_t maximumAgents = 0; std::vector agents; - std::vector velocities; + std::vector velocities; ~State() { mnavDestroyAvoidance(set); @@ -81,21 +86,21 @@ result::Status Avoidance::avoid(std::span agents, double se state.agents.clear(); for (const AvoidingAgent& agent : agents) { state.agents.push_back(mnavAgent{.position = groundOf(agent.position), - .velocity = groundOf(agent.velocity), - .preferred = groundOf(agent.preferred), + .velocity = velocityOf(agent.velocity), + .preferred = velocityOf(agent.preferred), .radius = agent.radius, .maxSpeed = agent.maxSpeed, .priority = agent.priority, .id = agent.id}); } state.velocities.resize(agents.size()); - RAWFRAME_TRY(checked(mnavAvoid(state.set, - state.agents.data(), - static_cast(state.agents.size()), - nullptr, - 0, - seconds, - state.velocities.data()), + RAWFRAME_TRY(checked(mnavAvoidance_Avoid(state.set, + state.agents.data(), + static_cast(state.agents.size()), + nullptr, + 0, + seconds, + state.velocities.data()), "an agent or the step was refused")); for (std::size_t at = 0; at < velocities.size(); ++at) { velocities[at] = Ground{.x = state.velocities[at].x, .y = state.velocities[at].y}; diff --git a/modules/navigation/src/navmesh.cpp b/modules/navigation/src/navmesh.cpp index d8f7599ff..cbc9bda8c 100644 --- a/modules/navigation/src/navmesh.cpp +++ b/modules/navigation/src/navmesh.cpp @@ -61,7 +61,8 @@ struct Tile { } // namespace struct Navmesh::State { - mnavBakeDef def{}; + /// The navmesh's def, which holds the def its tiles are baked with. + mnavNavmeshDef def{}; mnavNavmesh* navmesh = nullptr; mnavBaker* baker = nullptr; mnavQuery* query = nullptr; @@ -79,7 +80,7 @@ struct Navmesh::State { } [[nodiscard]] double tileSide() const noexcept { - return static_cast(def.cellSize) * def.tileCells; + return static_cast(def.bake.cellSize) * def.bake.tileCells; } /// Puts back what `staged` held before a bake that failed, so nothing @@ -88,9 +89,10 @@ struct Navmesh::State { for (const Place& kPlace : staged) { const auto kFound = tiles.find(kPlace); if (kFound != tiles.end()) { - static_cast(mnavStageTile(navmesh, kFound->second.bytes.data(), kFound->second.bytes.size())); + static_cast( + mnavNavmesh_StageTile(navmesh, kFound->second.bytes.data(), kFound->second.bytes.size())); } else { - static_cast(mnavStageTileRemoval(navmesh, kPlace.first, kPlace.second)); + static_cast(mnavNavmesh_StageTileRemoval(navmesh, kPlace.first, kPlace.second)); } } } @@ -98,8 +100,8 @@ struct Navmesh::State { [[nodiscard]] mnavNearest nearestOf(const Point& point, double reach) const noexcept { mnavNearest nearest{}; const auto kReach = static_cast(reach); - if (mnavFindNearest( - navmesh, nullptr, posOf(point), mnavVec3{.x = kReach, .y = kReach * 2, .z = kReach}, &nearest) != + if (mnavNavmesh_FindNearest( + navmesh, posOf(point), mnavVec3{.x = kReach, .y = kReach * 2, .z = kReach}, nullptr, &nearest) != mnav_success) { nearest.polygon.slot = 0; } @@ -114,8 +116,9 @@ Navmesh::~Navmesh() = default; result::Result> Navmesh::create(const Settings& settings) { auto state = std::make_unique(); - mnavBakeDef& def = state->def; - def = mnavDefaultBakeDef(); + mnavNavmeshDef& navmeshDef = state->def; + navmeshDef = mnavDefaultNavmeshDef(); + mnavBakeDef& def = navmeshDef.bake; def.origin = posOf(settings.origin); def.cellSize = static_cast(settings.cellSize); def.cellHeight = static_cast(settings.cellHeight); @@ -126,18 +129,21 @@ result::Result> Navmesh::create(const Settings& setting .maxSlopeDegrees = static_cast(settings.agent.maxSlope)}; def.minRegionArea = static_cast(settings.minRegionArea); if (settings.maximumTiles > 0) { - def.limits.tiles = settings.maximumTiles; + navmeshDef.limits.tiles = settings.maximumTiles; } + // The memory both the navmesh and a bake may hold. if (settings.maximumMemoryBytes > 0) { + navmeshDef.limits.memoryBytes = settings.maximumMemoryBytes; def.limits.memoryBytes = settings.maximumMemoryBytes; } // Tiles are baked again in place as the World changes. - def.tier = mnav_tierDynamic; - const mnavBakeDefResult kMade = mnavCreateNavmesh(&def, &state->navmesh); + navmeshDef.tier = mnav_tierDynamic; + const mnavNavmeshDefResult kMade = mnavCreateNavmesh(&navmeshDef, &state->navmesh); if (kMade.result != mnav_success) { return std::unexpected{checked(kMade.result, "a navmesh's settings were refused") .error() - .withContext("setting", std::to_string(kMade.setting))}; + .withContext("setting", std::to_string(kMade.setting)) + .withContext("bake setting", std::to_string(kMade.bakeSetting))}; } RAWFRAME_TRY(checked(mnavCreateBaker(&def, &state->baker).result, "a navmesh's baker could not be made")); const mnavQueryDef kQuery = mnavDefaultQueryDef(); @@ -147,7 +153,7 @@ result::Result> Navmesh::create(const Settings& setting result::Status Navmesh::bake(std::span meshes, const Area& area) { State& state = *state_; - const Point kOrigin = pointOf(state.def.origin); + const Point kOrigin = pointOf(state.def.bake.origin); // The meshes in the grid's frame, as Maul Nav reads them. std::vector> vertices; std::vector input; @@ -194,8 +200,8 @@ result::Status Navmesh::bake(std::span meshes, const Area& area) { for (auto row = static_cast(kFirstRow); row <= static_cast(kLastRow); ++row) { mnavBakeReport report{}; const mnavBakeInput kInput{.meshes = input.data(), .meshCount = static_cast(input.size())}; - const result::Status kBaked = - checked(mnavBakeTile(state.baker, &kInput, column, row, &report), "a navmesh tile could not be baked"); + const result::Status kBaked = checked(mnavBaker_BakeTile(state.baker, &kInput, column, row, &report), + "a navmesh tile could not be baked"); if (!kBaked.has_value()) { state.unstage(staged); return std::unexpected{kBaked.error() @@ -205,16 +211,16 @@ result::Status Navmesh::bake(std::span meshes, const Area& area) { .withContext("stage", std::to_string(report.stage))}; } staged.emplace_back(column, row); - if (report.polygons == 0) { - static_cast(mnavStageTileRemoval(state.navmesh, column, row)); + if (report.polygonCount == 0) { + static_cast(mnavNavmesh_StageTileRemoval(state.navmesh, column, row)); continue; } std::size_t size = 0; - static_cast(mnavCopyBakedTile(state.baker, nullptr, 0, &size)); + static_cast(mnavBaker_CopyTile(state.baker, nullptr, 0, &size)); std::vector bytes(size); mnavTileResult staging{}; - if (mnavCopyBakedTile(state.baker, bytes.data(), bytes.size(), &size) == mnav_success) { - staging = mnavStageTile(state.navmesh, bytes.data(), bytes.size()); + if (mnavBaker_CopyTile(state.baker, bytes.data(), bytes.size(), &size) == mnav_success) { + staging = mnavNavmesh_StageTile(state.navmesh, bytes.data(), bytes.size()); } else { staging.result = mnav_errorCapacity; } @@ -223,10 +229,11 @@ result::Status Navmesh::bake(std::span meshes, const Area& area) { return checked(staging.result, "a navmesh tile could not be staged"); } made.emplace(Place{column, row}, - Tile{.bytes = std::move(bytes), .polygons = static_cast(report.polygons)}); + Tile{.bytes = std::move(bytes), .polygons = static_cast(report.polygonCount)}); } } - if (result::Status committed = checked(mnavCommit(state.navmesh), "a navmesh's tiles could not be committed"); + if (result::Status committed = + checked(mnavNavmesh_Commit(state.navmesh), "a navmesh's tiles could not be committed"); !committed.has_value()) { state.unstage(staged); return committed; @@ -258,8 +265,8 @@ result::Result Navmesh::path(const Point& from, const Point& to, double re } mnavPath found{}; RAWFRAME_TRY(checked( - mnavFindPath( - state.query, state.navmesh, nullptr, kStart.polygon, kStart.point, kEnd.polygon, kEnd.point, &found), + mnavQuery_FindPath( + state.query, state.navmesh, kStart.polygon, kStart.point, kEnd.polygon, kEnd.point, nullptr, &found), "a path could not be searched for")); Path path{.end = found.end == mnav_pathFound ? PathEnd::Reached : PathEnd::Partial, .length = 0}; path.points.reserve(static_cast(found.pointCount)); @@ -289,8 +296,8 @@ std::uint64_t Navmesh::fingerprint() const noexcept { std::uint64_t hash = MNAV_HASH_INIT; for (const auto& [kPlace, kTile] : state_->tiles) { const std::array kAt{kPlace.first, kPlace.second}; - hash = mnavHash64(hash, kAt.data(), static_cast(sizeof kAt)); - hash = mnavHash64(hash, kTile.bytes.data(), static_cast(kTile.bytes.size())); + hash = mnavHash64(hash, kAt.data(), static_cast(sizeof kAt)); + hash = mnavHash64(hash, kTile.bytes.data(), static_cast(kTile.bytes.size())); } return hash; } diff --git a/modules/navigation/tests/navmesh_test.cpp b/modules/navigation/tests/navmesh_test.cpp index 00955308c..5e86e65e8 100644 --- a/modules/navigation/tests/navmesh_test.cpp +++ b/modules/navigation/tests/navmesh_test.cpp @@ -79,7 +79,7 @@ RAWFRAME_TEST(APathGoesAroundAWallAndStraightOnceItIsGone) { // The same geometry gives the same bytes, here and on every platform. // A tile records the format and generator that baked it, so a Maul Nav // that moves either moves this value; its commit says which. - RAWFRAME_EXPECT(navmesh.fingerprint() == 0x530d3bbdede27404ULL); + RAWFRAME_EXPECT(navmesh.fingerprint() == 0x4a35ccb805b9f2fbULL); auto again = Navmesh::create({}); RAWFRAME_EXPECT(again.has_value() && (*again)->bake(kWalled, kLot).has_value()); RAWFRAME_EXPECT(again.has_value() && (*again)->fingerprint() == navmesh.fingerprint()); diff --git a/third_party/maul-nav/CMakeLists.txt b/third_party/maul-nav/CMakeLists.txt index fd8e5aa9b..c78cd7453 100644 --- a/third_party/maul-nav/CMakeLists.txt +++ b/third_party/maul-nav/CMakeLists.txt @@ -191,6 +191,7 @@ if(MAUL_NAV_BUILD_TESTS) maul_add_dense(path_rules WHITEBOX) maul_add_test(raycast) maul_add_test(query_filter) + maul_add_test(query_range) maul_add_test(offmesh) # A link slot at its last generation, which the API reaches only after # 2^32 links (test_link_slots.c). @@ -213,6 +214,7 @@ if(MAUL_NAV_BUILD_TESTS) # (test_query_table.c). maul_add_test(query_table WHITEBOX) maul_add_test(walls) + maul_add_test(outputs) maul_add_test(corridor) maul_add_test(tiers) maul_add_test(resnap) @@ -251,7 +253,7 @@ if(MAUL_NAV_BUILD_TESTS) maul_add_test(scenarios) maul_add_dense(scenarios) # The 1,000-agent doorway crowd, a large scene: nightly, never in the - # pull request check. + # pull request check, where the scenarios suite runs its first ten rows. add_test(NAME doorway COMMAND test_scenarios --doorway) set_tests_properties(doorway PROPERTIES LABELS "scenarios;nightly") maul_add_test(terrain) diff --git a/third_party/maul-nav/cmake/CompilerFlags.cmake b/third_party/maul-nav/cmake/CompilerFlags.cmake index c51e76815..38d15cb95 100644 --- a/third_party/maul-nav/cmake/CompilerFlags.cmake +++ b/third_party/maul-nav/cmake/CompilerFlags.cmake @@ -100,9 +100,10 @@ function(maul_apply_flags target) target_compile_options(${target} PRIVATE -fsanitize=thread -fno-omit-frame-pointer) target_link_options(${target} PRIVATE -fsanitize=thread) endif() - # Coverage only adds counters, so it rides on the normal flags. + # Coverage only adds counters, so it rides on the normal flags. The + # counters are atomic so that threads running a line count it exactly. if(${MAUL_PREFIX}_COVERAGE) - target_compile_options(${target} PRIVATE --coverage -O0) + target_compile_options(${target} PRIVATE --coverage -fprofile-update=atomic -O0) target_link_options(${target} PRIVATE --coverage) endif() endif() diff --git a/third_party/maul-nav/cmake/ReadVersion.cmake b/third_party/maul-nav/cmake/ReadVersion.cmake index fb1f87638..3a98bcc47 100644 --- a/third_party/maul-nav/cmake/ReadVersion.cmake +++ b/third_party/maul-nav/cmake/ReadVersion.cmake @@ -1,7 +1,10 @@ # Reads the library version from the public base header. The header is # the single source of truth: CMake, the package version file, -# pkg-config and the code itself can never disagree. +# pkg-config and the code itself can never disagree. A change to the +# header configures the build again, so a version bump reaches an existing +# build directory. function(maul_read_version header prefix out_var) + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${header}") file(STRINGS "${header}" lines REGEX "^#define ${prefix}_VERSION_(MAJOR|MINOR|PATCH) [0-9]+$") set(parts "") foreach(part MAJOR MINOR PATCH) diff --git a/third_party/maul-nav/cmake/Testing.cmake b/third_party/maul-nav/cmake/Testing.cmake index 144bbe4e4..68163e3b1 100644 --- a/third_party/maul-nav/cmake/Testing.cmake +++ b/third_party/maul-nav/cmake/Testing.cmake @@ -86,10 +86,12 @@ endfunction() # the family requires. ROOT and NAME default to the project's # source directory and name; cmake/HeaderCheck passes them to check a # library's headers alone. MSVC's cl has no C23 mode (its /std:clatest is -# a draft mode), so the C23 check is skipped there. In C++17 the -# library's _NODISCARD, found in base.h, must not be empty. A macro, -# because enable_language must run at directory scope. Without a C++ -# compiler the C++ checks are skipped. +# a draft mode), so the C23 check is skipped there. In C23 and C++17 the +# library's _NODISCARD, found in base.h, must not be empty: GCC 14 +# reports its C23 as 202000L, so a macro that waits for 202311L would +# leave a dropped status unwarned there. A macro, because enable_language +# must run at directory scope. Without a C++ compiler the C++ checks are +# skipped. macro(maul_add_header_tests) cmake_parse_arguments(MAUL_HT "" "ROOT;NAME" "" ${ARGN}) if(NOT MAUL_HT_ROOT) @@ -131,6 +133,13 @@ macro(maul_add_header_tests) "static_assert(sizeof(MAUL_TEXT(${maul_nodiscard})) > 1,\n" " \"${maul_nodiscard} is empty in C++17\");\n") list(APPEND maul_cxx_sources ${maul_header_dir}/cxx_nodiscard.cpp) + file(WRITE ${maul_header_dir}/c23_nodiscard.c + "#include \"${MAUL_HT_NAME}/base.h\"\n" + "#define MAUL_TEXT(x) MAUL_TEXT_OF(x)\n" + "#define MAUL_TEXT_OF(x) #x\n" + "static_assert(sizeof(MAUL_TEXT(${maul_nodiscard})) > 1,\n" + " \"${maul_nodiscard} is empty in C23\");\n") + list(APPEND maul_c23_sources ${maul_header_dir}/c23_nodiscard.c) endif() if(MSVC) set(maul_header_flags /W4 /WX) diff --git a/third_party/maul-nav/include/maul-nav/avoidance.h b/third_party/maul-nav/include/maul-nav/avoidance.h index 958e06812..8d4c6cca3 100644 --- a/third_party/maul-nav/include/maul-nav/avoidance.h +++ b/third_party/maul-nav/include/maul-nav/avoidance.h @@ -35,21 +35,15 @@ extern "C" #define MNAV_MAX_AVOIDANCE_RADIUS 1.0e6 #define MNAV_MIN_AVOIDANCE_TIME 1.0e-6 - // A point or a velocity on the ground plane, binary64. - typedef struct mnavPos2 - { - double x; - double y; - } mnavPos2; - // An agent as the host sees it this step, its values in the ranges // above. typedef struct mnavAgent { mnavPos2 position; - mnavPos2 velocity; + // In meters per second. + mnavVec2 velocity; // Where the agent would go, in meters per second. - mnavPos2 preferred; + mnavVec2 preferred; // More than 0, up to MNAV_MAX_AVOIDANCE_RADIUS. double radius; // 0 to MNAV_MAX_AVOIDANCE_SPEED. @@ -81,8 +75,9 @@ extern "C" // A circle's radius, more than 0 and up to // MNAV_MAX_AVOIDANCE_RADIUS; 0 for two or more points. double radius; - // Each component within MNAV_MAX_AVOIDANCE_SPEED. - mnavPos2 velocity; + // In meters per second, each component within + // MNAV_MAX_AVOIDANCE_SPEED. + mnavVec2 velocity; // The host's id for the obstacle, which orders obstacles at equal // distances; ids should differ, or the input's order counts. uint64_t id; @@ -90,13 +85,14 @@ extern "C" uint32_t layers; } mnavObstacle; - // An agent in space, for mnavAvoid3D, its values in the ranges above. + // An agent in space, for mnavAvoidance_Avoid3D, its values in the ranges above. typedef struct mnavAgent3D { mnavPos3 position; - mnavPos3 velocity; + // In meters per second. + mnavVec3 velocity; // Where the agent would go, in meters per second. - mnavPos3 preferred; + mnavVec3 preferred; // More than 0, up to MNAV_MAX_AVOIDANCE_RADIUS. double radius; // 0 to MNAV_MAX_AVOIDANCE_SPEED. @@ -118,8 +114,9 @@ extern "C" mnavPos3 center; // More than 0, up to MNAV_MAX_AVOIDANCE_RADIUS. double radius; - // Each component within MNAV_MAX_AVOIDANCE_SPEED. - mnavPos3 velocity; + // In meters per second, each component within + // MNAV_MAX_AVOIDANCE_SPEED. + mnavVec3 velocity; // The host's id for the sphere, which orders spheres at equal // distances; ids should differ, or the input's order counts. uint64_t id; @@ -204,12 +201,12 @@ extern "C" /// @param avoidanceOut Receives the set, or NULL on failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a def /// not from mnavDefaultAvoidanceDef; `mnav_errorVersion` for one built - /// against another version of the headers; `mnav_errorInvalid` also for a - /// limit below 1 (below 0 for obstacle vertices), a neighbor distance not - /// more than 0 or not finite, or a horizon that is NaN or plus infinity; - /// `mnav_errorLimit` for a limit past its MNAV_MAX_AVOIDANCE_ value or a - /// horizon shorter than MNAV_MIN_AVOIDANCE_TIME; `mnav_errorCapacity` when - /// the allocator fails. + /// against another version of the headers; `mnav_errorInvalid` also for an + /// allocator with one function, a limit below 1 (below 0 for obstacle + /// vertices), a neighbor distance not more than 0 or not finite, or a + /// horizon that is NaN or plus infinity; `mnav_errorLimit` for a limit past + /// its MNAV_MAX_AVOIDANCE_ value or a horizon shorter than + /// MNAV_MIN_AVOIDANCE_TIME; `mnav_errorCapacity` when the allocator fails. /// @par Thread safety /// Safe from any thread. MNAV_NODISCARD MNAV_API mnavResult mnavCreateAvoidance(const mnavAvoidanceDef* def, @@ -233,7 +230,7 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetAvoidanceMisuse(const mnavAvoidance* avoidance); + MNAV_API uint64_t mnavAvoidance_GetMisuse(const mnavAvoidance* avoidance); /// Finds each agent's new velocity: the one nearest its preferred /// velocity, no faster than its maximum speed, that avoids the @@ -241,11 +238,13 @@ extern "C" /// neighbours within the time horizon if they do their share (ORCA); /// when none does, the one that keeps clear of the obstacles and breaks /// the agents' constraints least. An agent sees an obstacle edge only - /// from outside it. - /// An agent held back from its preferred velocity aims 1% of its speed - /// to the right of it, so that agents meeting in perfect symmetry pass - /// rather than stop face to face. The same agents give the same - /// velocities in any order, on every platform. + /// from outside it. An agent exactly on a circle's center leaves along + /// -Y (ground -Z). + /// An agent its neighbours or obstacles hold back from its preferred + /// velocity aims 1% of its speed to the right of it; one held back + /// only by its maximum speed does not. So agents meeting in perfect + /// symmetry pass rather than stop face to face. The same agents give + /// the same velocities in any order, on every platform. /// /// @param avoidance The set. /// @param agents The agents. @@ -256,8 +255,13 @@ extern "C" /// at least MNAV_MIN_AVOIDANCE_TIME: agents /// already overlapping part /// within it. - /// @param velocitiesOut Receives agentCount velocities, in the agents' - /// order. + /// @param velocitiesOut Receives agentCount velocities, in the agents' order, + /// found in binary64 and rounded to binary32: to + /// nearest, or toward zero where that would pass the + /// maximum speed. On any failure but + /// mnav_errorBusy, the first agentCount are zeroed + /// when the set's agent limit holds them; otherwise + /// nothing is written. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument with /// agents or obstacles, a negative count, a step that is NaN or plus /// infinity, an agent or obstacle with a NaN value, an agent with a radius @@ -272,20 +276,21 @@ extern "C" /// one misuse counted. /// @par Thread safety /// Safe from any thread; the set is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavAvoid(mnavAvoidance* avoidance, const mnavAgent* agents, - int32_t agentCount, const mnavObstacle* obstacles, - int32_t obstacleCount, double step, - mnavPos2* velocitiesOut); + MNAV_NODISCARD MNAV_API mnavResult mnavAvoidance_Avoid( + mnavAvoidance* avoidance, const mnavAgent* agents, int32_t agentCount, + const mnavObstacle* obstacles, int32_t obstacleCount, double step, mnavVec2* velocitiesOut); - /// Finds each flier's new velocity, as mnavAvoid does on the ground: + /// Finds each flier's new velocity, as mnavAvoidance_Avoid does on the ground: /// the one nearest its preferred velocity, no faster than its maximum /// speed, that avoids the spheres within the obstacle horizon and /// colliding with its neighbours within the time horizon if they do /// their share (ORCA in space); when none does, the one that keeps /// clear of the spheres and breaks the agents' constraints least. - /// An agent held back from its preferred velocity aims 1% of its speed - /// to the right of it, +Y being up; rising vertically it turns toward - /// +X, falling toward -X. + /// An agent its neighbours or spheres hold back from its preferred + /// velocity aims 1% of its speed to the right of it, +Y being up; + /// rising vertically it turns toward +X, falling toward -X. One held + /// back only by its maximum speed does not. A flier exactly on a + /// sphere's center leaves along -Z. /// The same agents give the same velocities in any order, on every /// platform. /// @@ -297,8 +302,13 @@ extern "C" /// @param step The step the velocities are for, in seconds, /// at least MNAV_MIN_AVOIDANCE_TIME: agents /// already overlapping part within it. - /// @param velocitiesOut Receives agentCount velocities, in the agents' - /// order. + /// @param velocitiesOut Receives agentCount velocities, in the agents' order, + /// found in binary64 and rounded to binary32: to + /// nearest, or toward zero where that would pass the + /// maximum speed. On any failure but + /// mnav_errorBusy, the first agentCount are zeroed + /// when the set's agent limit holds them; otherwise + /// nothing is written. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument with /// agents or spheres, a negative count, a step that is NaN or plus /// infinity, an agent or sphere with a NaN value or a radius not more than @@ -311,23 +321,24 @@ extern "C" /// set's allocator, one misuse counted. /// @par Thread safety /// Safe from any thread; the set is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavAvoid3D(mnavAvoidance* avoidance, - const mnavAgent3D* agents, int32_t agentCount, - const mnavSphere* spheres, int32_t sphereCount, - double step, mnavPos3* velocitiesOut); + MNAV_NODISCARD MNAV_API mnavResult mnavAvoidance_Avoid3D( + mnavAvoidance* avoidance, const mnavAgent3D* agents, int32_t agentCount, + const mnavSphere* spheres, int32_t sphereCount, double step, mnavVec3* velocitiesOut); /// Appends each agent's outline, a 16-gon (mnav_debugAgent), and a line - /// to each neighbour mnavAvoid would give it (mnav_debugNeighbor), at a + /// to each neighbour mnavAvoidance_Avoid would give it (mnav_debugNeighbor), at a /// height, agent X and Y lying at ground X and Z. /// /// @param avoidance The set; its scratch is used. /// @param agents The agents. /// @param agentCount How many, at least 0. /// @param height The lines' height. - /// @param buffer The buffer appended to. + /// @param buffer The buffer appended to. Left as it was on any failure + /// but the buffer filling (mnav_errorCapacity), which + /// writes what fits and counts what the whole needs. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument with /// agents, a height not finite, or a buffer with a count out of range, an - /// array missing or an origin not finite; for an agent mnavAvoid refuses, + /// array missing or an origin not finite; for an agent mnavAvoidance_Avoid refuses, /// the status it gives; `mnav_errorCapacity` for more agents than the set's /// limit, or when the buffer filled, its counts saying what the whole /// needs. Also `mnav_errorBusy` when called back in from the avoidance @@ -335,14 +346,14 @@ extern "C" /// @par Thread safety /// Safe from any thread; the set and the buffer are used by one thread /// at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugAvoidance(mnavAvoidance* avoidance, - const mnavAgent* agents, - int32_t agentCount, double height, - mnavDebugBuffer* buffer); + MNAV_NODISCARD MNAV_API mnavResult mnavAvoidance_Debug(mnavAvoidance* avoidance, + const mnavAgent* agents, + int32_t agentCount, double height, + mnavDebugBuffer* buffer); /// Appends each flier's outline, three 16-gons on its radius, one in /// each axis plane (mnav_debugAgent), a line to each neighbour - /// mnavAvoid3D would give it (mnav_debugNeighbor), and each sphere's + /// mnavAvoidance_Avoid3D would give it (mnav_debugNeighbor), and each sphere's /// three rings (mnav_debugObstacle). /// /// @param avoidance The set; its scratch is used. @@ -350,11 +361,13 @@ extern "C" /// @param agentCount How many, at least 0. /// @param spheres The spheres. /// @param sphereCount How many, at least 0. - /// @param buffer The buffer appended to. + /// @param buffer The buffer appended to. Left as it was on any failure + /// but the buffer filling (mnav_errorCapacity), which + /// writes what fits and counts what the whole needs. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument with /// agents or spheres, a negative count, or a buffer with a count out of /// range, an array missing or an origin not finite; for an agent or sphere - /// mnavAvoid3D refuses, or more of them than it takes, the status it gives; + /// mnavAvoidance_Avoid3D refuses, or more of them than it takes, the status it gives; /// `mnav_errorCapacity` when the buffer filled, its counts saying what the /// whole needs. Also `mnav_errorBusy` when called back in from the /// avoidance set's allocator, one misuse counted. @@ -362,8 +375,8 @@ extern "C" /// Safe from any thread; the set and the buffer are used by one thread /// at a time. MNAV_NODISCARD MNAV_API mnavResult - mnavDebugAvoidance3D(mnavAvoidance* avoidance, const mnavAgent3D* agents, int32_t agentCount, - const mnavSphere* spheres, int32_t sphereCount, mnavDebugBuffer* buffer); + mnavAvoidance_Debug3D(mnavAvoidance* avoidance, const mnavAgent3D* agents, int32_t agentCount, + const mnavSphere* spheres, int32_t sphereCount, mnavDebugBuffer* buffer); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/bake.h b/third_party/maul-nav/include/maul-nav/bake.h index adde47dd8..c32c1add8 100644 --- a/third_party/maul-nav/include/maul-nav/bake.h +++ b/third_party/maul-nav/include/maul-nav/bake.h @@ -27,6 +27,10 @@ extern "C" #define MNAV_MIN_TILE_CELLS 16 #define MNAV_MAX_TILE_CELLS 1024 +// The most a simplified wall may stray from the cells it follows, in +// meters: a tile's side at the largest cells and tiles. +#define MNAV_MAX_EDGE_ERROR (MNAV_MAX_CELL_SIZE * MNAV_MAX_TILE_CELLS) + // How far input may lie from the bake's origin: horizontally, in cells of // the bake's cell size; vertically, in cells of its cell height, up or down. #define MNAV_MAX_EXTENT_CELLS 4194304 @@ -46,13 +50,19 @@ extern "C" #define MNAV_MAX_TILE_SPANS 67108864 #define MNAV_MAX_TILE_POLYGONS 65535 #define MNAV_MAX_TILE_VERTICES 65535 -#define MNAV_MAX_TILE_LINKS 1048576 -#define MNAV_MAX_TILES 1048576 // The most tiles a tile cache may hold. #define MNAV_MAX_CACHED_TILES 1048576 -#define MNAV_MAX_LINKS 1048576 - // An area type, 0 to MNAV_AREA_TYPES - 1. +// The most detail vertices one polygon holds, its own corners included, +// as many as a detail triangle's one-byte corners reach, and the most +// detail samples inside one of its edges: six edges of +// MNAV_MAX_DETAIL_EDGE_SAMPLES samples and their corners, 252, always fit. +#define MNAV_MAX_DETAIL_VERTICES 255 +#define MNAV_MAX_DETAIL_EDGE_SAMPLES 41 + + // An area type: a number from 0 to MNAV_AREA_TYPES - 1 that the host + // chooses, which a query filter's costs index. It is a value, not an + // enumeration; the two constants below name common values. typedef uint8_t mnavAreaType; enum @@ -89,7 +99,8 @@ extern "C" { // Triangles in one bake's input. int32_t inputTriangles; - // Input triangles that touch one tile. + // Input triangles that touch one tile; its MNAV_MAX_ value is + // MNAV_MAX_INPUT_TRIANGLES. int32_t tileTriangles; // Span fragments rasterized into one tile: one per cell a triangle // covers, before they merge into spans. @@ -98,35 +109,10 @@ extern "C" int32_t tilePolygons; // Vertices in one tile. int32_t tileVertices; - // Links in one tile. - int32_t tileLinks; - // Tiles in one navmesh. - int32_t tiles; - // Off-mesh links in one navmesh, staged or committed, - // counted by crossings: a point link takes 1, an edge link 1 + - // ceil(width / spacing) up to MNAV_MAX_LINK_CROSSINGS (see - // mnavLinkDef.width). - int32_t links; // Bytes the bake may hold allocated at once. uint64_t memoryBytes; } mnavBakeLimits; - // How a bake runs. Build it with mnavDefaultBakeDef. - // A navmesh's runtime tier: what may change once loaded. - // Tiles and off-mesh links load and unload in every tier. - typedef uint8_t mnavTier; - - enum - { - // Nothing loaded changes. - mnav_tierStatic = 0, - // Polygons' areas change, and off-mesh links are enabled and - // disabled. - mnav_tierModifiers = 1, - // Also, a loaded tile is replaced in one commit. - mnav_tierDynamic = 2, - }; - // A clock the host passes in: now returns ticks in a unit of the host's // choosing, never going back, and is called from the thread that bakes, in // the host's own floating-point environment. It may call nothing of the @@ -139,6 +125,7 @@ extern "C" void* context; } mnavClock; + // How a bake runs. Build it with mnavDefaultBakeDef. typedef struct mnavBakeDef { uint32_t cookie; @@ -159,65 +146,76 @@ extern "C" int32_t tileCells; // The agent the navmesh is for. mnavAgentProfile agent; - // The smallest walkable region kept, in square meters, at least 0: - // smaller islands away from a tile's edge are dropped. + // The smallest walkable region kept, in square meters, from 0 to the + // area of 2^30 cells: smaller islands away from a tile's edge are + // dropped. float minRegionArea; // How far a simplified wall may stray from the cells it follows, in - // meters, at least 0. Walls simplified a cell or more can cross - // themselves or each other, and a ring that then does not + // meters, 0 to MNAV_MAX_EDGE_ERROR. Walls simplified a cell or more + // can cross themselves or each other, and a ring that then does not // triangulate whole loses the area it did not triangulate; the - // report counts it in partialRings. + // report counts it in partialRingCount. A region too thin for any + // wall to stray that far keeps the outline of its cells. float maxEdgeError; - // The longest wall edge, in meters, at least 0; 0 for no limit. + // The longest wall edge, in meters, from 0 to 2^30 cells; 0 for no + // limit, and a length below a cell limits walls to one cell. float maxEdgeLength; // How far apart the detail mesh samples the floor's height along - // polygon edges and inside polygons, in meters, at least 0; 0 for - // none, and at least one cell otherwise. + // polygon edges and inside polygons, in meters, from 0 to 2^26 + // cells; 0 for none, and any other distance below a cell samples + // every cell. An edge's samples lie on whole sixteenths of a cell + // along it, and where snapping to them leaves a gap past the + // distance it takes another sample. It takes at most + // MNAV_MAX_DETAIL_EDGE_SAMPLES samples inside it, and no more + // than it has such places, so an edge may still be sampled more + // widely: a long one, or one whose ends lie a number of cells + // apart along X and Z that share no factor, which has only 15 + // such places inside it. The report counts every edge with two + // neighboring samples farther apart than the distance in + // coarseEdgeCount. maxEdgeLength shortens walls, but not edges + // between polygons or along a tile's side. A smaller distance, and + // a detailMaxError near 0, grow the detail data steeply, up to + // MNAV_MAX_DETAIL_VERTICES vertices a polygon. float detailSampleDistance; // How far the detail surface may stray from a sampled height, in - // meters, at least 0. + // meters, from 0 to 2^26 cell heights. A polygon whose detail + // reaches MNAV_MAX_DETAIL_VERTICES keeps the samples that stray + // most, and the report counts it in cappedDetailCount. float detailMaxError; // The limits on the work. mnavBakeLimits limits; - // The runtime tier of a navmesh made with this def, - // mnav_tierStatic by default; the bake ignores it. - mnavTier tier; // The clock the bake reads between stages for its report; zeroed // for none. It never shapes the tile. mnavClock clock; } mnavBakeDef; // The setting a def check refused. - typedef uint8_t mnavBakeSetting; + typedef int32_t mnavBakeSetting; enum { - mnav_settingNone = 0, - mnav_settingCookie = 1, - mnav_settingAllocator = 2, - mnav_settingOrigin = 3, - mnav_settingCellSize = 4, - mnav_settingCellHeight = 5, - mnav_settingTileCells = 6, - mnav_settingAgentRadius = 7, - mnav_settingAgentHeight = 8, - mnav_settingAgentStepHeight = 9, - mnav_settingAgentMaxSlope = 10, - mnav_settingInputTriangles = 11, - mnav_settingTileTriangles = 12, - mnav_settingTileSpans = 13, - mnav_settingTilePolygons = 14, - mnav_settingTileVertices = 15, - mnav_settingTileLinks = 16, - mnav_settingTiles = 17, - mnav_settingMemoryBytes = 18, - mnav_settingMinRegionArea = 19, - mnav_settingMaxEdgeError = 20, - mnav_settingMaxEdgeLength = 21, - mnav_settingDetailSampleDistance = 22, - mnav_settingDetailMaxError = 23, - mnav_settingLinks = 24, - mnav_settingTier = 25, + mnav_bakeSettingNone = 0, + mnav_bakeSettingCookie = 1, + mnav_bakeSettingAllocator = 2, + mnav_bakeSettingOrigin = 3, + mnav_bakeSettingCellSize = 4, + mnav_bakeSettingCellHeight = 5, + mnav_bakeSettingTileCells = 6, + mnav_bakeSettingAgentRadius = 7, + mnav_bakeSettingAgentHeight = 8, + mnav_bakeSettingAgentStepHeight = 9, + mnav_bakeSettingAgentMaxSlope = 10, + mnav_bakeSettingInputTriangles = 11, + mnav_bakeSettingTileTriangles = 12, + mnav_bakeSettingTileSpans = 13, + mnav_bakeSettingTilePolygons = 14, + mnav_bakeSettingTileVertices = 15, + mnav_bakeSettingMemoryBytes = 16, + mnav_bakeSettingMinRegionArea = 17, + mnav_bakeSettingMaxEdgeError = 18, + mnav_bakeSettingMaxEdgeLength = 19, + mnav_bakeSettingDetailSampleDistance = 20, + mnav_bakeSettingDetailMaxError = 21, }; // A def check's outcome: the status, and the first setting it refused. @@ -250,8 +248,8 @@ extern "C" int32_t minRegion; // The maximum edge error, in cells, unrounded. float edgeError; - // The maximum wall edge length, in cells, rounded down; 0 for no - // limit. + // The maximum wall edge length, in cells, rounded down to at least + // 1; 0 for no limit. int32_t edgeLength; // The detail sample distance, in sixteenths of a cell, rounded // down; 0 for none, at least 16 otherwise. @@ -304,14 +302,6 @@ extern "C" const mnavAreaType* areas; } mnavTerrain; - // A point of a 2D outline, in meters relative to the bake's origin: - // (x, y) is the navmesh point (x, 0, y). - typedef struct mnavVec2 - { - float x; - float y; - } mnavVec2; - // A closed ring of points in a 2D bake's input. The cells whose // centers it holds, by the even-odd rule, take its area; one of area // mnav_areaNone cuts them out of every other outline. @@ -326,7 +316,7 @@ extern "C" } mnavOutline; // What a bake volume does. - typedef uint8_t mnavVolumeKind; + typedef int32_t mnavVolumeKind; enum { @@ -392,7 +382,7 @@ extern "C" } mnavBake2DInput; // The kind of input element a check refused. - typedef uint8_t mnavInputElement; + typedef int32_t mnavInputElement; enum { @@ -406,31 +396,37 @@ extern "C" mnav_elementCell = 5, }; - // An input check's outcome: the status, and the first element it - // refused, vertices before triangles, each in ascending index order. + // The kind of input a check refused. + typedef int32_t mnavInputKind; + + enum + { + // The input as a whole, or none. + mnav_inputNone = 0, + mnav_inputMesh = 1, + mnav_inputTerrain = 2, + mnav_inputVolume = 3, + mnav_inputOutline = 4, + // An obstacle of mnavBaker_RebuildTile. + mnav_inputObstacle = 5, + }; + + // An input check's outcome: the status, the input it refused, by its + // kind and its index in that kind's array, and the first element of + // that input it refused, vertices before triangles, each in ascending + // index order. typedef struct mnavInputResult { mnavResult result; - mnavInputElement element; - // The element's index, or -1 for mnav_elementNone. + // mnav_inputNone and -1 when no one input is refused: none is, or + // the arrays, the tile index or the total size are. + mnavInputKind kind; int32_t index; + // mnav_elementNone and -1 when the input is refused as a whole. + mnavInputElement element; + int32_t elementIndex; } mnavInputResult; - /// Checks a 2D outline as hostile input to a bake with a def. - /// - /// @param def The bake def the outline is for. - /// @param outline The outline. - /// @return `mnav_success`; for a def mnavValidateBakeDef refuses, the - /// status it gives; `mnav_errorInvalid` for a NULL argument, fewer than 3 - /// points, an area of MNAV_AREA_TYPES or more, or a point with a NaN - /// coordinate (the point); `mnav_errorLimit` for a point past the extent - /// input may have, an infinity included (the point); `mnav_errorCapacity` - /// for more points than the inputTriangles limit. - /// @par Thread safety - /// Safe from any thread. - MNAV_NODISCARD MNAV_API mnavInputResult mnavValidateOutline(const mnavBakeDef* def, - const mnavOutline* outline); - // The cookie mnavDefaultBakeDef sets and every function that takes a // bake def checks. #define MNAV_BAKE_DEF_COOKIE 0x4E415642u @@ -474,9 +470,6 @@ extern "C" def.limits.tileSpans = 4194304; def.limits.tilePolygons = 8192; def.limits.tileVertices = 16384; - def.limits.tileLinks = 32768; - def.limits.tiles = 65536; - def.limits.links = 4096; def.limits.memoryBytes = 268435456; return def; } @@ -484,8 +477,8 @@ extern "C" /// Checks a bake def and converts its meters to cells. /// /// @param def The def to check. - /// @param cellsOut Receives the settings in cells when the def is - /// valid. May be NULL. + /// @param cellsOut Receives the settings in cells when the def is valid. May + /// be NULL. Left as it was on failure. /// @return `mnav_success`; `mnav_errorInvalid` with the setting for a NULL /// def, a def without its cookie, an allocator with one function, a NaN, a /// value (an infinity included) outside the range its field gives with no @@ -499,40 +492,73 @@ extern "C" MNAV_NODISCARD MNAV_API mnavBakeDefResult mnavValidateBakeDef(const mnavBakeDef* def, mnavBakeCells* cellsOut); - /// Checks a triangle mesh as hostile input to a bake with a def. + /// Checks a 3D bake's whole input as hostile input to a bake with a + /// def, as mnavBaker_BakeTile checks it, without baking: every mesh, + /// all of its triangles even with a tile index, every terrain and + /// volume, their size against the def's inputTriangles limit, and that + /// the tile index fits them, for an import step that wants to catch bad + /// input early. Input it accepts, every bake with the def accepts at its + /// input stage; a bake may still refuse later, for a tile past the + /// extent, a piece past MNAV_MAX_PIECE_CORNERS or a limit reached, as + /// mnavBaker_BakeTile's return value says. /// - /// @param def The bake def the mesh is for. - /// @param mesh The mesh. + /// @param def The bake def the input is for. + /// @param input The meshes, terrains, volumes and index. /// @return `mnav_success`; for a def mnavValidateBakeDef refuses, the /// status it gives; `mnav_errorInvalid` for a NULL argument, a negative - /// count, a NULL array its count needs, a NaN vertex coordinate, a vertex - /// index outside the vertices or an area type of MNAV_AREA_TYPES or more; - /// `mnav_errorCapacity` for more triangles than the def's inputTriangles - /// limit; `mnav_errorLimit` for a vertex more than MNAV_MAX_EXTENT_CELLS - /// cells from the origin on the ground or MNAV_MAX_HEIGHT_CELLS cell - /// heights above or below it, an infinity included. The element and index - /// name the first offending vertex or triangle. + /// count or a NULL array with a positive count, or a tile index that + /// mnavBaker_BakeTile refuses; for a mesh, a terrain or a volume, the + /// status mnavBaker_BakeTile gives it, the input and its element named; + /// `mnav_errorCapacity` for more input triangles in all than the + /// inputTriangles limit, a terrain's triangles and a volume's points + /// counted. /// @par Thread safety - /// Safe from any thread. - MNAV_NODISCARD MNAV_API mnavInputResult mnavValidateTriangleMesh(const mnavBakeDef* def, - const mnavTriangleMesh* mesh); + /// Safe from any thread. A tile index may be read by any number of + /// checks and bakes at once. + MNAV_NODISCARD MNAV_API mnavInputResult mnavValidateBakeInput(const mnavBakeDef* def, + const mnavBakeInput* input); + + /// Checks a 2D bake's whole input as hostile input to a bake with a + /// def, as mnavBaker_BakeTile2D checks it, without baking: every + /// outline, their points against the def's inputTriangles limit, and + /// that the tile index fits them. Input it accepts, every 2D bake with + /// the def accepts at its input stage; a bake may still refuse later, + /// as mnavBaker_BakeTile2D's return value says. + /// + /// @param def The bake def the input is for. + /// @param input The outlines and index. + /// @return `mnav_success`; for a def mnavValidateBakeDef refuses, the + /// status it gives; `mnav_errorInvalid` for a NULL argument, a negative + /// count or NULL outlines with a positive count, or a tile index that + /// mnavBaker_BakeTile2D refuses; for an outline, the status + /// mnavBaker_BakeTile2D gives it, the outline and its point named: + /// `mnav_errorInvalid` for fewer than 3 points, an area of + /// MNAV_AREA_TYPES or more, or a point with a NaN coordinate, + /// `mnav_errorLimit` for a point past the extent input may have, an + /// infinity included; `mnav_errorCapacity` for more points in all than + /// the inputTriangles limit. + /// @par Thread safety + /// Safe from any thread. A tile index may be read by any number of + /// checks and bakes at once. + MNAV_NODISCARD MNAV_API mnavInputResult mnavValidateBake2DInput(const mnavBakeDef* def, + const mnavBake2DInput* input); // The tile format version this library writes and reads. -#define MNAV_TILE_FORMAT 3 +#define MNAV_TILE_FORMAT 4 // The navmesh bake's generation, written into every tile and hashed into // its fingerprint. It rises with any change to the library that gives // other tile bytes for the same def and input, and only then, so a tile a // host keeps by its fingerprint goes stale exactly when a bake would // differ, not at every release. -#define MNAV_BAKE_GENERATOR 3 +#define MNAV_BAKE_GENERATOR 6 // A baker: the def it was made with, its memory, and the last tile it // baked. Made by mnavCreateBaker. typedef struct mnavBaker mnavBaker; // The stage a bake ended in. - typedef uint8_t mnavBakeStage; + typedef int32_t mnavBakeStage; enum { @@ -562,8 +588,8 @@ extern "C" { mnavResult result; mnavBakeStage stage; - // The first mesh refused, or -1, and what its check found. - int32_t mesh; + // The first input refused, by its kind and index, and what its + // check found. mnavInputResult input; // The hash of everything that shaped the tile: the generator's // version, the def's settings, the tile's place and the input @@ -571,13 +597,13 @@ extern "C" uint64_t fingerprint; // Input triangles that reach the tile, walkable open spans, // regions, polygons, vertices, detail vertices and triangles. - int32_t triangles; - int32_t spans; - int32_t regions; - int32_t polygons; - int32_t vertices; - int32_t detailVertices; - int32_t detailTriangles; + int32_t triangleCount; + int32_t spanCount; + int32_t regionCount; + int32_t polygonCount; + int32_t vertexCount; + int32_t detailVertexCount; + int32_t detailTriangleCount; // The tile's bytes, and the most memory the bake held at once. uint64_t tileBytes; uint64_t memoryPeak; @@ -588,16 +614,19 @@ extern "C" uint64_t stageMemory[MNAV_BAKE_STAGES]; // Holes that no bridge could join to their outline, polygon rings // whose triangulation stopped short, detail samples that found no - // height and took the polygon's, and polygons whose detail reached - // its vertex cap. Rings cut short usually come from a large - // maxEdgeError. - int32_t droppedHoles; - int32_t partialRings; - int32_t fallbackHeights; - int32_t cappedDetail; + // height and took the polygon's, polygons whose detail reached + // MNAV_MAX_DETAIL_VERTICES with samples still beyond the maximum + // error, and polygon edges, each counted once, sampled more widely + // than detailSampleDistance (see there). Rings cut short usually + // come from a large maxEdgeError. + int32_t holesDropped; + int32_t partialRingCount; + int32_t fallbackHeightCount; + int32_t cappedDetailCount; + int32_t coarseEdgeCount; // Islands of walkable space smaller than the minimum region area // and away from the tile's border, dropped. - int32_t droppedRegions; + int32_t regionsDropped; } mnavBakeReport; /// Makes a baker from a def: checks the def and keeps a copy, its @@ -633,37 +662,38 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetBakerMisuse(const mnavBaker* baker); + MNAV_API uint64_t mnavBaker_GetMisuse(const mnavBaker* baker); /// Bakes tile (tileX, tileZ) of triangle meshes and terrains, shaped /// by volumes: checks the input, then rasterizes, filters, /// partitions, traces and triangulates the tile and lays its detail, - /// and keeps the tile's bytes for mnavCopyBakedTile, in place of any + /// and keeps the tile's bytes for mnavBaker_CopyTile, in place of any /// tile baked before. The same input and def give the same bytes on /// every platform. A terrain's triangles count as input triangles, /// after the meshes', toward the limits and the fingerprint, and a - /// volume's points count as input triangles too. A refused terrain is - /// reported as the mesh at its index after the meshes, a refused - /// volume as the one at its index after the terrains. Given a tile - /// index, the bake checks each mesh's counts and pointers and then - /// only the triangles the index lists for the tile, with their - /// vertices, before reading them. + /// volume's points count as input triangles too. The report names a + /// refused mesh, terrain or volume by its kind and its index in that + /// kind's array. Given a tile index, the bake checks each mesh's counts + /// and pointers and then only the triangles the index lists for the + /// tile, with their vertices, before reading them. /// /// @param baker The baker; the tile it held before is dropped. /// @param input The meshes, terrains, volumes and index, in the /// def's frame. /// @param tileX The tile's column in the grid from the origin. /// @param tileZ The tile's row. - /// @param reportOut Receives what the bake did. May be NULL. + /// @param reportOut Receives what the bake did. May be NULL. Written on failure + /// too, naming it. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL baker or input, a /// negative count, NULL meshes, terrains or volumes with a positive count, - /// or a mesh its check refuses (the report names it); a terrain with fewer - /// than 2 samples on a side, a spacing not more than 0 or not finite, a NaN + /// or a mesh mnavValidateBakeInput refuses (the report names it); a + /// terrain with fewer than 2 samples on a side, a spacing not more than 0 or not finite, a NaN /// height (the sample) or an area of MNAV_AREA_TYPES or more (the cell) is /// `mnav_errorInvalid`, a side past MNAV_MAX_TERRAIN_SIDE or a sample past /// the extent input may have `mnav_errorLimit`; a volume with fewer than 3 - /// points, a kind out of range, an area volume's area not walkable, heights - /// not finite or backward, or a NaN point coordinate (the point) is + /// points, a negative kind or one past mnav_volumeArea, an area volume's + /// area not walkable, heights not finite or backward, or a NaN point + /// coordinate (the point) is /// `mnav_errorInvalid`, a point past the extent, an infinity included (the /// point), `mnav_errorLimit`; a tile index made for another grid, another /// number of meshes or another number of triangles in a mesh is @@ -677,9 +707,9 @@ extern "C" /// @par Thread safety /// Safe from any thread; the baker is used by one thread at a time. /// Bakers share nothing, so one per thread bakes tiles in parallel. - MNAV_NODISCARD MNAV_API mnavResult mnavBakeTile(mnavBaker* baker, const mnavBakeInput* input, - int32_t tileX, int32_t tileZ, - mnavBakeReport* reportOut); + MNAV_NODISCARD MNAV_API mnavResult mnavBaker_BakeTile(mnavBaker* baker, + const mnavBakeInput* input, int32_t tileX, + int32_t tileZ, mnavBakeReport* reportOut); /// Makes a tile index of meshes for a def's tile grid: /// checks every mesh as a bake does, then lists for each tile the @@ -697,9 +727,10 @@ extern "C" /// @param indexOut Receives the index, or NULL on failure. /// @param reportOut Receives the result, the first mesh refused and /// what its check found, and the memory peak; the - /// other fields are 0. May be NULL. + /// other fields are 0. May be NULL. Written on failure + /// too, naming it. /// @return `mnav_success`; for a def mnavValidateBakeDef refuses, the - /// status it gives; for a mesh mnavValidateTriangleMesh refuses, the + /// status it gives; for a mesh mnavValidateBakeInput refuses, the /// status it gives; `mnav_errorInvalid` for a NULL indexOut, a negative /// count or NULL meshes with a positive count; `mnav_errorLimit` past /// MNAV_MAX_INDEX_ENTRIES listed triangles or tiles of the grid; @@ -728,9 +759,9 @@ extern "C" /// @param outlineCount The number of outlines, at least 0. /// @param indexOut Receives the index, or NULL on failure. /// @param reportOut Receives the result, the first outline refused - /// (in the report's mesh) and what its check found, - /// and the memory peak; the other fields are 0. May - /// be NULL. + /// and what its check found, and the memory peak; + /// the other fields are 0. May be NULL. Written on + /// failure too, naming it. /// @return As mnavCreateTileIndex, for outlines. /// @par Thread safety /// Safe from any thread. An index is never changed after it is made, @@ -761,15 +792,15 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetTileIndexMisuse(const mnavTileIndex* index); + MNAV_API uint64_t mnavTileIndex_GetMisuse(const mnavTileIndex* index); /// Bakes tile (tileX, tileZ) of 2D outlines: checks the outlines, /// fills the cells whose centers a walkable outline holds and no /// obstruction does as a flat floor at height 0, with the highest /// area among the outlines holding them, then erodes, partitions, - /// traces and triangulates as mnavBakeTile does; the walkable filters, - /// which judge heights, do not run. The report's mesh names a refused - /// outline and its triangles count the outlines reaching the tile. + /// traces and triangulates as mnavBaker_BakeTile does; the walkable filters, + /// which judge heights, do not run. The report names a refused outline, + /// and its triangles count the outlines reaching the tile. /// Given a tile index of the outlines, the bake checks only the /// outlines it lists for the tile, which hold every outline reaching /// it, and makes the same tile to the byte; the total of the points @@ -779,8 +810,9 @@ extern "C" /// @param input The outlines and the index, in the def's frame. /// @param tileX The tile's column in the grid from the origin. /// @param tileZ The tile's row. - /// @param reportOut Receives what the bake did. May be NULL. - /// @return As mnavBakeTile, for outlines: a NULL input is + /// @param reportOut Receives what the bake did. May be NULL. Written on failure + /// too, naming it. + /// @return As mnavBaker_BakeTile, for outlines: a NULL input is /// `mnav_errorInvalid`, and so is a tile index made for another grid, for /// meshes, or for another number of outlines or of points in one; also /// `mnav_errorCapacity` for more points in all than the inputTriangles @@ -790,25 +822,30 @@ extern "C" /// @par Thread safety /// Safe from any thread; the baker is used by one thread at a time. /// Bakers share nothing, so one per thread bakes tiles in parallel. - MNAV_NODISCARD MNAV_API mnavResult mnavBakeTile2D(mnavBaker* baker, - const mnavBake2DInput* input, int32_t tileX, - int32_t tileZ, mnavBakeReport* reportOut); + MNAV_NODISCARD MNAV_API mnavResult mnavBaker_BakeTile2D(mnavBaker* baker, + const mnavBake2DInput* input, + int32_t tileX, int32_t tileZ, + mnavBakeReport* reportOut); /// Copies the last baked tile's bytes into caller memory. /// /// @param baker The baker. /// @param bufferOut Receives the bytes: at least capacity bytes; may be - /// NULL when capacity is 0. + /// NULL when capacity is 0. Zeroed over the capacity on + /// any failure, a buffer too small included. /// @param capacity The buffer's size in bytes. - /// @param sizeOut Receives the tile's size in bytes. May be NULL. + /// @param sizeOut Receives the tile's size in bytes. May be NULL. Zeroed on + /// any failure but mnav_errorCapacity, which gives the size. /// @return `mnav_success`; `mnav_errorCapacity` when the buffer is /// smaller than the tile, with the size still written; - /// `mnav_errorInvalid` for a NULL baker, a NULL buffer with a - /// positive capacity, or a baker holding no tile. + /// `mnav_errorInvalid` for a NULL baker or a NULL buffer with a + /// positive capacity; `mnav_errorState` for a baker holding no tile, + /// before its first bake or after a failed one. /// @par Thread safety /// Safe from any thread; the baker is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavCopyBakedTile(const mnavBaker* baker, uint8_t* bufferOut, - size_t capacity, size_t* sizeOut); + MNAV_NODISCARD MNAV_API mnavResult mnavBaker_CopyTile(const mnavBaker* baker, + uint8_t* bufferOut, size_t capacity, + size_t* sizeOut); // The limits of a tile cache. typedef struct mnavTileCacheLimits @@ -894,18 +931,19 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetTileCacheMisuse(const mnavTileCache* cache); + MNAV_API uint64_t mnavTileCache_GetMisuse(const mnavTileCache* cache); - /// Bakes a tile as mnavBakeTile does and keeps what a rebuild + /// Bakes a tile as mnavBaker_BakeTile does and keeps what a rebuild /// needs in the cache, replacing what it held for the tile. /// /// @param baker The baker. /// @param cache The cache. - /// @param input The input, as mnavBakeTile takes it. + /// @param input The input, as mnavBaker_BakeTile takes it. /// @param tileX The tile's column. /// @param tileZ The tile's row. - /// @param reportOut Receives the report. May be NULL. - /// @return As mnavBakeTile; `mnav_errorInvalid` for a NULL cache; + /// @param reportOut Receives the report. May be NULL. Written on failure too, + /// naming it. + /// @return As mnavBaker_BakeTile; `mnav_errorInvalid` for a NULL cache; /// `mnav_errorCapacity` when the cache holds its most tiles or bytes, with /// no tile baked and the tile's old entry dropped. Also `mnav_errorBusy` /// when called back in from host code a call on the baker or the cache runs @@ -913,13 +951,15 @@ extern "C" /// @par Thread safety /// Safe from any thread; the baker and the cache are used by one thread /// at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBakeTileCached(mnavBaker* baker, mnavTileCache* cache, - const mnavBakeInput* input, int32_t tileX, - int32_t tileZ, mnavBakeReport* reportOut); + MNAV_NODISCARD MNAV_API mnavResult mnavBaker_BakeTileCached(mnavBaker* baker, + mnavTileCache* cache, + const mnavBakeInput* input, + int32_t tileX, int32_t tileZ, + mnavBakeReport* reportOut); /// Rebuilds a cached tile with obstacles, without its triangles: the /// cached field, the cached volumes and then the obstacles, run from - /// the volumes on. The bytes are those mnavBakeTile gives the same + /// the volumes on. The bytes are those mnavBaker_BakeTile gives the same /// input with the obstacles appended to its volumes. An exclude /// obstacle carves, an area obstacle changes the area within it. /// @@ -931,22 +971,23 @@ extern "C" /// @param obstacles The obstacles, as bake volumes. May be NULL /// when obstacleCount is 0. /// @param obstacleCount How many, at least 0. - /// @param reportOut Receives the report. May be NULL. + /// @param reportOut Receives the report. May be NULL. Written on failure + /// too, naming it. /// @return `mnav_success`; `mnav_errorNotLoaded` for a tile the cache does /// not hold; `mnav_errorInvalid` for a NULL argument, a negative count or a /// baker of another def; for an obstacle a bake refuses as a volume, the /// status the bake gives, the obstacle named in the report; otherwise as - /// mnavBakeTile. Also `mnav_errorBusy` when called back in from host code a + /// mnavBaker_BakeTile. Also `mnav_errorBusy` when called back in from host code a /// call on the baker or the cache runs (an allocator, the clock), one /// misuse counted. /// @par Thread safety /// Safe from any thread; the baker and the cache are used by one thread /// at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavRebuildTile(mnavBaker* baker, mnavTileCache* cache, - int32_t tileX, int32_t tileZ, - const mnavBakeVolume* obstacles, - int32_t obstacleCount, - mnavBakeReport* reportOut); + MNAV_NODISCARD MNAV_API mnavResult mnavBaker_RebuildTile(mnavBaker* baker, mnavTileCache* cache, + int32_t tileX, int32_t tileZ, + const mnavBakeVolume* obstacles, + int32_t obstacleCount, + mnavBakeReport* reportOut); /// Drops what the cache holds for a tile, if anything. Called back in from /// host code a call on the cache runs (an allocator, the clock), it does @@ -957,7 +998,7 @@ extern "C" /// @param tileZ The tile's row. /// @par Thread safety /// Safe from any thread; the cache is used by one thread at a time. - MNAV_API void mnavDropCachedTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ); + MNAV_API void mnavTileCache_DropTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ); /// Returns the bytes a tile cache holds, its tiles' and its own. /// @@ -965,7 +1006,7 @@ extern "C" /// @return The bytes; 0 for NULL. /// @par Thread safety /// Safe from any thread; the cache is used by one thread at a time. - MNAV_API uint64_t mnavGetTileCacheBytes(const mnavTileCache* cache); + MNAV_API uint64_t mnavTileCache_GetBytes(const mnavTileCache* cache); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/base.h b/third_party/maul-nav/include/maul-nav/base.h index fae0d8e30..1eb18013c 100644 --- a/third_party/maul-nav/include/maul-nav/base.h +++ b/third_party/maul-nav/include/maul-nav/base.h @@ -23,7 +23,7 @@ extern "C" // The library version. CMake reads it from here, and release-please // moves it between the markers. // x-release-please-start-major -#define MNAV_VERSION_MAJOR 0 +#define MNAV_VERSION_MAJOR 1 // x-release-please-end // x-release-please-start-minor #define MNAV_VERSION_MINOR 2 @@ -70,15 +70,22 @@ extern "C" // MNAV_NODISCARD marks a function whose result must be read: every // function that returns a status. The attribute is standard in C23 and -// C++17 and left out for older dialects; MSVC states its C++ dialect in -// _MSVC_LANG, not __cplusplus. +// C++17 and left out for older dialects. MSVC keeps __cplusplus at +// 199711L unless /Zc:__cplusplus is given, so _MSVC_LANG is read too. In +// C, any dialect after C17 whose compiler has the attribute takes it: +// GCC 14 reports 202000L in its C23 mode. #if (defined(__cplusplus) && __cplusplus >= 201703L) || \ (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) #define MNAV_NODISCARD [[nodiscard]] -#elif !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#elif !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L && \ + defined(__has_c_attribute) +#if __has_c_attribute(nodiscard) #define MNAV_NODISCARD [[nodiscard]] #else #define MNAV_NODISCARD +#endif +#else +#define MNAV_NODISCARD #endif // The status a fallible function returns. Zero is success, positive @@ -166,7 +173,16 @@ extern "C" void* context; } mnavAllocator; - // A vector or a position within a frame, in meters, binary32. + // A vector, binary32: a velocity, a direction, or a position within a + // frame the library names, in meters. mnavVec2 is one on a plane: a 2D + // outline's (x, y) is the navmesh point (x, 0, y), and avoidance's lies + // on the plane its agents move on. + typedef struct mnavVec2 + { + float x; + float y; + } mnavVec2; + typedef struct mnavVec3 { float x; @@ -174,8 +190,15 @@ extern "C" float z; } mnavVec3; - // A world position in meters, in doubles, so that precision does not - // depend on the distance from the world origin. + // A world position in meters, binary64, so that precision does not + // depend on the distance from the world origin. mnavPos2 is one on a + // plane, as avoidance takes positions. + typedef struct mnavPos2 + { + double x; + double y; + } mnavPos2; + typedef struct mnavPos3 { double x; @@ -205,7 +228,7 @@ extern "C" /// @return The hash. /// @par Thread safety /// Safe from any thread. - MNAV_API uint64_t mnavHash64(uint64_t seed, const void* data, int32_t byteCount); + MNAV_API uint64_t mnavHash64(uint64_t seed, const void* data, int64_t byteCount); /// Returns the name of a result code, for diagnostics. /// @@ -213,7 +236,7 @@ extern "C" /// @return A static, NUL-terminated string such as "mnav_errorCapacity". /// @par Thread safety /// Safe from any thread. - MNAV_API const char* mnavResultName(mnavResult result); + MNAV_API const char* mnavGetResultName(mnavResult result); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/debug.h b/third_party/maul-nav/include/maul-nav/debug.h index c263ac47a..1994a1a6a 100644 --- a/third_party/maul-nav/include/maul-nav/debug.h +++ b/third_party/maul-nav/include/maul-nav/debug.h @@ -19,17 +19,16 @@ extern "C" { #endif - /// Appends the committed tiles on a range of places: each polygon's + /// Appends the committed tiles on a box of places: each polygon's /// detail triangles (mnav_debugPolygon, its area), its edges as lines /// (inner edges once, walls, tile sides) and each tile's bounds on the /// ground at its lowest detail height. /// /// @param navmesh The navmesh. - /// @param tileX0 The first tile column. - /// @param tileZ0 The first tile row. - /// @param tileX1 The last tile column, at least tileX0. - /// @param tileZ1 The last tile row, at least tileZ0. - /// @param buffer The buffer appended to. + /// @param tiles The tile places drawn, or NULL for every tile. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// range backward, or a buffer with a count out of range, an array /// missing or an origin not finite; `mnav_errorCapacity` when the @@ -38,29 +37,39 @@ extern "C" /// Safe from any thread. Any number of threads may read the navmesh /// at once while no commit runs on it; the buffer is used by one /// thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugNavmesh(const mnavNavmesh* navmesh, int32_t tileX0, - int32_t tileZ0, int32_t tileX1, - int32_t tileZ1, mnavDebugBuffer* buffer); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_Debug(const mnavNavmesh* navmesh, + const mnavTileRange* tiles, + mnavDebugBuffer* buffer); /// Appends every committed off-mesh link as an arc of eight lines from /// its start to its end, rising a quarter of its length: at its /// snapped points when attached, its defined ones otherwise. /// /// @param navmesh The navmesh. - /// @param buffer The buffer appended to. - /// @return As mnavDebugNavmesh. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. + /// @return As mnavNavmesh_Debug. /// @par Thread safety /// Safe from any thread. Any number of threads may read the navmesh /// at once while no commit runs on it; the buffer is used by one /// thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugLinks(const mnavNavmesh* navmesh, - mnavDebugBuffer* buffer); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_DebugLinks(const mnavNavmesh* navmesh, + mnavDebugBuffer* buffer); - /// Appends a path's straight line, point to point. + /// Appends a path's straight line, point to point. The path may be the + /// caller's own: its points are the caller's memory, read only from + /// `points` for `pointCount` points, each checked, and nothing else of + /// it is read. /// /// @param path The path. - /// @param buffer The buffer appended to. - /// @return As mnavDebugNavmesh. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. + /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a + /// path with a negative count, no points or a point that is not finite, + /// or a buffer as mnavNavmesh_Debug refuses it; `mnav_errorCapacity` + /// when the buffer filled. /// @par Thread safety /// Safe from any thread; the buffer is used by one thread at a time. MNAV_NODISCARD MNAV_API mnavResult mnavDebugPath(const mnavPath* path, mnavDebugBuffer* buffer); @@ -72,16 +81,19 @@ extern "C" /// @param navmesh The navmesh. /// @param polygons The polygons. /// @param count How many, at least 0. - /// @param buffer The buffer appended to. - /// @return As mnavDebugNavmesh, and `mnav_errorInvalid` for a polygon id + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. + /// @return As mnavNavmesh_Debug, and `mnav_errorInvalid` for a polygon id /// never handed out. /// @par Thread safety /// Safe from any thread. Any number of threads may read the navmesh /// at once while no commit runs on it; the buffer is used by one /// thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugCorridor(const mnavNavmesh* navmesh, - const mnavPolygonId* polygons, - int32_t count, mnavDebugBuffer* buffer); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_DebugCorridor(const mnavNavmesh* navmesh, + const mnavPolygonId* polygons, + int32_t count, + mnavDebugBuffer* buffer); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/draw.h b/third_party/maul-nav/include/maul-nav/draw.h index e80b212bc..8544042cc 100644 --- a/third_party/maul-nav/include/maul-nav/draw.h +++ b/third_party/maul-nav/include/maul-nav/draw.h @@ -17,7 +17,7 @@ extern "C" #endif // What a debug vertex shows; its value says more. - typedef uint16_t mnavDebugKind; + typedef int32_t mnavDebugKind; enum { @@ -38,7 +38,7 @@ extern "C" // A path's straight line. mnav_debugPath = 7, // A corridor polygon's fill; the value is its place in the - // corridor, modulo 65536. + // corridor. mnav_debugCorridor = 8, // A flow field's arrow. mnav_debugFlow = 9, @@ -60,12 +60,14 @@ extern "C" float y; float z; mnavDebugKind kind; - uint16_t value; + uint32_t value; } mnavDebugVertex; // A caller's debug buffer: vertices, and triangle and line lists of // indices into them. Debug calls append, counting past a capacity // without writing there, so the counts say what a full draw needs. + // Capacities and counts are of vertices, triangles and lines, as in + // mnavTriangleMesh, not of indices. typedef struct mnavDebugBuffer { // Subtracted from every position before it becomes a float. @@ -73,11 +75,11 @@ extern "C" mnavDebugVertex* vertices; int32_t vertexCapacity; int32_t vertexCount; - // Three indices a triangle. + // Three indices a triangle: room for 3 * triangleCapacity. uint32_t* triangles; int32_t triangleCapacity; int32_t triangleCount; - // Two indices a line. + // Two indices a line: room for 2 * lineCapacity. uint32_t* lines; int32_t lineCapacity; int32_t lineCount; diff --git a/third_party/maul-nav/include/maul-nav/flight.h b/third_party/maul-nav/include/maul-nav/flight.h index f49936957..bb81c3f89 100644 --- a/third_party/maul-nav/include/maul-nav/flight.h +++ b/third_party/maul-nav/include/maul-nav/flight.h @@ -39,16 +39,15 @@ extern "C" // The largest values a flight tile's node and leaf limits may take. #define MNAV_MAX_FLIGHT_TILE_NODES 16777216 #define MNAV_MAX_FLIGHT_TILE_LEAVES 16777216 -// The most voxels the box mnavDebugFlight draws may span. +// The most voxels the box mnavFlightVolume_Debug draws may span. #define MNAV_MAX_FLIGHT_DEBUG_VOXELS 16777216 // The farthest a point a flight volume's calls take may lie from the // volume's origin, in voxels on any axis (the floor's voxel counted from // the origin), so that a walk's voxel indices stay well within 32 bits. #define MNAV_MAX_FLIGHT_VOXELS 268435456 - // The named limits that bound a flight bake's work and a flight - // volume's size. Each is at least 1 and at most its MNAV_MAX_ value. - // Reaching one is mnav_errorCapacity. + // The named limits that bound a flight bake's work. Each is at least 1 + // and at most its MNAV_MAX_ value. Reaching one is mnav_errorCapacity. typedef struct mnavFlightLimits { // Input triangles a bake reads, as mnavBakeLimits counts them. @@ -58,15 +57,12 @@ extern "C" // Octree nodes and leaves in a tile. int32_t tileNodes; int32_t tileLeaves; - // Tiles committed in a flight volume at once. - int32_t tiles; - // The most memory a baker or a flight volume holds at once; more - // than 0. + // The most memory a baker holds at once; more than 0. uint64_t memoryBytes; } mnavFlightLimits; - // How flight tiles are baked and what a flight volume accepts. Made - // by mnavDefaultFlightDef and then changed. + // How flight tiles are baked. Made by mnavDefaultFlightDef and then + // changed. typedef struct mnavFlightDef { // Set by mnavDefaultFlightDef; a def not made by it is refused. @@ -96,12 +92,12 @@ extern "C" // Whether each column is solid from the floor up to its lowest // solid voxel, so that space beneath the ground is not open. Off // for open space such as a field of floating debris. - bool groundBelow; + bool enableGroundFill; mnavFlightLimits limits; } mnavFlightDef; // The setting a flight def check refused. - typedef uint8_t mnavFlightSetting; + typedef int32_t mnavFlightSetting; enum { mnav_flightSettingNone = 0, @@ -117,8 +113,7 @@ extern "C" mnav_flightSettingTileSpans = 10, mnav_flightSettingTileNodes = 11, mnav_flightSettingTileLeaves = 12, - mnav_flightSettingTiles = 13, - mnav_flightSettingMemoryBytes = 14, + mnav_flightSettingMemoryBytes = 13, }; // A flight def check's outcome: the status, and the first setting it @@ -129,13 +124,62 @@ extern "C" mnavFlightSetting setting; } mnavFlightDefResult; + // The limits of a flight volume. Reaching one is mnav_errorCapacity. + typedef struct mnavFlightVolumeLimits + { + // Tiles committed at once, 1 to MNAV_MAX_TILES. + int32_t tiles; + // Bytes the volume may hold allocated at once, at least 1. + uint64_t memoryBytes; + } mnavFlightVolumeLimits; + + // How a flight volume is made. Build it with + // mnavDefaultFlightVolumeDef. + typedef struct mnavFlightVolumeDef + { + uint32_t cookie; + // MNAV_ABI_VERSION, set by mnavDefaultFlightVolumeDef. + uint32_t version; + // The allocator the volume uses; zeroed for the C library's. + mnavAllocator allocator; + // The def the volume's tiles are baked with, checked whole as a + // baker checks it. The volume reads its origin, voxel size, tile + // side, floor, ceiling and radius; its allocator and limits are + // the bakers'. + mnavFlightDef flight; + mnavFlightVolumeLimits limits; + } mnavFlightVolumeDef; + + // The setting a flight volume def check refused. + typedef int32_t mnavFlightVolumeSetting; + + enum + { + mnav_flightVolumeSettingNone = 0, + mnav_flightVolumeSettingCookie = 1, + mnav_flightVolumeSettingAllocator = 2, + // The flight def, its own setting named beside. + mnav_flightVolumeSettingFlight = 3, + mnav_flightVolumeSettingTiles = 4, + mnav_flightVolumeSettingMemoryBytes = 5, + }; + + // A flight volume def check's outcome: the status, the first setting + // it refused, and for mnav_flightVolumeSettingFlight, the flight def's + // setting (mnav_flightSettingNone otherwise). + typedef struct mnavFlightVolumeDefResult + { + mnavResult result; + mnavFlightVolumeSetting setting; + mnavFlightSetting flightSetting; + } mnavFlightVolumeDefResult; + // What a flight bake did. typedef struct mnavFlightBakeReport { mnavResult result; - // The first mesh refused, or -1, and what its check found, as in - // mnavBakeReport. - int32_t mesh; + // The first input refused, by its kind and index, and what its + // check found, as in mnavBakeReport. mnavInputResult input; // The hash of everything that shaped the tile: the generator's // version, the def's settings, the tile's place and the input that @@ -144,11 +188,11 @@ extern "C" // Input triangles that reach the tile, solid spans, the tile's // cubes, nodes and leaves, its bytes, and the most memory the bake // held at once. - int32_t triangles; - int32_t spans; - int32_t cubes; - int32_t nodes; - int32_t leaves; + int32_t triangleCount; + int32_t spanCount; + int32_t cubeCount; + int32_t nodeCount; + int32_t leafCount; uint64_t tileBytes; uint64_t memoryPeak; } mnavFlightBakeReport; @@ -167,7 +211,7 @@ extern "C" } mnavFlightPath; // What stopped a flight raycast. - typedef uint8_t mnavFlightStop; + typedef int32_t mnavFlightStop; enum { // Nothing: the ray reached its end. @@ -222,11 +266,37 @@ extern "C" def.floor = 0.0f; def.ceiling = 64.0f; def.radius = 0.5f; - def.groundBelow = true; + def.enableGroundFill = true; def.limits.inputTriangles = 4194304; def.limits.tileSpans = 4194304; def.limits.tileNodes = 1048576; def.limits.tileLeaves = 1048576; + def.limits.memoryBytes = 268435456; + return def; + } + +// The cookie mnavDefaultFlightVolumeDef sets and mnavCreateFlightVolume +// checks. +#define MNAV_FLIGHT_VOLUME_DEF_COOKIE 0x4E415656u + + /// Returns the default flight volume def: tiles of the default flight + /// def, up to 65536 of them in 256 MiB. + /// Built in the program, so that it carries the version of the + /// headers the program includes. + /// + /// @return The def, with a valid cookie. + /// @par Thread safety + /// Safe from any thread. + static inline mnavFlightVolumeDef mnavDefaultFlightVolumeDef(void) + { +#ifdef __cplusplus + mnavFlightVolumeDef def = {}; +#else + mnavFlightVolumeDef def = {0}; +#endif + def.cookie = MNAV_FLIGHT_VOLUME_DEF_COOKIE; + def.version = MNAV_ABI_VERSION; + def.flight = mnavDefaultFlightDef(); def.limits.tiles = 65536; def.limits.memoryBytes = 268435456; return def; @@ -235,11 +305,12 @@ extern "C" /// Checks a flight def. /// /// @param def The def. - /// @return `mnav_success`; `mnav_errorInvalid` for a NULL def, or with the - /// first setting refused for a NaN, a value (an infinity included) outside - /// the range its field gives with no named constant, or not a power of two - /// where one is needed; `mnav_errorLimit` with the setting for a value past - /// an MNAV_MIN_ or MNAV_MAX_ bound its field names, an infinity included; + /// @return `mnav_success`; `mnav_errorInvalid` with the setting for a NULL + /// def, a def without its cookie, an allocator with one function, a NaN, a + /// value (an infinity included) outside the range its field gives with no + /// named constant, or not a power of two where one is needed; + /// `mnav_errorLimit` with the setting for a value past an MNAV_MIN_ or + /// MNAV_MAX_ bound its field names, an infinity included; /// `mnav_errorVersion` with no setting for a def built against another /// version of the headers. /// @par Thread safety @@ -278,11 +349,11 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetFlightBakerMisuse(const mnavFlightBaker* baker); + MNAV_API uint64_t mnavFlightBaker_GetMisuse(const mnavFlightBaker* baker); /// Bakes one flight tile from the input a navmesh bake reads: /// every triangle is rasterized at the voxel size, and a - /// voxel any triangle touches is solid; with groundBelow, so is each + /// voxel any triangle touches is solid; with enableGroundFill, so is each /// column below its lowest solid voxel; then every voxel within the /// flier's radius of a solid one, by whole voxels, is blocked too. A /// tile index in the input is not used: it lists triangles for a @@ -290,55 +361,64 @@ extern "C" /// /// @param baker The baker; the tile it held before is dropped. /// @param input The meshes, terrains and volumes, as - /// mnavBakeTile reads them. + /// mnavBaker_BakeTile reads them. /// @param tileX The tile's column. /// @param tileZ The tile's row. - /// @param reportOut Receives the report. May be NULL. + /// @param reportOut Receives the report. May be NULL. Written on failure too, + /// naming it. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL baker or input; - /// for input mnavBakeTile refuses, the status it gives, the mesh named in - /// the report; `mnav_errorLimit` for a tile past the extent, or when + /// for input mnavBaker_BakeTile refuses, the status it gives, the input named + /// in the report; `mnav_errorLimit` for a tile past the extent, or when /// rasterizing cuts a triangle into a piece of more than - /// MNAV_MAX_PIECE_CORNERS corners, as for mnavBakeTile; + /// MNAV_MAX_PIECE_CORNERS corners, as for mnavBaker_BakeTile; /// `mnav_errorCapacity` past a named limit or the memory limit, or when the /// allocator fails. Also `mnav_errorBusy` when called back in from the /// flight baker's allocator, one misuse counted. /// @par Thread safety /// Safe from any thread; the baker is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBakeFlightTile(mnavFlightBaker* baker, - const mnavBakeInput* input, int32_t tileX, - int32_t tileZ, - mnavFlightBakeReport* reportOut); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightBaker_BakeTile(mnavFlightBaker* baker, + const mnavBakeInput* input, + int32_t tileX, int32_t tileZ, + mnavFlightBakeReport* reportOut); /// Copies the last baked flight tile's bytes into caller memory. /// /// @param baker The baker. /// @param bufferOut Receives the bytes: at least capacity bytes; may be - /// NULL when capacity is 0. + /// NULL when capacity is 0. Zeroed over the capacity on + /// any failure, a buffer too small included. /// @param capacity The buffer's size in bytes. - /// @param sizeOut Receives the tile's size in bytes. May be NULL. + /// @param sizeOut Receives the tile's size in bytes. May be NULL. Zeroed on + /// any failure but mnav_errorCapacity, which gives the size. /// @return `mnav_success`; `mnav_errorCapacity` when the buffer is /// smaller than the tile, with the size still written; - /// `mnav_errorInvalid` for a NULL baker, a NULL buffer with a - /// positive capacity, or a baker holding no tile. + /// `mnav_errorInvalid` for a NULL baker or a NULL buffer with a + /// positive capacity; `mnav_errorState` for a baker holding no tile, + /// before its first bake or after a failed one. /// @par Thread safety /// Safe from any thread; the baker is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavCopyFlightTile(const mnavFlightBaker* baker, - uint8_t* bufferOut, size_t capacity, - size_t* sizeOut); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightBaker_CopyTile(const mnavFlightBaker* baker, + uint8_t* bufferOut, size_t capacity, + size_t* sizeOut); - /// Makes an empty flight volume for tiles baked with a def: checks the - /// def and keeps a copy, its allocator and its limits. + /// Makes an empty flight volume from a def: checks the def and keeps a + /// copy, its allocator and its limits. /// - /// @param def The def the tiles are baked with. + /// @param def The def, from mnavDefaultFlightVolumeDef. /// @param volumeOut Receives the volume, or NULL on failure. - /// @return `mnav_success`; for a def mnavValidateFlightDef refuses, the - /// status and setting it gives; `mnav_errorInvalid` for a NULL - /// volumeOut; `mnav_errorCapacity` when the volume does not fit the + /// @return `mnav_success`; `mnav_errorInvalid` with the setting for a NULL + /// def, a def without its cookie, an allocator with one function or a + /// limit below 1; `mnav_errorLimit` with the setting for more tiles than + /// MNAV_MAX_TILES; `mnav_errorVersion` with no setting for a def built + /// against another version of the headers; for a flight def + /// mnavValidateFlightDef refuses, the status it gives with + /// mnav_flightVolumeSettingFlight and its setting; `mnav_errorInvalid` for + /// a NULL volumeOut; `mnav_errorCapacity` when the volume does not fit the /// def's memory limit or the allocator fails. /// @par Thread safety /// Safe from any thread. - MNAV_NODISCARD MNAV_API mnavFlightDefResult - mnavCreateFlightVolume(const mnavFlightDef* def, mnavFlightVolume** volumeOut); + MNAV_NODISCARD MNAV_API mnavFlightVolumeDefResult + mnavCreateFlightVolume(const mnavFlightVolumeDef* def, mnavFlightVolume** volumeOut); /// Destroys a flight volume, its tiles and everything staged. Called back /// in from the flight volume's allocator, it does nothing and counts one @@ -359,7 +439,7 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetFlightVolumeMisuse(const mnavFlightVolume* volume); + MNAV_API uint64_t mnavFlightVolume_GetMisuse(const mnavFlightVolume* volume); /// Loads flight tile bytes, checking every field as hostile input and /// the tile's settings against the volume's def, and stages the tile @@ -367,21 +447,23 @@ extern "C" /// before. Queries do not see it until the commit. /// /// @param volume The volume. - /// @param bytes The tile's bytes, as mnavCopyFlightTile gives them. + /// @param bytes The tile's bytes, as mnavFlightBaker_CopyTile gives them. /// @param size Their size in bytes. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument; /// `mnav_errorFormat` naming the section and element for malformed bytes, a - /// tile baked with other settings or past the def's node or leaf limit (the - /// header); `mnav_errorVersion` for another flight tile format version; - /// `mnav_errorCapacity` past the memory limit or when the allocator fails. + /// tile baked with other settings, past the def's node or leaf limit or at + /// a place past the extent the bake writes (the header); `mnav_errorVersion` + /// for another flight tile format version; `mnav_errorCapacity` past the + /// memory limit or when the allocator fails. /// Also `mnav_errorBusy` when called back in from the flight volume's /// allocator, one misuse counted. /// @par Thread safety /// Safe from any thread; the volume is used by one thread at a time. /// Staging changes nothing queries read, but it may not run beside /// another call that stages or commits. - MNAV_NODISCARD MNAV_API mnavTileResult mnavStageFlightTile(mnavFlightVolume* volume, - const uint8_t* bytes, size_t size); + MNAV_NODISCARD MNAV_API mnavTileResult mnavFlightVolume_StageTile(mnavFlightVolume* volume, + const uint8_t* bytes, + size_t size); /// Stages the removal of the flight tile at a place for the next /// commit, in place of anything staged there before. @@ -395,8 +477,9 @@ extern "C" /// allocator, one misuse counted. /// @par Thread safety /// Safe from any thread; the volume is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavStageFlightTileRemoval(mnavFlightVolume* volume, - int32_t tileX, int32_t tileZ); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_StageTileRemoval(mnavFlightVolume* volume, + int32_t tileX, + int32_t tileZ); /// Applies everything staged at once: installs and removes the tiles. /// Either all of it applies or, on failure, nothing does and the @@ -410,7 +493,7 @@ extern "C" /// @par Thread safety /// Safe from any thread; the volume is used by one thread at a time, /// so no query may run during a commit. - MNAV_NODISCARD MNAV_API mnavResult mnavCommitFlight(mnavFlightVolume* volume); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_Commit(mnavFlightVolume* volume); /// Reads the fingerprint of the flight tile committed at a place, as /// its bake reported it, so that a host can tell whether its input @@ -419,15 +502,16 @@ extern "C" /// @param volume The volume. /// @param tileX The tile's column. /// @param tileZ The tile's row. - /// @param fingerprintOut Receives the fingerprint, or 0. + /// @param fingerprintOut Receives the fingerprint, or 0. Zeroed on any + /// failure. /// @return `mnav_success`; `mnav_errorNotLoaded` when no tile is /// committed there; `mnav_errorInvalid` for a NULL argument. /// @par Thread safety /// Safe from any thread. Any number of threads may read the volume at /// once between commits; none may while a stage or commit call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavGetFlightTile(const mnavFlightVolume* volume, - int32_t tileX, int32_t tileZ, - uint64_t* fingerprintOut); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_GetTile(const mnavFlightVolume* volume, + int32_t tileX, int32_t tileZ, + uint64_t* fingerprintOut); /// Tells whether the flier's center may be at a point: whether the /// voxel holding it is neither solid nor within the flier's radius of @@ -435,7 +519,7 @@ extern "C" /// /// @param volume The volume. /// @param point The point, in world coordinates. - /// @param openOut Receives whether the point is open; false on + /// @param openOut Receives whether the point is open; false on any /// failure. /// @return `mnav_success`; `mnav_errorNotLoaded` when no tile is committed /// under the point; `mnav_errorInvalid` for a NULL argument or a point with @@ -445,8 +529,8 @@ extern "C" /// @par Thread safety /// Safe from any thread. Any number of threads may read the volume at /// once between commits; none may while a stage or commit call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavIsFlightOpen(const mnavFlightVolume* volume, - mnavPos3 point, bool* openOut); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_IsOpen(const mnavFlightVolume* volume, + mnavPos3 point, bool* openOut); /// Finds a flier's path between two points: Lazy Theta* /// (Nash, Koenig and Tovey, 2010) over the open blocks of the @@ -461,18 +545,19 @@ extern "C" /// @param volume The flight volume. /// @param start The start, in world coordinates. /// @param end The end, in world coordinates. - /// @param pathOut Receives the path. A start or end not open ends the - /// search at once: `mnav_pathNone` for a blocked voxel, - /// `mnav_pathNotLoaded` for a place with no tile, with - /// the start alone as its path. So does an end in a - /// closed space without the start, such as the inside - /// of a closed mesh, when the space is found closed - /// within 512 blocks and the node budget: the search - /// looks over the end's space that far first. - /// `mnav_pathNone`, with the start alone. + /// @param pathOut Receives the path. A start or end not open ends the search + /// at once: `mnav_pathNone` for a blocked voxel, + /// `mnav_pathNotLoaded` for a place with no tile, with the + /// start alone as its path. So does an end in a closed space + /// without the start, such as the inside of a closed mesh, + /// when the space is found closed within 512 blocks and the + /// node budget: the search looks over the end's space that + /// far first. `mnav_pathNone`, with the start alone. Zeroed + /// on any failure but mnav_errorBusy, which leaves it as it + /// was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// point with a NaN coordinate; `mnav_errorOutside` for a point below or - /// above the volume, pathOut left as it was; `mnav_errorLimit` for a point + /// above the volume; `mnav_errorLimit` for a point /// more than MNAV_MAX_FLIGHT_VOXELS voxels from the origin, an infinity /// included. Also `mnav_errorBusy` when called back in from host code a /// call using the context runs (a link generation's clearance test, a @@ -480,15 +565,15 @@ extern "C" /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time, /// and the volume is not changed during the search. - MNAV_NODISCARD MNAV_API mnavResult mnavFindFlightPath(mnavQuery* query, - const mnavFlightVolume* volume, - mnavPos3 start, mnavPos3 end, - mnavFlightPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindFlightPath(mnavQuery* query, + const mnavFlightVolume* volume, + mnavPos3 start, mnavPos3 end, + mnavFlightPath* pathOut); /// Begins a flier's path search to run in slices: the same search as - /// mnavFindFlightPath, continued by mnavContinueFlightPath and ended by - /// mnavFinishFlightPath. Whatever the slices, the path is the one - /// mnavFindFlightPath gives. It looks over the end's space for a closed + /// mnavQuery_FindFlightPath, continued by mnavQuery_ContinueFlightPath and ended by + /// mnavQuery_FinishFlightPath. Whatever the slices, the path is the one + /// mnavQuery_FindFlightPath gives. It looks over the end's space for a closed /// one, up to 512 blocks, before the first slice. /// /// @param query The query context; it holds the search until the next @@ -506,17 +591,18 @@ extern "C" /// a hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBeginFlightPath(mnavQuery* query, - const mnavFlightVolume* volume, - mnavPos3 start, mnavPos3 end); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_BeginFlightPath(mnavQuery* query, + const mnavFlightVolume* volume, + mnavPos3 start, mnavPos3 end); - /// Continues a search begun by mnavBeginFlightPath: closes up to a + /// Continues a search begun by mnavQuery_BeginFlightPath: closes up to a /// number of nodes, fewer when the search ends first. /// /// @param query The query context. /// @param volume The volume the search began on. /// @param nodes The most nodes to close, at least 1. - /// @param endedOut Receives whether the search has ended. + /// @param endedOut Receives whether the search has ended. Zeroed on any + /// failure but mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, fewer /// than 1 node, or a volume other than the search's; `mnav_errorState` for /// no flight search begun, or a volume committed to since the search began: @@ -526,17 +612,18 @@ extern "C" /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time, /// and the volume is not changed during the call. - MNAV_NODISCARD MNAV_API mnavResult mnavContinueFlightPath(mnavQuery* query, - const mnavFlightVolume* volume, - int32_t nodes, bool* endedOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_ContinueFlightPath(mnavQuery* query, + const mnavFlightVolume* volume, + int32_t nodes, bool* endedOut); - /// Ends a search begun by mnavBeginFlightPath and writes its path: as - /// mnavFindFlightPath's when it has ended; otherwise to the point + /// Ends a search begun by mnavQuery_BeginFlightPath and writes its path: as + /// mnavQuery_FindFlightPath's when it has ended; otherwise to the point /// searched nearest the end, its end mnav_pathUnfinished. /// /// @param query The query context; its memory holds the path. /// @param volume The volume the search began on. - /// @param pathOut Receives the path. + /// @param pathOut Receives the path. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// volume other than the search's; `mnav_errorState` for no flight search /// begun, or a volume committed to since the search began. Also @@ -546,9 +633,9 @@ extern "C" /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time, /// and the volume is not changed during the call. - MNAV_NODISCARD MNAV_API mnavResult mnavFinishFlightPath(mnavQuery* query, - const mnavFlightVolume* volume, - mnavFlightPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FinishFlightPath(mnavQuery* query, + const mnavFlightVolume* volume, + mnavFlightPath* pathOut); /// Casts a flier's ray: walks the voxels the segment crosses, and /// where it passes an edge or a corner every voxel it touches, and @@ -557,19 +644,20 @@ extern "C" /// @param volume The flight volume. /// @param from The ray's start, in world coordinates. /// @param to Its end. - /// @param hitOut Receives what stopped it, where and at what fraction. + /// @param hitOut Receives what stopped it, where and at what fraction. Zeroed + /// on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// point with a NaN coordinate; `mnav_errorLimit` for a point more than /// MNAV_MAX_FLIGHT_VOXELS voxels from the origin, an infinity included. /// @par Thread safety /// Safe from any thread. Any number of threads may read the volume at /// once between commits; none may while a stage or commit call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavFlightRaycast(const mnavFlightVolume* volume, - mnavPos3 from, mnavPos3 to, - mnavFlightHit* hitOut); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_Raycast(const mnavFlightVolume* volume, + mnavPos3 from, mnavPos3 to, + mnavFlightHit* hitOut); /// Checks a flier's path against the volume as it is now: casts each - /// step's ray in order, as mnavFlightRaycast does, and stops at the + /// step's ray in order, as mnavFlightVolume_Raycast does, and stops at the /// first step that meets a voxel the flier may not enter or a place /// with no tile. A host that follows a path checks what is left of it /// after a commit, and searches again from where it is when a step @@ -579,12 +667,12 @@ extern "C" /// @param points The path's points, in world coordinates; may be /// NULL when pointCount is 0. /// @param pointCount How many, at least 0. - /// @param stepOut Receives the first step not clear, i for the - /// one from point i to point i + 1, or -1 when every - /// step is clear. - /// @param hitOut Receives what stopped that step, as - /// mnavFlightRaycast gives it; may be NULL, and is - /// left alone when every step is clear. + /// @param stepOut Receives the first step not clear, i for the one from + /// point i to point i + 1, or -1 when every step is clear. + /// Zeroed on any failure. + /// @param hitOut Receives what stopped that step, as mnavFlightVolume_Raycast + /// gives it; may be NULL, and is left alone when every step + /// is clear. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// negative count or a point with a NaN coordinate; `mnav_errorLimit` for a /// point more than MNAV_MAX_FLIGHT_VOXELS voxels from the origin, an @@ -592,10 +680,11 @@ extern "C" /// @par Thread safety /// Safe from any thread. Any number of threads may read the volume at /// once between commits; none may while a stage or commit call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavCheckFlightPath(const mnavFlightVolume* volume, - const mnavPos3* points, - int32_t pointCount, int32_t* stepOut, - mnavFlightHit* hitOut); + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_CheckPath(const mnavFlightVolume* volume, + const mnavPos3* points, + int32_t pointCount, + int32_t* stepOut, + mnavFlightHit* hitOut); /// Finds the nearest point open to the flier's center within a radius /// of a point: the point itself when it is open, or else a point just @@ -605,8 +694,10 @@ extern "C" /// @param volume The flight volume. /// @param point The point, in world coordinates. /// @param radius How far to look, in meters, at least 0. - /// @param nearestOut Receives the nearest open point. - /// @param foundOut Receives whether one lies within the radius. + /// @param nearestOut Receives the nearest open point; zeroed when none lies + /// within the radius, and on any failure. + /// @param foundOut Receives whether one lies within the radius. Zeroed on + /// any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a point /// with a NaN coordinate, or a radius below 0 or not finite; /// `mnav_errorLimit` for a point more than MNAV_MAX_FLIGHT_VOXELS voxels @@ -614,10 +705,9 @@ extern "C" /// @par Thread safety /// Safe from any thread. Any number of threads may read the volume at /// once between commits; none may while a stage or commit call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavFindNearestFlightPoint(const mnavFlightVolume* volume, - mnavPos3 point, float radius, - mnavPos3* nearestOut, - bool* foundOut); + MNAV_NODISCARD MNAV_API mnavResult + mnavFlightVolume_FindNearestPoint(const mnavFlightVolume* volume, mnavPos3 point, float radius, + mnavPos3* nearestOut, bool* foundOut); /// Appends the open blocks of a flight volume's octrees inside a box, /// each as a wire box of twelve lines (mnav_debugFlightBlock, its value @@ -628,7 +718,9 @@ extern "C" /// @param volume The flight volume. /// @param low The box's lowest corner, in world coordinates. /// @param high Its highest corner, at least low on every axis. - /// @param buffer The buffer appended to. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// corner with a NaN coordinate, a box backward, or a buffer with a count /// out of range, an array missing or an origin not finite; @@ -640,17 +732,23 @@ extern "C" /// @par Thread safety /// Safe from any thread. Any number of threads may read the volume at /// once between commits; the buffer is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugFlight(const mnavFlightVolume* volume, mnavPos3 low, - mnavPos3 high, mnavDebugBuffer* buffer); - - /// Appends a flight path's steps, point to point (mnav_debugPath). + MNAV_NODISCARD MNAV_API mnavResult mnavFlightVolume_Debug(const mnavFlightVolume* volume, + mnavPos3 low, mnavPos3 high, + mnavDebugBuffer* buffer); + + /// Appends a flight path's steps, point to point (mnav_debugPath). The + /// path may be the caller's own: its points are the caller's memory, + /// read only from `points` for `pointCount` points, each checked, and + /// nothing else of it is read. /// /// @param path The path. - /// @param buffer The buffer appended to. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a - /// path with a negative count or no points, or a buffer as - /// mnavDebugFlight refuses it; `mnav_errorCapacity` when the buffer - /// filled. + /// path with a negative count, no points or a point that is not finite, + /// or a buffer as mnavFlightVolume_Debug refuses it; `mnav_errorCapacity` + /// when the buffer filled. /// @par Thread safety /// Safe from any thread; the buffer is used by one thread at a time. MNAV_NODISCARD MNAV_API mnavResult mnavDebugFlightPath(const mnavFlightPath* path, diff --git a/third_party/maul-nav/include/maul-nav/flow.h b/third_party/maul-nav/include/maul-nav/flow.h index b893c80c5..9d8f85792 100644 --- a/third_party/maul-nav/include/maul-nav/flow.h +++ b/third_party/maul-nav/include/maul-nav/flow.h @@ -21,82 +21,82 @@ extern "C" { #endif -// The most cells a flow field may hold. -#define MNAV_MAX_FLOW_CELLS 16777216 +// The most cells a grid flow field may hold. +#define MNAV_MAX_GRID_FLOW_CELLS 16777216 - // How a flow field is made. Build it with mnavDefaultFlowFieldDef. - typedef struct mnavFlowFieldDef + // How a grid flow field is made. Build it with mnavDefaultGridFlowDef. + typedef struct mnavGridFlowDef { uint32_t cookie; - // MNAV_ABI_VERSION, set by mnavDefaultFlowFieldDef. + // MNAV_ABI_VERSION, set by mnavDefaultGridFlowDef. uint32_t version; // The allocator the field uses; zeroed for the C library's. mnavAllocator allocator; - // The most cells a grid may have, 1 to MNAV_MAX_FLOW_CELLS. + // The most cells a grid may have, 1 to MNAV_MAX_GRID_FLOW_CELLS. int32_t cells; - } mnavFlowFieldDef; + } mnavGridFlowDef; - // A flow field: the memory for its cells, and the field last built. - typedef struct mnavFlowField mnavFlowField; + // A grid flow field: the memory for its cells, and the field last built. + typedef struct mnavGridFlow mnavGridFlow; // A cell's way to the goals: its cost, infinite where no goal is // reached, and the next cell, the cell itself at a goal and where no // goal is reached. - typedef struct mnavFlow + typedef struct mnavCellFlow { double cost; mnavCell next; - } mnavFlow; + } mnavCellFlow; -// The cookie mnavDefaultFlowFieldDef sets and mnavCreateFlowField checks. -#define MNAV_FLOW_FIELD_DEF_COOKIE 0x4E415646u +// The cookie mnavDefaultGridFlowDef sets and mnavCreateGridFlow checks. +#define MNAV_GRID_FLOW_DEF_COOKIE 0x4E415646u - /// Returns the default flow field def: up to 65536 cells. + /// Returns the default grid flow field def: up to 65536 cells. /// Built in the program, so that it carries the version of the /// headers the program includes. /// /// @return The def. /// @par Thread safety /// Safe from any thread. - static inline mnavFlowFieldDef mnavDefaultFlowFieldDef(void) + static inline mnavGridFlowDef mnavDefaultGridFlowDef(void) { #ifdef __cplusplus - mnavFlowFieldDef def = {}; + mnavGridFlowDef def = {}; #else - mnavFlowFieldDef def = {0}; + mnavGridFlowDef def = {0}; #endif - def.cookie = MNAV_FLOW_FIELD_DEF_COOKIE; + def.cookie = MNAV_GRID_FLOW_DEF_COOKIE; def.version = MNAV_ABI_VERSION; def.cells = 65536; return def; } - /// Makes a flow field with the memory its cell limit needs. + /// Makes a grid flow field with the memory its cell limit needs. /// - /// @param def The def, from mnavDefaultFlowFieldDef. + /// @param def The def, from mnavDefaultGridFlowDef. /// @param fieldOut Receives the field, or NULL on failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a def - /// not from mnavDefaultFlowFieldDef; `mnav_errorVersion` for one built + /// not from mnavDefaultGridFlowDef; `mnav_errorVersion` for one built /// against another version of the headers; `mnav_errorInvalid` also for an /// allocator with one function or a cell limit below 1; `mnav_errorLimit` - /// for a cell limit past MNAV_MAX_FLOW_CELLS; `mnav_errorCapacity` when the + /// for a cell limit past MNAV_MAX_GRID_FLOW_CELLS; `mnav_errorCapacity` when the /// allocator fails. /// @par Thread safety /// Safe from any thread. - MNAV_NODISCARD MNAV_API mnavResult mnavCreateFlowField(const mnavFlowFieldDef* def, - mnavFlowField** fieldOut); + MNAV_NODISCARD MNAV_API mnavResult mnavCreateGridFlow(const mnavGridFlowDef* def, + mnavGridFlow** fieldOut); - /// Destroys a flow field. Called back in from the field's allocator, it + /// Destroys a grid flow field. Called back in from the field's allocator, it /// does nothing and counts one misuse. /// /// @param field The field, or NULL. /// @par Thread safety /// Safe from any thread; the field is used by one thread at a time. - MNAV_API void mnavDestroyFlowField(mnavFlowField* field); + MNAV_API void mnavDestroyGridFlow(mnavGridFlow* field); - /// Returns how many calls against the flow field were refused as misuse + /// Returns how many calls against the grid flow field were refused as misuse /// since it was made: an argument refused with `mnav_errorInvalid` when the - /// flow field was the first object the call was given, and a call back in + /// grid flow field was the first object the call was given, and a call back in /// from host code it ran (`mnav_errorBusy`). /// /// @param field The field. @@ -104,38 +104,29 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetFlowFieldMisuse(const mnavFlowField* field); - - // A window of a grid's cells: columns x to x + width - 1 and rows y to - // y + height - 1. - typedef struct mnavFlowRegion - { - int32_t x; - int32_t y; - int32_t width; - int32_t height; - } mnavFlowRegion; + MNAV_API uint64_t mnavGridFlow_GetMisuse(const mnavGridFlow* field); /// Begins building the field for a region of a grid and a set of goal /// cells: one search from all the goals, with the steps of - /// mnavFindGridPath, to the 8 neighbours, never across a blocked + /// mnavQuery_FindGridPath, to the 8 neighbours, never across a blocked /// corner, a step costing its length times the mean of its two cells' /// area costs. Cells outside the region are as blocked. A cell's way /// to the goals is a cheapest one, ties going to the neighbour found /// first by cost, then by index; the same grid, region and goals give /// the same field on every platform, however the work is divided. /// Blocked goals and goals outside the region are left out. - /// mnavContinueFlowField does the work. + /// mnavGridFlow_Continue does the work. /// /// @param field The field; its last field is dropped. /// @param grid The grid; its areas are read until the work ends. - /// @param filter The areas usable and their costs, or NULL; copied. - /// @param region The region, or NULL for the whole grid. + /// @param region The cells searched, both corners included, or NULL for + /// the whole grid. /// @param goals The goal cells, in grid places. /// @param goalCount How many, at least 0. + /// @param filter The areas usable and their costs, or NULL; copied. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// negative count, a grid with no areas, a side below 1 or a cell size not - /// more than 0 or not finite, a region empty or not within the grid, a goal + /// more than 0 or not finite, a region backward or not within the grid, a goal /// outside the grid, or a filter not built from mnavDefaultQueryFilter or /// with a NaN cost; `mnav_errorLimit` for a side past MNAV_MAX_GRID_SIDE or /// a filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity @@ -145,11 +136,10 @@ extern "C" /// misuse counted. /// @par Thread safety /// Safe from any thread; the field is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBeginFlowField(mnavFlowField* field, - const mnavGrid* grid, - const mnavQueryFilter* filter, - const mnavFlowRegion* region, - const mnavCell* goals, int32_t goalCount); + MNAV_NODISCARD MNAV_API mnavResult mnavGridFlow_Begin(mnavGridFlow* field, const mnavGrid* grid, + const mnavCellRange* region, + const mnavCell* goals, int32_t goalCount, + const mnavQueryFilter* filter); /// Continues the work begun on a field: settles up to a number of /// cells, fewer when the work ends first. @@ -158,7 +148,9 @@ extern "C" /// @param grid The grid the work began on, its areas unchanged; /// it may lie elsewhere in memory. /// @param cells The most cells to settle, at least 1. - /// @param endedOut Receives whether the work has ended. May be NULL. + /// @param endedOut Receives whether the work has ended. May be NULL. Zeroed + /// on any failure but mnav_errorBusy, which leaves it as it + /// was. /// @return `mnav_success`, also when the work had already ended; /// `mnav_errorInvalid` for a NULL field or grid, fewer than 1 cell, or a /// grid of another size or cell size; `mnav_errorState` for no work begun. @@ -166,16 +158,16 @@ extern "C" /// one misuse counted. /// @par Thread safety /// Safe from any thread; the field is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavContinueFlowField(mnavFlowField* field, + MNAV_NODISCARD MNAV_API mnavResult mnavGridFlow_Continue(mnavGridFlow* field, const mnavGrid* grid, int32_t cells, bool* endedOut); /// Begins repairing the field after its goals or the areas of some of /// its grid's cells changed: only the cells whose ways /// change are searched again, and when the work ends the field is the - /// one mnavBeginFlowField would make with the new goals and areas, bit + /// one mnavGridFlow_Begin would make with the new goals and areas, bit /// for bit. The cells whose ways ran through what changed are reset in - /// this call; mnavContinueFlowField does the rest by its budget. + /// this call; mnavGridFlow_Continue does the rest by its budget. /// /// @param field The field; its work must have ended. /// @param grid The grid the field was begun on, of the same @@ -195,43 +187,43 @@ extern "C" /// misuse counted. /// @par Thread safety /// Safe from any thread; the field is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavUpdateFlowField(mnavFlowField* field, + MNAV_NODISCARD MNAV_API mnavResult mnavGridFlow_Update(mnavGridFlow* field, const mnavGrid* grid, const mnavCell* goals, int32_t goalCount, const mnavCell* changed, int32_t changedCount); /// Builds the field for a whole grid and a set of goal cells: begins as - /// mnavBeginFlowField and continues to the end. + /// mnavGridFlow_Begin and continues to the end. /// /// @param field The field; its last field is replaced. /// @param grid The grid, read only during the call. - /// @param filter The areas usable and their costs, or NULL. /// @param goals The goal cells. /// @param goalCount How many, at least 0. - /// @return As mnavBeginFlowField. Also `mnav_errorBusy` when called back in + /// @param filter The areas usable and their costs, or NULL. + /// @return As mnavGridFlow_Begin. Also `mnav_errorBusy` when called back in /// from the field's allocator, one misuse counted. /// @par Thread safety /// Safe from any thread; the field is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBuildFlowField(mnavFlowField* field, - const mnavGrid* grid, - const mnavQueryFilter* filter, - const mnavCell* goals, int32_t goalCount); + MNAV_NODISCARD MNAV_API mnavResult mnavGridFlow_Build(mnavGridFlow* field, const mnavGrid* grid, + const mnavCell* goals, int32_t goalCount, + const mnavQueryFilter* filter); /// Reads a cell's way to the goals from the field. /// /// @param field The field. /// @param cell The cell, in grid places. - /// @param flowOut Receives the cell's way, its next cell in grid places. + /// @param flowOut Receives the cell's way, its next cell in grid places. Zeroed + /// on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument; - /// `mnav_errorOutside` for a cell outside the field's region, flowOut left - /// as it was; `mnav_errorState` for nothing begun, or while work begun on + /// `mnav_errorOutside` for a cell outside the field's region; + /// `mnav_errorState` for nothing begun, or while work begun on /// the field has not ended. /// @par Thread safety /// Safe from any thread. Any number of threads may read a field at once /// while no work runs on it. - MNAV_NODISCARD MNAV_API mnavResult mnavFlowAt(const mnavFlowField* field, mnavCell cell, - mnavFlow* flowOut); + MNAV_NODISCARD MNAV_API mnavResult mnavGridFlow_GetFlow(const mnavGridFlow* field, + mnavCell cell, mnavCellFlow* flowOut); /// Appends an arrow for each cell of the field that has a next cell /// (mnav_debugFlow): from the cell's center toward the next one's, 0.8 @@ -240,7 +232,9 @@ extern "C" /// /// @param field The field. /// @param height The arrows' height. - /// @param buffer The buffer appended to. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. /// @return `mnav_success`, appending nothing when nothing was begun; /// `mnav_errorInvalid` for a NULL argument, a height not finite, or a /// buffer with a count out of range, an array missing or an origin not @@ -251,7 +245,7 @@ extern "C" /// Safe from any thread. Any number of threads may read a field at once /// while no work runs on it; the buffer is used by one thread at a /// time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugFlowField(const mnavFlowField* field, double height, + MNAV_NODISCARD MNAV_API mnavResult mnavGridFlow_Debug(const mnavGridFlow* field, double height, mnavDebugBuffer* buffer); #ifdef __cplusplus diff --git a/third_party/maul-nav/include/maul-nav/hierarchy.h b/third_party/maul-nav/include/maul-nav/hierarchy.h index 6272d2e0f..e4cc2d813 100644 --- a/third_party/maul-nav/include/maul-nav/hierarchy.h +++ b/third_party/maul-nav/include/maul-nav/hierarchy.h @@ -58,10 +58,10 @@ extern "C" // ran. typedef struct mnavHierarchyReport { - int32_t clusters; - int32_t transitions; - int32_t edges; - int32_t searches; + int32_t clusterCount; + int32_t transitionCount; + int32_t edgeCount; + int32_t searchCount; } mnavHierarchyReport; // The cookie mnavDefaultHierarchyDef sets and mnavCreateHierarchy checks. @@ -97,9 +97,10 @@ extern "C" /// @param hierarchyOut Receives the hierarchy, or NULL on failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a def /// not from mnavDefaultHierarchyDef; `mnav_errorVersion` for one built - /// against another version of the headers; `mnav_errorInvalid` also for a - /// limit or cluster side below 1; `mnav_errorLimit` for one past its - /// MNAV_MAX_ value; `mnav_errorCapacity` when the allocator fails. + /// against another version of the headers; `mnav_errorInvalid` also for an + /// allocator with one function, or a limit or cluster side below 1; + /// `mnav_errorLimit` for one past its MNAV_MAX_ value; `mnav_errorCapacity` + /// when the allocator fails. /// @par Thread safety /// Safe from any thread. MNAV_NODISCARD MNAV_API mnavResult mnavCreateHierarchy(const mnavHierarchyDef* def, @@ -123,7 +124,7 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetHierarchyMisuse(const mnavHierarchy* hierarchy); + MNAV_API uint64_t mnavHierarchy_GetMisuse(const mnavHierarchy* hierarchy); /// Builds the hierarchy for a navmesh as committed and a filter. Each /// run of tile links along a cluster border, one way, is an entrance @@ -142,7 +143,8 @@ extern "C" /// @param navmesh The navmesh. /// @param filter The areas usable and their costs, or NULL; the /// hierarchy keeps a copy. - /// @param reportOut Receives what was made. May be NULL. + /// @param reportOut Receives what was made. May be NULL. Zeroed on any + /// failure but mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// filter not built from mnavDefaultQueryFilter or with a NaN cost; /// `mnav_errorLimit` for a filter cost past MNAV_MIN_AREA_COST or @@ -154,12 +156,13 @@ extern "C" /// context, one misuse counted. /// @par Thread safety /// Safe from any thread; the hierarchy and the context are used by one - /// thread at a time, and no commit runs on the navmesh. - MNAV_NODISCARD MNAV_API mnavResult mnavBuildHierarchy(mnavHierarchy* hierarchy, - mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavHierarchyReport* reportOut); + /// thread at a time, no search reads the hierarchy, and no commit runs on + /// the navmesh. + MNAV_NODISCARD MNAV_API mnavResult mnavHierarchy_Build(mnavHierarchy* hierarchy, + mnavQuery* query, + const mnavNavmesh* navmesh, + const mnavQueryFilter* filter, + mnavHierarchyReport* reportOut); /// Brings the hierarchy up to the navmesh's last commit. When only /// polygon areas changed, only the edges of transitions entering the @@ -172,23 +175,25 @@ extern "C" /// @param query A context for the searches within clusters; its /// last search ends. /// @param navmesh The navmesh. - /// @param reportOut Receives what the graph holds and the searches run. - /// May be NULL. + /// @param reportOut Receives what the graph holds and the searches run. May + /// be NULL. Zeroed on any failure but mnav_errorBusy, which + /// leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// hierarchy built for another navmesh; `mnav_errorState` for a hierarchy - /// with no graph; `mnav_errorCapacity` as mnavBuildHierarchy. On an error + /// with no graph; `mnav_errorCapacity` as mnavHierarchy_Build. On an error /// the hierarchy holds no graph. Also `mnav_errorBusy` when called back in /// from the hierarchy's allocator, or from a clearance test whose link /// generation uses the context, one misuse counted. /// @par Thread safety /// Safe from any thread; the hierarchy and the context are used by one - /// thread at a time, and no commit runs on the navmesh. - MNAV_NODISCARD MNAV_API mnavResult mnavUpdateHierarchy(mnavHierarchy* hierarchy, - mnavQuery* query, - const mnavNavmesh* navmesh, - mnavHierarchyReport* reportOut); - - /// Finds a path as mnavFindPath does, with the hierarchy's filter, + /// thread at a time, no search reads the hierarchy, and no commit runs on + /// the navmesh. + MNAV_NODISCARD MNAV_API mnavResult mnavHierarchy_Update(mnavHierarchy* hierarchy, + mnavQuery* query, + const mnavNavmesh* navmesh, + mnavHierarchyReport* reportOut); + + /// Finds a path as mnavQuery_FindPath does, with the hierarchy's filter, /// through the hierarchy: the start and the end join their clusters' /// transitions by searches within those clusters, A* over the /// transitions picks the clusters to cross, and the navmesh search @@ -199,28 +204,35 @@ extern "C" /// platform, but which one is not promised; the paths they refine to /// may differ a little in cost. /// - /// @param query The context; its memory holds the path. + /// @param query The context; its memory holds the path and the + /// search over the hierarchy's transitions. /// @param hierarchy The hierarchy, built for this navmesh. /// @param navmesh The navmesh. /// @param startPolygon The polygon the start lies in. /// @param start The start point. /// @param endPolygon The polygon the end lies in. /// @param end The end point. - /// @param pathOut Receives the path. + /// @param pathOut Receives the path. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a point - /// with a NaN coordinate, or a hierarchy built for another navmesh; + /// that is not finite, or a hierarchy built for another navmesh; /// `mnav_errorState` for a hierarchy with no graph, or a navmesh committed - /// since the build; the errors of mnavFindPath otherwise. Also - /// `mnav_errorBusy` when called back in from host code a call using the - /// context runs (a link generation's clearance test, a hierarchy's + /// since the build; `mnav_errorLimit` for a point more than + /// MNAV_MAX_QUERY_DISTANCE from the navmesh's origin on an axis; + /// `mnav_errorCapacity` for a hierarchy of more transitions than the + /// context's transitions limit; the errors of mnavQuery_FindPath otherwise. + /// Also `mnav_errorBusy` when called back in from host code a call using + /// the context runs (a link generation's clearance test, a hierarchy's /// allocator as it builds), one misuse counted. /// @par Thread safety - /// Safe from any thread; the context and the hierarchy are used by one - /// thread at a time. - MNAV_NODISCARD MNAV_API mnavResult - mnavFindHierarchicalPath(mnavQuery* query, mnavHierarchy* hierarchy, const mnavNavmesh* navmesh, - mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, - mnavPos3 end, mnavPath* pathOut); + /// Safe from any thread; the context is used by one thread at a time. + /// The search only reads the hierarchy, so any number of contexts may + /// search one at once, as they do a navmesh, while no build or update + /// runs on it and no commit on the navmesh. + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindHierarchicalPath( + mnavQuery* query, const mnavHierarchy* hierarchy, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, + mnavPath* pathOut); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/linkgen.h b/third_party/maul-nav/include/maul-nav/linkgen.h index c5f8300e9..8a2fcedfa 100644 --- a/third_party/maul-nav/include/maul-nav/linkgen.h +++ b/third_party/maul-nav/include/maul-nav/linkgen.h @@ -25,16 +25,16 @@ extern "C" // Decides whether the way between two points is free for the agent: // the host's collision test. True to keep the link. It is called only - // within mnavGenerateLinks, on the thread that called it, before that + // within mnavQuery_GenerateLinks, on the thread that called it, before that // call returns; never afterward or from another thread. Its answer // decides the links, so it runs in the library's floating-point // environment: round to nearest, denormals kept. It may call what only // reads the navmesh, with a query context of its own, and - // mnavStageTile and mnavStageTileRemoval, which change nothing + // mnavNavmesh_StageTile and mnavNavmesh_StageTileRemoval, which change nothing // generation reads; while it runs, the navmesh's other stage calls, // commit and destroy and every call on the generating context are // refused with mnav_errorBusy, one misuse counted on that object. - typedef bool (*mnavClearanceFn)(void* context, mnavPos3 from, mnavPos3 to); + typedef bool mnavClearanceFunction(void* context, mnavPos3 from, mnavPos3 to); // How links are generated. Build it with mnavDefaultLinkGenDef. typedef struct mnavLinkGenDef @@ -65,11 +65,11 @@ extern "C" mnavLinkKind dropKind; mnavLinkKind jumpKind; // The host's collision test, or NULL for none, and its context. - mnavClearanceFn clear; + mnavClearanceFunction* clearance; void* context; } mnavLinkGenDef; -// The cookie mnavDefaultLinkGenDef sets and mnavGenerateLinks checks. +// The cookie mnavDefaultLinkGenDef sets and mnavQuery_GenerateLinks checks. #define MNAV_LINK_GEN_DEF_COOKIE 0x4E41564Cu /// Returns the default generation def: samples every 1 m, drops up to @@ -77,7 +77,7 @@ extern "C" /// when walking is within 3 times as far or their ends within 1 m of /// another's, a snap radius of 0.5 m, costs 2 and 4, kinds /// mnav_linkDrop and mnav_linkJump, and no clearance test. It carries - /// only a cookie: mnavGenerateLinks also takes a navmesh and a query + /// only a cookie: mnavQuery_GenerateLinks also takes a navmesh and a query /// context, whose creations checked the version. /// /// @return The def. @@ -112,40 +112,54 @@ extern "C" /// surface between the agent's step height and `dropMax` below, and a /// jump, landing from `2 * radius` to `jumpMax` out at the nearest /// distance where a surface lies within the step height of the start, - /// the radius, step height and cell size being the navmesh's. A link is - /// kept when the clearance test, if any, passes it, when the navmesh - /// does not already walk from its start to its landing within `detour` - /// times the straight distance, and when no earlier link has both ends - /// within `filterDistance` of its own. The order is tile place, - /// polygon, edge, sample; the same navmesh gives the same links. + /// the radius, step height and cell size being the navmesh's. Neither + /// lands on the polygon it starts from. A link is kept when the + /// clearance test, if any, passes it, when the navmesh does not already + /// walk from its start to its landing within `detour` times the + /// straight distance, and when no earlier link in the buffer has both + /// ends within `filterDistance` of its own. The order is tile + /// place, polygon, edge, sample; the same navmesh gives the same links. + /// A buffer too short holds the same first links as a longer one, but + /// links past it are not filtered against each other, since the + /// context holds no more than the buffer does: the count may then + /// exceed what a long enough buffer keeps, and is always room enough. /// /// @param query A context for the walking searches; its last search /// ends. /// @param navmesh The navmesh. + /// @param def The def, from mnavDefaultLinkGenDef. + /// @param tiles The tile places whose edges are sampled, or NULL for + /// every tile. /// @param filter The areas links may start and land on, and walk /// through, or NULL. - /// @param def The def, from mnavDefaultLinkGenDef. - /// @param tileX0 The first tile column. - /// @param tileZ0 The first tile row. - /// @param tileX1 The last tile column, at least tileX0. - /// @param tileZ1 The last tile row, at least tileZ0. - /// @param linksOut Receives the links, up to capacity. + /// @param linksOut Receives the links, up to capacity. Zeroed over the + /// capacity on any failure but those that keep the links + /// found, as countOut lists them; mnav_errorBusy leaves it + /// as it was. /// @param capacity The buffer's size in links, at least 0. - /// @param countOut Receives the number of links generated, also beyond - /// the capacity. + /// @param countOut Receives the number of links generated, also beyond the + /// capacity, where it is room enough for a second call + /// and may be more than that call keeps. Zeroed on any + /// failure but those that count the links found: + /// mnav_errorCapacity, and mnav_errorLimit when generation + /// stops at a bound; mnav_errorBusy leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a def /// not from mnavDefaultLinkGenDef, a def value NaN or below 0, a spacing of /// 0 or below the navmesh's cell size, a detour other than 0 below 1 or - /// past 10^6, a kind of MNAV_LINK_KINDS or more, a tile range backward, or - /// a filter not built from mnavDefaultQueryFilter or with a NaN cost; - /// `mnav_errorLimit` for a def value past its MNAV_MAX_ bound, an infinity - /// included, or a filter cost past MNAV_MIN_AREA_COST or + /// past 10^6, a negative kind or one of MNAV_LINK_KINDS or more, a tile + /// range backward, or a filter not built from mnavDefaultQueryFilter or + /// with a NaN cost; `mnav_errorLimit` for a def value past its MNAV_MAX_ + /// bound, an infinity included, or a filter cost past MNAV_MIN_AREA_COST or /// MNAV_MAX_AREA_COST, an infinity included; `mnav_errorCapacity` when more - /// links were generated than the buffer holds, the first capacity written; - /// `mnav_errorLimit` when generation stops, the links found so far counted - /// and the first capacity written: at a link past MNAV_MAX_LINKS, the most - /// a navmesh holds, or at a landing's search box meeting more than - /// MNAV_MAX_LINK_LANDINGS polygons. Also `mnav_errorBusy` when called back + /// links were generated than the buffer holds, the first capacity written + /// and the count room enough for every link, generation stopping once the + /// count reaches MNAV_MAX_LINKS, which is then the count; `mnav_errorLimit` + /// when generation stops, the links found so far counted and the first + /// capacity written: at a link past MNAV_MAX_LINKS, the most a navmesh + /// holds, in a buffer holding that many, or at a landing's search box + /// meeting more than MNAV_MAX_LINK_LANDINGS polygons. A call that measures + /// with a short buffer is refused with mnav_errorLimit only where one with + /// room for the count would be too. Also `mnav_errorBusy` when called back /// in from host code a call using the context runs (a link generation's /// clearance test, a hierarchy's allocator as it builds), one misuse /// counted. @@ -153,12 +167,12 @@ extern "C" /// Safe from any thread; the context is used by one thread at a time, /// and no commit runs on the navmesh. The def's clearance test runs on /// the calling thread, within the call; while it runs, the navmesh's - /// stage calls but mnavStageTile and mnavStageTileRemoval are refused + /// stage calls but mnavNavmesh_StageTile and mnavNavmesh_StageTileRemoval are refused /// with `mnav_errorBusy` from any thread. MNAV_NODISCARD MNAV_API mnavResult - mnavGenerateLinks(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavLinkGenDef* def, int32_t tileX0, int32_t tileZ0, int32_t tileX1, - int32_t tileZ1, mnavLinkDef* linksOut, int32_t capacity, int32_t* countOut); + mnavQuery_GenerateLinks(mnavQuery* query, const mnavNavmesh* navmesh, const mnavLinkGenDef* def, + const mnavTileRange* tiles, const mnavQueryFilter* filter, + mnavLinkDef* linksOut, int32_t capacity, int32_t* countOut); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/navflow.h b/third_party/maul-nav/include/maul-nav/navflow.h index 6a44c1e89..f1e446c5a 100644 --- a/third_party/maul-nav/include/maul-nav/navflow.h +++ b/third_party/maul-nav/include/maul-nav/navflow.h @@ -24,20 +24,20 @@ extern "C" // The most polygons, tile slots and off-mesh links a navmesh flow field // may hold. -#define MNAV_MAX_NAVFLOW_POLYGONS 16777216 -#define MNAV_MAX_NAVFLOW_TILES 1048576 -#define MNAV_MAX_NAVFLOW_LINKS 1048576 +#define MNAV_MAX_NAV_FLOW_POLYGONS 16777216 +#define MNAV_MAX_NAV_FLOW_TILES 1048576 +#define MNAV_MAX_NAV_FLOW_LINKS 1048576 // What a navmesh flow field may hold; its memory is taken for them all // when it is made, and a build needs no more. typedef struct mnavNavFlowLimits { - // Polygons in the tiles searched, 1 to MNAV_MAX_NAVFLOW_POLYGONS. + // Polygons in the tiles searched, 1 to MNAV_MAX_NAV_FLOW_POLYGONS. int32_t polygons; - // Tile slots of the navmesh, 1 to MNAV_MAX_NAVFLOW_TILES. + // Tile slots of the navmesh, 1 to MNAV_MAX_NAV_FLOW_TILES. int32_t tiles; // Off-mesh links of the navmesh, counted as its links limit counts - // them (by crossings), 1 to MNAV_MAX_NAVFLOW_LINKS. + // them (by crossings), 1 to MNAV_MAX_NAV_FLOW_LINKS. int32_t links; } mnavNavFlowLimits; @@ -114,9 +114,10 @@ extern "C" /// @param fieldOut Receives the field, or NULL on failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a def /// not from mnavDefaultNavFlowDef; `mnav_errorVersion` for one built - /// against another version of the headers; `mnav_errorInvalid` also for a - /// limit below 1; `mnav_errorLimit` for a limit past its MNAV_MAX_NAVFLOW_ - /// value; `mnav_errorCapacity` when the allocator fails. + /// against another version of the headers; `mnav_errorInvalid` also for an + /// allocator with one function or a limit below 1; `mnav_errorLimit` for a + /// limit past its MNAV_MAX_NAV_FLOW_ value; `mnav_errorCapacity` when the + /// allocator fails. /// @par Thread safety /// Safe from any thread. MNAV_NODISCARD MNAV_API mnavResult mnavCreateNavFlow(const mnavNavFlowDef* def, @@ -140,40 +141,31 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetNavFlowMisuse(const mnavNavFlow* field); - - // A box of tile places, from (x0, z0) to (x1, z1), both included. - typedef struct mnavNavFlowRegion - { - int32_t x0; - int32_t z0; - int32_t x1; - int32_t z1; - } mnavNavFlowRegion; + MNAV_API uint64_t mnavNavFlow_GetMisuse(const mnavNavFlow* field); /// Begins building the field over the tiles of a region, as - /// mnavBuildNavFlow builds it over all; polygons outside the region + /// mnavNavFlow_Build builds it over all; polygons outside the region /// are as left out, and only those inside count toward the field's - /// limit. mnavContinueNavFlow does the work; however it is divided, the + /// limit. mnavNavFlow_Continue does the work; however it is divided, the /// field is the same. /// /// @param field The field; its last field is dropped. /// @param navmesh The navmesh; the work needs it unchanged. - /// @param filter The areas usable and their costs, and the link - /// kinds, or NULL; copied. /// @param region The tiles searched, or NULL for all. /// @param goals The goals; those outside the region are left out. /// @param goalCount How many, at least 0. - /// @return As mnavBuildNavFlow; also `mnav_errorInvalid` for a region with + /// @param filter The areas usable and their costs, and the link + /// kinds, or NULL; copied. + /// @return As mnavNavFlow_Build; also `mnav_errorInvalid` for a region with /// x0 past x1 or z0 past z1. Also `mnav_errorBusy` when called back in from /// the field's allocator, one misuse counted. /// @par Thread safety /// Safe from any thread. Any number of threads may read the navmesh at /// once while no commit runs on it; the field is used by one thread at /// a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBeginNavFlow( - mnavNavFlow* field, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavNavFlowRegion* region, const mnavNavFlowGoal* goals, int32_t goalCount); + MNAV_NODISCARD MNAV_API mnavResult mnavNavFlow_Begin( + mnavNavFlow* field, const mnavNavmesh* navmesh, const mnavTileRange* region, + const mnavNavFlowGoal* goals, int32_t goalCount, const mnavQueryFilter* filter); /// Continues the work begun on a field: settles up to a number of /// polygons, fewer when the work ends first. @@ -181,7 +173,9 @@ extern "C" /// @param field The field. /// @param navmesh The navmesh the work began on. /// @param polygons The most polygons to settle, at least 1. - /// @param endedOut Receives whether the work has ended. May be NULL. + /// @param endedOut Receives whether the work has ended. May be NULL. Zeroed + /// on any failure but mnav_errorBusy, which leaves it as it + /// was. /// @return `mnav_success`, also when the work had already ended; /// `mnav_errorInvalid` for a NULL field or navmesh, fewer than 1 polygon, /// or a navmesh other than the field's; `mnav_errorState` for nothing @@ -192,9 +186,9 @@ extern "C" /// Safe from any thread. Any number of threads may read the navmesh at /// once while no commit runs on it; the field is used by one thread at /// a time. - MNAV_NODISCARD MNAV_API mnavResult mnavContinueNavFlow(mnavNavFlow* field, - const mnavNavmesh* navmesh, - int32_t polygons, bool* endedOut); + MNAV_NODISCARD MNAV_API mnavResult mnavNavFlow_Continue(mnavNavFlow* field, + const mnavNavmesh* navmesh, + int32_t polygons, bool* endedOut); /// Builds the field for a navmesh and a set of goal points by one /// search backward from all of them, as path searches price their ways: @@ -210,49 +204,51 @@ extern "C" /// /// @param field The field; its last field is replaced. /// @param navmesh The navmesh; reads need it unchanged since. - /// @param filter The areas usable and their costs, and the link - /// kinds, or NULL; copied. /// @param goals The goals. /// @param goalCount How many, at least 0. + /// @param filter The areas usable and their costs, and the link + /// kinds, or NULL; copied. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// negative count, a goal polygon id never handed out or a goal point not /// finite, or a filter not built from mnavDefaultQueryFilter or with a NaN - /// cost; `mnav_errorLimit` for a filter cost past MNAV_MIN_AREA_COST or - /// MNAV_MAX_AREA_COST, an infinity included; `mnav_errorStale` for a goal - /// polygon whose tile was replaced or removed; `mnav_errorCapacity` for a - /// navmesh of more polygons (in the tiles searched), tile slots or off-mesh - /// links than the field's limits. A build allocates nothing. On an error - /// the field holds nothing. Also `mnav_errorBusy` when called back in from - /// the field's allocator, one misuse counted. + /// cost; `mnav_errorLimit` for a goal point more than + /// MNAV_MAX_QUERY_DISTANCE from the navmesh's origin on an axis, or a + /// filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity + /// included; `mnav_errorStale` for a goal polygon whose tile was replaced + /// or removed; `mnav_errorCapacity` for a navmesh of more polygons (in the + /// tiles searched), tile slots or off-mesh links than the field's limits. A + /// build allocates nothing. On an error the field holds nothing. Also + /// `mnav_errorBusy` when called back in from the field's allocator, one + /// misuse counted. /// @par Thread safety /// Safe from any thread. Any number of threads may read the navmesh at /// once while no commit runs on it; the field is used by one thread at /// a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBuildNavFlow(mnavNavFlow* field, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - const mnavNavFlowGoal* goals, - int32_t goalCount); + MNAV_NODISCARD MNAV_API mnavResult mnavNavFlow_Build(mnavNavFlow* field, + const mnavNavmesh* navmesh, + const mnavNavFlowGoal* goals, + int32_t goalCount, + const mnavQueryFilter* filter); /// Reads a polygon's way to the goals from the field. /// /// @param field The field. /// @param navmesh The navmesh the field was built on. /// @param polygon The polygon. - /// @param flowOut Receives its way. + /// @param flowOut Receives its way. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// navmesh other than the field's or a polygon id never handed out; - /// `mnav_errorOutside` for a polygon outside the region, flowOut left as it - /// was; `mnav_errorState` for nothing begun, a navmesh committed to since - /// the work began, or work not yet ended; `mnav_errorStale` for a polygon - /// whose tile was replaced or removed. + /// `mnav_errorOutside` for a polygon outside the region; `mnav_errorState` + /// for nothing begun, a navmesh committed to since the work began, or work + /// not yet ended; `mnav_errorStale` for a polygon whose tile was replaced + /// or removed. /// @par Thread safety /// Safe from any thread. Any number of threads may read a field at once /// while no build runs on it. - MNAV_NODISCARD MNAV_API mnavResult mnavNavFlowAt(const mnavNavFlow* field, - const mnavNavmesh* navmesh, - mnavPolygonId polygon, - mnavPolygonFlow* flowOut); + MNAV_NODISCARD MNAV_API mnavResult mnavNavFlow_GetFlow(const mnavNavFlow* field, + const mnavNavmesh* navmesh, + mnavPolygonId polygon, + mnavPolygonFlow* flowOut); /// Appends an arrow for each polygon of the field that has a way on /// (mnav_debugFlow): from the mean of the polygon's corners to the @@ -262,7 +258,9 @@ extern "C" /// /// @param field The field. /// @param navmesh The navmesh the field was built on. - /// @param buffer The buffer appended to. + /// @param buffer The buffer appended to. Left as it was on any failure but + /// the buffer filling (mnav_errorCapacity), which writes what + /// fits and counts what the whole needs. /// @return `mnav_success`, appending nothing when nothing was begun; /// `mnav_errorInvalid` for a NULL argument, a navmesh other than the /// field's, or a buffer with a count out of range, an array missing or an @@ -273,9 +271,9 @@ extern "C" /// Safe from any thread. Any number of threads may read a field at once /// while no build runs on it; the buffer is used by one thread at a /// time. - MNAV_NODISCARD MNAV_API mnavResult mnavDebugNavFlow(const mnavNavFlow* field, - const mnavNavmesh* navmesh, - mnavDebugBuffer* buffer); + MNAV_NODISCARD MNAV_API mnavResult mnavNavFlow_Debug(const mnavNavFlow* field, + const mnavNavmesh* navmesh, + mnavDebugBuffer* buffer); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/navmesh.h b/third_party/maul-nav/include/maul-nav/navmesh.h index ed5e613f5..14a29bbf2 100644 --- a/third_party/maul-nav/include/maul-nav/navmesh.h +++ b/third_party/maul-nav/include/maul-nav/navmesh.h @@ -20,9 +20,122 @@ extern "C" { #endif +// The largest values a navmesh's limits may take. +#define MNAV_MAX_TILES 1048576 +#define MNAV_MAX_TILE_LINKS 1048576 +#define MNAV_MAX_LINKS 1048576 + // A navmesh. Made by mnavCreateNavmesh. typedef struct mnavNavmesh mnavNavmesh; + // A navmesh's runtime tier: what may change once loaded. + // Tiles and off-mesh links load and unload in every tier. + typedef int32_t mnavTier; + + enum + { + // Nothing loaded changes. + mnav_tierStatic = 0, + // Polygons' areas change, and off-mesh links are enabled and + // disabled. + mnav_tierModifiers = 1, + // Also, a loaded tile is replaced in one commit. + mnav_tierDynamic = 2, + }; + + // The named limits of a navmesh. Each count is at least 1 and at most + // its MNAV_MAX_ value. Reaching one is mnav_errorCapacity. + typedef struct mnavNavmeshLimits + { + // Tiles committed at once. + int32_t tiles; + // Links one tile has to its own polygons and its neighbors', made + // as it is committed. + int32_t tileLinks; + // Off-mesh links, staged or committed, counted by crossings: a + // point link takes 1, an edge link 1 + ceil(width / spacing) up to + // MNAV_MAX_LINK_CROSSINGS (see mnavLinkDef.width). + int32_t links; + // Bytes the navmesh may hold allocated at once, at least 1. + uint64_t memoryBytes; + } mnavNavmeshLimits; + + // How a navmesh is made. Build it with mnavDefaultNavmeshDef. + typedef struct mnavNavmeshDef + { + uint32_t cookie; + // MNAV_ABI_VERSION, set by mnavDefaultNavmeshDef. + uint32_t version; + // The allocator the navmesh uses; zeroed for the C library's. + mnavAllocator allocator; + // The def the navmesh's tiles are baked with, checked whole as a + // baker checks it. The navmesh reads only what a tile's header + // records of it, the origin, the cell size and height, the tile + // cells and the agent, and refuses a tile baked with other values; + // its allocator, limits and clock are the bakers'. + mnavBakeDef bake; + // What may change once loaded. + mnavTier tier; + mnavNavmeshLimits limits; + } mnavNavmeshDef; + + // The setting a navmesh def check refused. + typedef int32_t mnavNavmeshSetting; + + enum + { + mnav_navmeshSettingNone = 0, + mnav_navmeshSettingCookie = 1, + mnav_navmeshSettingAllocator = 2, + // The bake def, its own setting named beside. + mnav_navmeshSettingBake = 3, + mnav_navmeshSettingTier = 4, + mnav_navmeshSettingTiles = 5, + mnav_navmeshSettingTileLinks = 6, + mnav_navmeshSettingLinks = 7, + mnav_navmeshSettingMemoryBytes = 8, + }; + + // A navmesh def check's outcome: the status, the first setting it + // refused, and for mnav_navmeshSettingBake, the bake def's setting + // (mnav_bakeSettingNone otherwise). + typedef struct mnavNavmeshDefResult + { + mnavResult result; + mnavNavmeshSetting setting; + mnavBakeSetting bakeSetting; + } mnavNavmeshDefResult; + +// The cookie mnavDefaultNavmeshDef sets and mnavCreateNavmesh checks. +#define MNAV_NAVMESH_DEF_COOKIE 0x4E41564Du + + /// Returns the default navmesh def: tiles of the default bake def, the + /// static tier, and limits sized for a large level: 65536 tiles, 32768 + /// links in a tile, 4096 off-mesh links and 256 MiB. + /// Built in the program, so that it carries the version of the + /// headers the program includes. + /// + /// @return The def, with a valid cookie. + /// @par Thread safety + /// Safe from any thread. + static inline mnavNavmeshDef mnavDefaultNavmeshDef(void) + { +#ifdef __cplusplus + mnavNavmeshDef def = {}; +#else + mnavNavmeshDef def = {0}; +#endif + def.cookie = MNAV_NAVMESH_DEF_COOKIE; + def.version = MNAV_ABI_VERSION; + def.bake = mnavDefaultBakeDef(); + def.tier = mnav_tierStatic; + def.limits.tiles = 65536; + def.limits.tileLinks = 32768; + def.limits.links = 4096; + def.limits.memoryBytes = 268435456; + return def; + } + // A tile in a navmesh: its 1-based slot (0 for none) and the slot's // generation, which changes whenever the slot's tile does. typedef struct mnavTileId @@ -39,8 +152,17 @@ extern "C" uint32_t polygon; } mnavPolygonId; + // A box of tile places, from (x0, z0) to (x1, z1), both included. + typedef struct mnavTileRange + { + int32_t x0; + int32_t z0; + int32_t x1; + int32_t z1; + } mnavTileRange; + // The part of tile bytes a load refused. - typedef uint8_t mnavTileSection; + typedef int32_t mnavTileSection; enum { @@ -67,19 +189,24 @@ extern "C" int32_t index; } mnavTileResult; - /// Makes an empty navmesh for tiles baked with a def: checks the def - /// and keeps a copy, its allocator and its limits. + /// Makes an empty navmesh from a def: checks the def and keeps a copy, + /// its allocator and its limits. /// - /// @param def The def the tiles are baked with. + /// @param def The def, from mnavDefaultNavmeshDef. /// @param navmeshOut Receives the navmesh, or NULL on failure. - /// @return `mnav_success`; for a def mnavValidateBakeDef refuses, the - /// status and setting it gives; `mnav_errorInvalid` for a NULL - /// navmeshOut; `mnav_errorCapacity` when the navmesh does not fit the - /// def's memory limit or the allocator fails. + /// @return `mnav_success`; `mnav_errorInvalid` with the setting for a NULL + /// def, a def without its cookie, an allocator with one function, a + /// negative tier or one past mnav_tierDynamic, a limit below 1; + /// `mnav_errorLimit` with the setting for a limit past its MNAV_MAX_ value; + /// `mnav_errorVersion` with no setting for a def built against another + /// version of the headers; for a bake def mnavValidateBakeDef refuses, the + /// status it gives with mnav_navmeshSettingBake and its setting; + /// `mnav_errorInvalid` for a NULL navmeshOut; `mnav_errorCapacity` when the + /// navmesh does not fit the def's memory limit or the allocator fails. /// @par Thread safety /// Safe from any thread. - MNAV_NODISCARD MNAV_API mnavBakeDefResult mnavCreateNavmesh(const mnavBakeDef* def, - mnavNavmesh** navmeshOut); + MNAV_NODISCARD MNAV_API mnavNavmeshDefResult mnavCreateNavmesh(const mnavNavmeshDef* def, + mnavNavmesh** navmeshOut); /// Destroys a navmesh, its tiles and everything staged. Called back in from /// host code a call on the navmesh runs (its allocator, a link generation's @@ -92,12 +219,12 @@ extern "C" MNAV_API void mnavDestroyNavmesh(mnavNavmesh* navmesh); /// Loads tile bytes, checking every field as hostile input and the - /// tile's settings against the navmesh's def, and stages the tile for + /// tile's settings against the navmesh's bake def, and stages the tile for /// the next commit, in place of anything staged at its place before. /// Queries do not see it until the commit. /// /// @param navmesh The navmesh. - /// @param bytes The tile's bytes, as mnavCopyBakedTile gives them. + /// @param bytes The tile's bytes, as mnavBaker_CopyTile gives them. /// @param size Their size in bytes. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument; /// `mnav_errorFormat` naming the section and element for malformed bytes, @@ -112,8 +239,8 @@ extern "C" /// Safe from any thread; the navmesh is used by one thread at a time. /// Staging changes nothing queries read, but it may not run beside /// another call that stages or commits. - MNAV_NODISCARD MNAV_API mnavTileResult mnavStageTile(mnavNavmesh* navmesh, const uint8_t* bytes, - size_t size); + MNAV_NODISCARD MNAV_API mnavTileResult mnavNavmesh_StageTile(mnavNavmesh* navmesh, + const uint8_t* bytes, size_t size); /// Stages the removal of the tile at a place for the next commit, in /// place of anything staged there before. @@ -130,8 +257,8 @@ extern "C" /// Safe from any thread; the navmesh is used by one thread at a time. /// Staging changes nothing queries read, but it may not run beside /// another call that stages or commits. - MNAV_NODISCARD MNAV_API mnavResult mnavStageTileRemoval(mnavNavmesh* navmesh, int32_t tileX, - int32_t tileZ); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_StageTileRemoval(mnavNavmesh* navmesh, + int32_t tileX, int32_t tileZ); /// Applies everything staged at once: installs and removes the tiles /// and links every polygon on a changed tile's sides to the polygons @@ -150,7 +277,7 @@ extern "C" /// @par Thread safety /// Safe from any thread; the navmesh is used by one thread at a time, /// so no query may run during a commit. - MNAV_NODISCARD MNAV_API mnavResult mnavCommit(mnavNavmesh* navmesh); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_Commit(mnavNavmesh* navmesh); /// Reads a navmesh's runtime tier. /// @@ -160,7 +287,7 @@ extern "C" /// Safe from any thread. Any number of threads may read it and query /// at once between commits; none may while a stage or commit call /// runs. - MNAV_API mnavTier mnavGetTier(const mnavNavmesh* navmesh); + MNAV_API mnavTier mnavNavmesh_GetTier(const mnavNavmesh* navmesh); /// Returns how many calls against the navmesh were refused as misuse since /// it was made: an argument refused with `mnav_errorInvalid` when the @@ -172,7 +299,7 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetNavmeshMisuse(const mnavNavmesh* navmesh); + MNAV_API uint64_t mnavNavmesh_GetMisuse(const mnavNavmesh* navmesh); /// Stages a change of a polygon's area, applied at the next commit; /// mnav_areaNone blocks it. The change lasts while the polygon's tile @@ -192,14 +319,15 @@ extern "C" /// @par Thread safety /// Safe from any thread; the navmesh is used by one thread at a time, /// so no query may run during a stage call. - MNAV_NODISCARD MNAV_API mnavResult mnavStageArea(mnavNavmesh* navmesh, mnavPolygonId polygon, - mnavAreaType area); + MNAV_NODISCARD MNAV_API mnavResult mnavPolygon_StageArea(mnavNavmesh* navmesh, + mnavPolygonId polygon, + mnavAreaType area); /// Reads a polygon's area as committed. /// /// @param navmesh The navmesh. /// @param polygon The polygon. - /// @param areaOut Receives its area. + /// @param areaOut Receives its area. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or /// an id never handed out; `mnav_errorStale` for a polygon whose tile /// has been replaced or removed. @@ -207,23 +335,26 @@ extern "C" /// Safe from any thread. Any number of threads may read areas and /// query at once between commits; none may while a stage or commit /// call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavGetArea(const mnavNavmesh* navmesh, - mnavPolygonId polygon, mnavAreaType* areaOut); + MNAV_NODISCARD MNAV_API mnavResult mnavPolygon_GetArea(const mnavNavmesh* navmesh, + mnavPolygonId polygon, + mnavAreaType* areaOut); /// Finds the tile at a place. /// /// @param navmesh The navmesh. /// @param tileX The tile's column. /// @param tileZ The tile's row. - /// @param tileOut Receives the tile's id, or a zeroed id. + /// @param tileOut Receives the tile's id, or a zeroed id. Zeroed on any + /// failure. /// @return `mnav_success`; `mnav_errorNotLoaded` when no tile is /// committed there; `mnav_errorInvalid` for a NULL argument. /// @par Thread safety /// Safe from any thread. Any number of threads may find tiles and /// query at once between commits; none may while a stage or commit /// call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavGetTile(const mnavNavmesh* navmesh, int32_t tileX, - int32_t tileZ, mnavTileId* tileOut); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_GetTile(const mnavNavmesh* navmesh, + int32_t tileX, int32_t tileZ, + mnavTileId* tileOut); // The number of off-mesh link kinds. #define MNAV_LINK_KINDS 64 @@ -239,7 +370,7 @@ extern "C" // How an agent traverses an off-mesh link: kinds 0 to 5 are the // library's, 6 to MNAV_LINK_KINDS - 1 the host's to name. - typedef uint8_t mnavLinkKind; + typedef int32_t mnavLinkKind; enum { @@ -275,7 +406,7 @@ extern "C" // apart than the agent's radius (the cell size when the radius is // 0): 1 + ceil(width / spacing) crossings, at most // MNAV_MAX_LINK_CROSSINGS. Each crossing takes one of the navmesh's - // links (mnavBakeLimits.links), so a link 16 m wide for agents of + // links (mnavNavmeshLimits.links), so a link 16 m wide for agents of // 0.5 m takes 33; size the limit by crossings, not by links. float width; } mnavLinkDef; @@ -307,7 +438,7 @@ extern "C" // link; for an edge link, those whose ends both snapped, the // polygons and points above being the first of them along the // width. - int32_t crossings; + int32_t crossingCount; } mnavLinkState; /// Stages an off-mesh link; it is added at the next commit. Its ends @@ -316,22 +447,24 @@ extern "C" /// /// @param navmesh The navmesh. /// @param def The link. - /// @param linkOut Receives its id, usable once it is committed. + /// @param linkOut Receives its id, usable once it is committed. Zeroed on any + /// failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a point /// with a NaN coordinate, or an edge link whose ends lie at one place on /// the ground or so near that the square of their distance rounds to 0, a - /// radius, cost or width below 0 or NaN, or a kind of MNAV_LINK_KINDS or - /// more; `mnav_errorLimit` for a point farther from the def's origin than - /// bake input may lie (MNAV_MAX_EXTENT_CELLS cells on the ground, - /// MNAV_MAX_HEIGHT_CELLS cell heights up or down), a radius, cost or width - /// past its MNAV_MAX_ bound, an infinity included, or when a crossing would - /// need a slot from MNAV_MAX_LINKS on (left only by slots worn out by 2^32 - /// links each); `mnav_errorCapacity` when the links' crossings, staged and - /// committed, would pass the links limit, or memory its limit, or when the - /// allocator fails. A refused link is refused whole, none of its crossings - /// taken. Also `mnav_errorBusy` when called back in from host code a call - /// on the navmesh runs (its allocator, a link generation's clearance test, - /// a hierarchy's allocator as it builds on it), one misuse counted. + /// radius, cost or width below 0 or NaN, or a negative kind or one of + /// MNAV_LINK_KINDS or more; `mnav_errorLimit` for a point farther from the + /// def's origin than bake input may lie (MNAV_MAX_EXTENT_CELLS cells on the + /// ground, MNAV_MAX_HEIGHT_CELLS cell heights up or down), a radius, cost + /// or width past its MNAV_MAX_ bound, an infinity included, or when a + /// crossing would need a slot from MNAV_MAX_LINKS on (left only by slots + /// worn out by 2^32 links each); `mnav_errorCapacity` when the links' + /// crossings, staged and committed, would pass the links limit, or memory + /// its limit, or when the allocator fails. A refused link is refused whole, + /// none of its crossings taken. Also `mnav_errorBusy` when called back in + /// from host code a call on the navmesh runs (its allocator, a link + /// generation's clearance test, a hierarchy's allocator as it builds on + /// it), one misuse counted. /// @par Thread safety /// Safe from any thread; the navmesh is used by one thread at a time, /// so no query may run while a link is staged. @@ -369,14 +502,31 @@ extern "C" /// @par Thread safety /// Safe from any thread; the navmesh is used by one thread at a time, /// so no query may run during a stage call. - MNAV_NODISCARD MNAV_API mnavResult mnavStageLinkEnabled(mnavNavmesh* navmesh, mnavLinkId link, + MNAV_NODISCARD MNAV_API mnavResult mnavLink_StageEnable(mnavNavmesh* navmesh, mnavLinkId link, bool enabled); + /// Reads whether an off-mesh link may be crossed, as committed: a + /// toggle mnavLink_StageEnable stages shows only after the next commit. + /// + /// @param navmesh The navmesh. + /// @param link The link. + /// @param enabledOut Receives whether it is enabled. False on any + /// failure. + /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or an + /// id never handed out; `mnav_errorState` for a link staged and not yet + /// committed; `mnav_errorStale` for a link removed. + /// @par Thread safety + /// Safe from any thread. Any number of threads may read links and + /// query at once between commits; none may while a stage or commit + /// call runs. + MNAV_NODISCARD MNAV_API mnavResult mnavLink_IsEnabled(const mnavNavmesh* navmesh, + mnavLinkId link, bool* enabledOut); + /// Reads an off-mesh link as committed. /// /// @param navmesh The navmesh. /// @param link The link. - /// @param stateOut Receives its state. + /// @param stateOut Receives its state. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or an /// id never handed out; `mnav_errorState` for a link staged and not yet /// committed; `mnav_errorStale` for a link removed. @@ -384,8 +534,8 @@ extern "C" /// Safe from any thread. Any number of threads may read links and /// query at once between commits; none may while a stage or commit /// call runs. - MNAV_NODISCARD MNAV_API mnavResult mnavGetLink(const mnavNavmesh* navmesh, mnavLinkId link, - mnavLinkState* stateOut); + MNAV_NODISCARD MNAV_API mnavResult mnavLink_GetState(const mnavNavmesh* navmesh, + mnavLinkId link, mnavLinkState* stateOut); #ifdef __cplusplus } diff --git a/third_party/maul-nav/include/maul-nav/query.h b/third_party/maul-nav/include/maul-nav/query.h index 8d6e0f01b..d36efecff 100644 --- a/third_party/maul-nav/include/maul-nav/query.h +++ b/third_party/maul-nav/include/maul-nav/query.h @@ -89,7 +89,7 @@ extern "C" typedef struct mnavFound { // The polygons found, also beyond the buffer's capacity. - int32_t count; + int32_t polygonCount; // Whether part of the box lies on places with no tile loaded. bool incomplete; } mnavFound; @@ -99,30 +99,32 @@ extern "C" /// by tile, in place order (x, then z), then by polygon index. /// /// @param navmesh The navmesh. - /// @param filter The areas wanted, or NULL for every walkable one; - /// one including mnav_areaNone finds blocked polygons. /// @param center The box's center. /// @param halfExtents The box's half sizes, in meters, at least 0. + /// @param filter The areas wanted, or NULL for every walkable one; + /// one including mnav_areaNone finds blocked polygons. /// @param polygonsOut Receives up to capacity ids; NULL when capacity - /// is 0. + /// is 0. Zeroed over the capacity on any failure but + /// mnav_errorCapacity, which keeps the first capacity. /// @param capacity The room, at least 0. - /// @param foundOut Receives the count and whether the box was all - /// loaded. + /// @param foundOut Receives the count and whether the box was all loaded. + /// Zeroed on any failure but mnav_errorCapacity, which gives + /// the total. /// @return `mnav_success`; `mnav_errorCapacity` when more polygons meet the /// box than the buffer holds: it holds the first ones and foundOut counts /// all; `mnav_errorInvalid` for a NULL argument, a negative capacity, a /// center or extent that is not finite or a negative extent, or a filter /// not built from mnavDefaultQueryFilter or with a NaN cost; - /// `mnav_errorLimit` for a filter cost past MNAV_MIN_AREA_COST or - /// MNAV_MAX_AREA_COST, an infinity included. + /// `mnav_errorLimit` for a center more than MNAV_MAX_QUERY_DISTANCE from + /// the navmesh's origin on an axis, or a filter cost past + /// MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity included. /// @par Thread safety /// Safe from any thread. Any number of queries may run at once between /// commits. - MNAV_NODISCARD MNAV_API mnavResult mnavFindPolygons(const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPos3 center, mnavVec3 halfExtents, - mnavPolygonId* polygonsOut, - int32_t capacity, mnavFound* foundOut); + MNAV_NODISCARD MNAV_API mnavResult + mnavNavmesh_FindPolygons(const mnavNavmesh* navmesh, mnavPos3 center, mnavVec3 halfExtents, + const mnavQueryFilter* filter, mnavPolygonId* polygonsOut, + int32_t capacity, mnavFound* foundOut); /// Finds the polygon, among those the filter includes, nearest a point /// whose nearest point lies within a box round it, and that point. A @@ -132,23 +134,24 @@ extern "C" /// polygon index. /// /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL for every walkable one. /// @param point The query point. /// @param halfExtents The box's half sizes, in meters, at least 0. - /// @param nearestOut Receives the result. + /// @param filter The areas usable, or NULL for every walkable one. + /// @param nearestOut Receives the result. Zeroed on any failure. /// @return `mnav_success`, also when no polygon's nearest point lies in the /// box (the polygon's slot is then 0); `mnav_errorInvalid` for a NULL /// argument, a point or extent that is not finite, a negative extent, or a /// filter not built from mnavDefaultQueryFilter or with a NaN cost; - /// `mnav_errorLimit` for a filter cost past MNAV_MIN_AREA_COST or + /// `mnav_errorLimit` for a point more than MNAV_MAX_QUERY_DISTANCE from the + /// navmesh's origin on an axis, or a filter cost past MNAV_MIN_AREA_COST or /// MNAV_MAX_AREA_COST, an infinity included. /// @par Thread safety /// Safe from any thread. Any number of queries may run at once between /// commits. - MNAV_NODISCARD MNAV_API mnavResult mnavFindNearest(const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPos3 point, mnavVec3 halfExtents, - mnavNearest* nearestOut); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_FindNearest(const mnavNavmesh* navmesh, + mnavPos3 point, mnavVec3 halfExtents, + const mnavQueryFilter* filter, + mnavNearest* nearestOut); // The most corners a polygon clipped to a circle holds: a polygon's six // and one more for each of the 32 sides of the 32-gon standing for the @@ -159,6 +162,12 @@ extern "C" #define MNAV_MAX_QUERY_NODES 1048576 // The longest path length limit, in meters. #define MNAV_MAX_PATH_LENGTH 1.0e7f +// How far a query's points, ends, centers and radii may reach from the +// navmesh's origin (its bake def's origin), in meters, on each axis; one +// past it is mnav_errorLimit. Within it, every difference between such a +// point and the navmesh's own, and its square, is a finite double, so a +// query's arithmetic never overflows; no world comes near it. +#define MNAV_MAX_QUERY_DISTANCE 1.0e100 // A query context: the scratch memory searches use, sized by its // limits when made, so searches never allocate. Made by @@ -175,6 +184,11 @@ extern "C" // MNAV_MAX_PATH_LENGTH: no node is opened whose way from the start // and on to the end is longer. float pathLength; + // The transitions of the largest hierarchy the context searches + // (mnavQuery_FindHierarchicalPath), 0 to + // MNAV_MAX_HIERARCHY_TRANSITIONS; 0 for a context that searches + // none. The context holds the search over them, 28 bytes each. + int32_t transitions; } mnavQueryLimits; // How a query context is made. Build it with mnavDefaultQueryDef. @@ -190,7 +204,7 @@ extern "C" } mnavQueryDef; // How a path search ended, checked in this order. - typedef uint8_t mnavPathEnd; + typedef int32_t mnavPathEnd; enum { @@ -243,13 +257,19 @@ extern "C" // until its next search. const mnavPathLink* links; int32_t linkCount; + // The context whose memory holds the arrays, and which of its paths + // this is, set by the search that gives it, so that mnavSetCorridor + // can tell the arrays are still this path's. + const mnavQuery* query; + uint64_t generation; } mnavPath; // The cookie mnavDefaultQueryDef sets and mnavCreateQuery checks. #define MNAV_QUERY_DEF_COOKIE 0x4E415651u /// Returns a query def with 8,192 nodes per search, paths up to - /// 1,000 m and the C library's allocator. Built in the program, so + /// 1,000 m, room for a hierarchy of 16,384 transitions, as the default + /// hierarchy def's, and the C library's allocator. Built in the program, so /// that it carries the version of the headers the program includes. /// /// @return The def. @@ -266,6 +286,7 @@ extern "C" def.version = MNAV_ABI_VERSION; def.limits.nodes = 8192; def.limits.pathLength = 1000.0f; + def.limits.transitions = 16384; return def; } @@ -275,11 +296,13 @@ extern "C" /// @param queryOut Receives the context, or NULL on failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a def /// not from mnavDefaultQueryDef; `mnav_errorVersion` for one built against - /// another version of the headers; `mnav_errorInvalid` also for fewer than - /// 1 node or a path length not more than 0 or NaN; `mnav_errorLimit` for - /// more nodes than MNAV_MAX_QUERY_NODES or a path length past - /// MNAV_MAX_PATH_LENGTH, an infinity included; `mnav_errorCapacity` when - /// the allocator fails. + /// another version of the headers; `mnav_errorInvalid` also for an + /// allocator with one function, fewer than 1 node, a path length not more + /// than 0 or NaN, or fewer than 0 transitions; `mnav_errorLimit` for more + /// nodes than MNAV_MAX_QUERY_NODES, a path length past + /// MNAV_MAX_PATH_LENGTH, an infinity included, or more transitions than + /// MNAV_MAX_HIERARCHY_TRANSITIONS; `mnav_errorCapacity` when the + /// allocator fails. /// @par Thread safety /// Safe from any thread. MNAV_NODISCARD MNAV_API mnavResult mnavCreateQuery(const mnavQueryDef* def, @@ -305,7 +328,7 @@ extern "C" /// @par Thread safety /// Safe from any thread. It may be read at any time, also while host /// code the object runs calls back in. - MNAV_API uint64_t mnavGetQueryMisuse(const mnavQuery* query); + MNAV_API uint64_t mnavQuery_GetMisuse(const mnavQuery* query); /// Searches for the shortest way from a point on one polygon to a point /// on another: A* over the edges between polygons, its @@ -321,32 +344,32 @@ extern "C" /// /// @param query The context; its memory holds the result. /// @param navmesh The navmesh. - /// @param filter The areas usable and their costs, or NULL for - /// every walkable area at a cost of 1. /// @param startPolygon The polygon the start point lies on, as - /// mnavFindNearest gives it. + /// mnavNavmesh_FindNearest gives it. /// @param start The start point. /// @param endPolygon The polygon the end point lies on. /// @param end The end point. - /// @param pathOut Receives the result. + /// @param filter The areas usable and their costs, or NULL for + /// every walkable area at a cost of 1. + /// @param pathOut Receives the result. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success` whenever a search ran, however it ended; /// `mnav_errorInvalid` for a NULL argument, a point that is not finite, a /// polygon id that never existed, or a filter not built from - /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a - /// filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity - /// included; `mnav_errorStale` for a polygon id whose tile has been - /// replaced or removed. Also `mnav_errorBusy` when called back in from host - /// code a call using the context runs (a link generation's clearance test, - /// a hierarchy's allocator as it builds), one misuse counted. + /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a point + /// more than MNAV_MAX_QUERY_DISTANCE from the navmesh's origin on an axis, + /// or a filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an + /// infinity included; `mnav_errorStale` for a polygon id whose tile has + /// been replaced or removed. Also `mnav_errorBusy` when called back in from + /// host code a call using the context runs (a link generation's clearance + /// test, a hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// Any number of contexts may search one navmesh at once between /// commits. - MNAV_NODISCARD MNAV_API mnavResult mnavFindPath(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPolygonId startPolygon, mnavPos3 start, - mnavPolygonId endPolygon, mnavPos3 end, - mnavPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindPath( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, const mnavQueryFilter* filter, mnavPath* pathOut); /// Searches for the shortest way on the ground from a point on one /// polygon to a point on another: Polyanya (Cui, Harabor @@ -360,77 +383,80 @@ extern "C" /// their declared cost, from the takeoff point to the landing point. /// Ties go to the node made first. When the nodes run out, the /// A* search tells whether the end can be reached at all, and an end it - /// cannot reach ends the search as no path. The result is that of mnavFindPath: + /// cannot reach ends the search as no path. The result is that of mnavQuery_FindPath: /// the polygons crossed, the turning points, which are the path, and /// the links crossed; cost and length are those of the points, in three /// dimensions. A sliced path search in the same context ends. /// /// @param query The context; its memory holds the result. /// @param navmesh The navmesh. - /// @param filter The areas usable and their costs, or NULL for - /// every walkable area at a cost of 1. /// @param startPolygon The polygon the start point lies on, as - /// mnavFindNearest gives it. + /// mnavNavmesh_FindNearest gives it. /// @param start The start point. /// @param endPolygon The polygon the end point lies on. /// @param end The end point. - /// @param pathOut Receives the result. + /// @param filter The areas usable and their costs, or NULL for + /// every walkable area at a cost of 1. + /// @param pathOut Receives the result. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success` whenever a search ran, however it ended; /// `mnav_errorInvalid` for a NULL argument, a point that is not finite, a /// polygon id that never existed, a filter not built from /// mnavDefaultQueryFilter or with a NaN cost or one whose included areas - /// differ in cost; `mnav_errorLimit` for a filter cost past - /// MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity included; - /// `mnav_errorStale` for a polygon id whose tile has been replaced or - /// removed. Also `mnav_errorBusy` when called back in from host code a call - /// using the context runs (a link generation's clearance test, a - /// hierarchy's allocator as it builds), one misuse counted. + /// differ in cost; `mnav_errorLimit` for a point more than + /// MNAV_MAX_QUERY_DISTANCE from the navmesh's origin on an axis, or a + /// filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity + /// included; `mnav_errorStale` for a polygon id whose tile has been + /// replaced or removed. Also `mnav_errorBusy` when called back in from host + /// code a call using the context runs (a link generation's clearance test, + /// a hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// Any number of contexts may search one navmesh at once between /// commits. - MNAV_NODISCARD MNAV_API mnavResult - mnavFindShortestPath(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId startPolygon, mnavPos3 start, - mnavPolygonId endPolygon, mnavPos3 end, mnavPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindShortestPath( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, const mnavQueryFilter* filter, mnavPath* pathOut); /// Begins a path search to run in slices: the same search - /// as mnavFindPath, with its own copy of the filter, continued by - /// mnavContinuePath and ended by mnavFinishPath. Whatever the slices, - /// the result is the one mnavFindPath gives. + /// as mnavQuery_FindPath, with its own copy of the filter, continued by + /// mnavQuery_ContinuePath and ended by mnavQuery_FinishPath. Whatever the slices, + /// the result is the one mnavQuery_FindPath gives. /// /// @param query The context; it holds the search until the next /// search of any kind, or a move along the /// surface, begins. /// @param navmesh The navmesh. - /// @param filter The areas usable and their costs, or NULL for - /// every walkable area at a cost of 1; copied. /// @param startPolygon The polygon the start point lies on. /// @param start The start point. /// @param endPolygon The polygon the end point lies on. /// @param end The end point. + /// @param filter The areas usable and their costs, or NULL for + /// every walkable area at a cost of 1; copied. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a point /// that is not finite, a polygon id that never existed or a filter not /// built from mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` - /// for a filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an - /// infinity included; `mnav_errorStale` for a polygon id whose tile has - /// been replaced or removed. Also `mnav_errorBusy` when called back in from - /// host code a call using the context runs (a link generation's clearance - /// test, a hierarchy's allocator as it builds), one misuse counted. + /// for a point more than MNAV_MAX_QUERY_DISTANCE from the navmesh's origin + /// on an axis, or a filter cost past MNAV_MIN_AREA_COST or + /// MNAV_MAX_AREA_COST, an infinity included; `mnav_errorStale` for a + /// polygon id whose tile has been replaced or removed. Also + /// `mnav_errorBusy` when called back in from host code a call using the + /// context runs (a link generation's clearance test, a hierarchy's + /// allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavBeginPath(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPolygonId startPolygon, mnavPos3 start, - mnavPolygonId endPolygon, mnavPos3 end); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_BeginPath( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, const mnavQueryFilter* filter); - /// Continues a search begun by mnavBeginPath: closes up to a number of + /// Continues a search begun by mnavQuery_BeginPath: closes up to a number of /// nodes, fewer when the search ends first. /// /// @param query The context. /// @param navmesh The navmesh the search began on. /// @param nodes The most nodes to close, at least 1. - /// @param endedOut Receives whether the search has ended. + /// @param endedOut Receives whether the search has ended. Zeroed on any + /// failure but mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, fewer /// than 1 node, or a navmesh other than the search's; `mnav_errorState` for /// no search begun, or a navmesh committed to since the search began: begin @@ -439,17 +465,18 @@ extern "C" /// hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavContinuePath(mnavQuery* query, - const mnavNavmesh* navmesh, int32_t nodes, - bool* endedOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_ContinuePath(mnavQuery* query, + const mnavNavmesh* navmesh, + int32_t nodes, bool* endedOut); - /// Ends a search begun by mnavBeginPath and writes its result: as - /// mnavFindPath's when it has ended; otherwise toward the node nearest + /// Ends a search begun by mnavQuery_BeginPath and writes its result: as + /// mnavQuery_FindPath's when it has ended; otherwise toward the node nearest /// the end, its end mnav_pathUnfinished. /// /// @param query The context; its memory holds the result. /// @param navmesh The navmesh the search began on. - /// @param pathOut Receives the result. + /// @param pathOut Receives the result. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// navmesh other than the search's; `mnav_errorState` for no search begun, /// or a navmesh committed to since the search began. Also `mnav_errorBusy` @@ -458,11 +485,12 @@ extern "C" /// misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavFinishPath(mnavQuery* query, const mnavNavmesh* navmesh, - mnavPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FinishPath(mnavQuery* query, + const mnavNavmesh* navmesh, + mnavPath* pathOut); // How a move along the surface ended. - typedef uint8_t mnavMoveEnd; + typedef int32_t mnavMoveEnd; enum { @@ -500,40 +528,46 @@ extern "C" /// /// @param query The context; its memory holds the polygons. /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL for every walkable one. /// @param startPolygon The polygon the start point lies on. /// @param start The start point. /// @param end The wanted point; only its ground position /// counts. - /// @param moveOut Receives the result. + /// @param filter The areas usable, or NULL for every walkable one. + /// @param moveOut Receives the result. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success` whenever the move ran, however it ended; /// `mnav_errorInvalid` for a NULL argument, a point that is not finite, a /// polygon id that never existed or a filter not built from - /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a - /// filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity - /// included; `mnav_errorStale` for a polygon id whose tile has been - /// replaced or removed. Also `mnav_errorBusy` when called back in from host - /// code a call using the context runs (a link generation's clearance test, - /// a hierarchy's allocator as it builds), one misuse counted. + /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a point + /// more than MNAV_MAX_QUERY_DISTANCE from the navmesh's origin on an axis + /// (the end's x and z), or a filter cost past MNAV_MIN_AREA_COST or + /// MNAV_MAX_AREA_COST, an infinity included; `mnav_errorStale` for a + /// polygon id whose tile has been replaced or removed. Also + /// `mnav_errorBusy` when called back in from host code a call using the + /// context runs (a link generation's clearance test, a hierarchy's + /// allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// Any number of contexts may move on one navmesh at once between /// commits. - MNAV_NODISCARD MNAV_API mnavResult mnavMoveAlongSurface( - mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - mnavPolygonId startPolygon, mnavPos3 start, mnavPos3 end, mnavMove* moveOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_MoveAlongSurface( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, + mnavPos3 end, const mnavQueryFilter* filter, mnavMove* moveOut); // A path corridor: the polygons from the agent's to its // target's, in a buffer the caller owns, with the agent's position on // the first and the target on the last. It is well formed with a - // buffer, a capacity of at least 1, and from 1 to capacity polygons; - // every corridor call refuses one that is not. + // finite position and target, a buffer, a capacity of at least 1, and + // from 1 to capacity polygons; every corridor call refuses one that is + // not, and every one that takes a navmesh also one whose position or + // target lies more than MNAV_MAX_QUERY_DISTANCE from the navmesh's + // origin on an axis. typedef struct mnavCorridor { mnavPos3 position; mnavPos3 target; mnavPolygonId* polygons; - int32_t count; + int32_t polygonCount; int32_t capacity; } mnavCorridor; @@ -580,13 +614,19 @@ extern "C" /// position and its last as the target. /// /// @param corridor The corridor. - /// @param path A path from mnavFindPath or mnavFinishPath. + /// @param path A path from mnavQuery_FindPath or mnavQuery_FinishPath, + /// as the search gave it, before its context's next search. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// corridor with no buffer or a capacity below 1, or a path with no - /// polygon or point; `mnav_errorCapacity` when the path - /// has more polygons than the corridor's buffer, which is unchanged. + /// polygon or point, a NULL `polygons` or `points`, or a path no search + /// gave: arrays other than its context's, or more polygons, points or + /// links than the search gave; `mnav_errorStale` for a path a later + /// search of its context has replaced; `mnav_errorCapacity` when the + /// path has more polygons than the corridor's buffer. The corridor is + /// unchanged on any failure. /// @par Thread safety - /// Safe from any thread; the corridor is used by one thread at a time. + /// Safe from any thread; the corridor is used by one thread at a time, + /// and the path's context, which it reads, by no other thread meanwhile. MNAV_NODISCARD MNAV_API mnavResult mnavSetCorridor(mnavCorridor* corridor, const mnavPath* path); @@ -595,61 +635,71 @@ extern "C" /// edge, a tile link or an off-mesh link of a kind it includes. Fewer /// than all means the corridor needs trimming there and replanning. /// - /// @param navmesh The navmesh. - /// @param filter The areas and kinds usable, or NULL for all. - /// @param corridor The corridor. - /// @param validOut Receives the count. + /// @param navmesh The navmesh. + /// @param corridor The corridor. + /// @param filter The areas and kinds usable, or NULL for all. + /// @param validCountOut Receives the count. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// corridor not well formed or a filter not built from /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a - /// filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity - /// included. + /// corridor whose position or target lies more than MNAV_MAX_QUERY_DISTANCE + /// from the navmesh's origin on an axis, or a filter cost past + /// MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity included. /// @par Thread safety /// Safe from any thread. Any number of threads may check corridors at /// once between commits. - MNAV_NODISCARD MNAV_API mnavResult mnavCheckCorridor(const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - const mnavCorridor* corridor, - int32_t* validOut); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_CheckCorridor(const mnavNavmesh* navmesh, + const mnavCorridor* corridor, + const mnavQueryFilter* filter, + int32_t* validCountOut); /// Finds a corridor's straight path from its position to its target: /// the funnel over the portals between its polygons. /// /// @param query The context; its memory holds the corners. - /// @param navmesh The navmesh. /// @param corridor The corridor. - /// @param cornersOut Receives the straight path. + /// @param navmesh The navmesh. + /// @param cornersOut Receives the straight path. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a - /// corridor not well formed or polygons not joined; `mnav_errorStale` for a - /// polygon whose tile has been replaced or removed; `mnav_errorCapacity` - /// for a corridor of more polygons than the context's node limit. Also + /// corridor not well formed or polygons not joined; `mnav_errorLimit` for a + /// corridor whose position or target lies more than MNAV_MAX_QUERY_DISTANCE + /// from the navmesh's origin on an axis; `mnav_errorStale` for a polygon + /// whose tile has been replaced or removed; `mnav_errorCapacity` for a + /// corridor of more polygons than the context's node limit. Also /// `mnav_errorBusy` when called back in from host code a call using the /// context runs (a link generation's clearance test, a hierarchy's /// allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavCorridorCorners(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavCorridor* corridor, - mnavCorners* cornersOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindCorridorCorners(mnavQuery* query, + const mnavCorridor* corridor, + const mnavNavmesh* navmesh, + mnavCorners* cornersOut); /// Finds the areas along a corridor's straight path: the corners - /// mnavCorridorCorners gives, which replace the context's last ones, + /// mnavQuery_FindCorridorCorners gives, which replace the context's last ones, /// and a run where the path starts, one wherever it crosses into a /// polygon of another area, and one where it lands from each off-mesh /// link, in order. Hosts change gait or sound at a run's start; a /// path's areas are its corridor's, once loaded with mnavSetCorridor. /// /// @param query The context; its memory holds the corners. - /// @param navmesh The navmesh. /// @param corridor The corridor. - /// @param cornersOut Receives the straight path, or NULL. + /// @param navmesh The navmesh. + /// @param cornersOut Receives the straight path, or NULL. Zeroed on any + /// failure but mnav_errorBusy, which leaves it as it was. /// @param runsOut Receives the runs, in order. May be NULL when - /// capacity is 0. + /// capacity is 0. Zeroed over the capacity on any + /// failure but mnav_errorCapacity, which keeps the first + /// capacity, and mnav_errorBusy, which leaves it as it + /// was. /// @param capacity How many runs fit, at least 0. - /// @param countOut Receives how many runs there are, also when they - /// do not all fit. - /// @return As mnavCorridorCorners; also `mnav_errorInvalid` for a NULL + /// @param countOut Receives how many runs there are, also when they do not + /// all fit. Zeroed on any failure but mnav_errorCapacity, + /// which gives the total, and mnav_errorBusy, which leaves + /// it as it was. + /// @return As mnavQuery_FindCorridorCorners; also `mnav_errorInvalid` for a NULL /// countOut, a negative capacity or NULL runs with a positive one, and /// `mnav_errorCapacity` when the runs do not fit, the first capacity of /// them written. Also `mnav_errorBusy` when called back in from host code a @@ -657,8 +707,8 @@ extern "C" /// hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavCorridorAreas( - mnavQuery* query, const mnavNavmesh* navmesh, const mnavCorridor* corridor, + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindCorridorAreas( + mnavQuery* query, const mnavCorridor* corridor, const mnavNavmesh* navmesh, mnavCorners* cornersOut, mnavAreaRun* runsOut, int32_t capacity, int32_t* countOut); // The fastest mnavSteer may steer, and the longest distance it slows @@ -683,11 +733,11 @@ extern "C" float arriveDistance; // Whether to swing wide of the next corner so as to meet the turn // already heading out of it, as Detour's crowd does. - bool anticipateTurns; + bool enableTurnAnticipation; } mnavSteerDef; // What mnavSteer found. - typedef uint8_t mnavSteerState; + typedef int32_t mnavSteerState; enum { @@ -704,7 +754,7 @@ extern "C" { // On the ground (y is 0), in meters per second: the agent's // preferred velocity for avoidance, x and z. - mnavPos3 velocity; + mnavVec3 velocity; mnavSteerState state; // At a link: its index among the corners' links; otherwise -1. int32_t link; @@ -733,7 +783,7 @@ extern "C" def.maxSpeed = 3.5f; def.slowDistance = 1.0f; def.arriveDistance = 0.1f; - def.anticipateTurns = true; + def.enableTurnAnticipation = true; return def; } @@ -742,14 +792,19 @@ extern "C" /// its slowing distance of the target by the distance left along the /// corners over it; stopped within the arrival distance of the target /// or of the next link's takeoff. Keeps no state: call it each step - /// with the corners mnavCorridorCorners gives, feed the velocity to - /// avoidance, and limit acceleration and move the agent yourself. + /// with the corners mnavQuery_FindCorridorCorners gives, feed the velocity to + /// avoidance, and limit acceleration and move the agent yourself. The + /// corners may be the caller's own: their arrays are the caller's + /// memory, read only within `pointCount` and `linkCount`, every point + /// checked; a link's `point` is compared, never followed. /// /// @param corners The corners, the agent's position first. /// @param def The def, from mnavDefaultSteerDef. - /// @param steeringOut Receives the velocity and state. + /// @param steeringOut Receives the velocity and state. Left as it was on + /// failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, corners - /// with no points or a def not from mnavDefaultSteerDef; + /// with no points or a link count below 0, or a def not from + /// mnavDefaultSteerDef; /// `mnav_errorVersion` for one built against another version of the /// headers; `mnav_errorInvalid` also for a def value below 0 or NaN, or a /// corner not finite; `mnav_errorLimit` for a speed past @@ -762,19 +817,22 @@ extern "C" mnavSteering* steeringOut); /// Moves a corridor's position along the surface toward a wanted point - /// (mnavMoveAlongSurface from its first polygon) and merges the + /// (mnavQuery_MoveAlongSurface from its first polygon) and merges the /// polygons walked into its start: it keeps its polygons from the /// farthest one the walk passed, led to by the walk. /// /// @param query The context, for the walk. - /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL for every walkable one. /// @param corridor The corridor. + /// @param navmesh The navmesh. /// @param wanted Where the agent would be; only its ground position /// counts. - /// @param moveOut Receives the walk, or NULL. - /// @return As mnavMoveAlongSurface; also `mnav_errorInvalid` for a corridor - /// not well formed, and `mnav_errorCapacity` when the merged corridor would + /// @param filter The areas usable, or NULL for every walkable one. + /// @param moveOut Receives the walk, or NULL. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. + /// @return As mnavQuery_MoveAlongSurface; also `mnav_errorInvalid` for a + /// corridor not well formed, `mnav_errorLimit` for one whose position or + /// target lies more than MNAV_MAX_QUERY_DISTANCE from the navmesh's origin + /// on an axis, and `mnav_errorCapacity` when the merged corridor would /// outgrow its buffer, which leaves it unchanged. Also `mnav_errorBusy` /// when called back in from host code a call using the context runs (a link /// generation's clearance test, a hierarchy's allocator as it builds), one @@ -782,36 +840,33 @@ extern "C" /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// So is the corridor. - MNAV_NODISCARD MNAV_API mnavResult mnavMoveCorridor(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavCorridor* corridor, mnavPos3 wanted, - mnavMove* moveOut); + MNAV_NODISCARD MNAV_API mnavResult + mnavQuery_MoveCorridor(mnavQuery* query, mnavCorridor* corridor, const mnavNavmesh* navmesh, + mnavPos3 wanted, const mnavQueryFilter* filter, mnavMove* moveOut); /// Moves a corridor's target along the surface toward a wanted point - /// (mnavMoveAlongSurface from its last polygon) and merges the polygons + /// (mnavQuery_MoveAlongSurface from its last polygon) and merges the polygons /// walked into its end: it keeps its polygons up to the first one the /// walk passed, then the rest of the walk. /// /// @param query The context, for the walk. - /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL for every walkable one. /// @param corridor The corridor. + /// @param navmesh The navmesh. /// @param wanted Where the target would be; only its ground position /// counts. - /// @param moveOut Receives the walk, or NULL. - /// @return As mnavMoveCorridor. Also `mnav_errorBusy` when called back in + /// @param filter The areas usable, or NULL for every walkable one. + /// @param moveOut Receives the walk, or NULL. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. + /// @return As mnavQuery_MoveCorridor. Also `mnav_errorBusy` when called back in /// from host code a call using the context runs (a link generation's /// clearance test, a hierarchy's allocator as it builds), one misuse /// counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// So is the corridor. - MNAV_NODISCARD MNAV_API mnavResult mnavMoveCorridorTarget(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavCorridor* corridor, - mnavPos3 wanted, mnavMove* moveOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_MoveCorridorTarget( + mnavQuery* query, mnavCorridor* corridor, const mnavNavmesh* navmesh, mnavPos3 wanted, + const mnavQueryFilter* filter, mnavMove* moveOut); /// Shortens a corridor where the agent can see ahead: casts a ray from /// its position toward a point, usually a corner a few ahead; when the @@ -819,24 +874,25 @@ extern "C" /// start up to the last corridor polygon it passed, if that is fewer. /// /// @param query The context, for the ray. - /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL for every walkable one. /// @param corridor The corridor. + /// @param navmesh The navmesh. /// @param toward The point to look toward. - /// @param shortenedOut Receives whether the corridor changed. - /// @return As mnavRaycast; also `mnav_errorInvalid` for a corridor not well - /// formed or a NULL shortenedOut. Also `mnav_errorBusy` when called back in + /// @param filter The areas usable, or NULL for every walkable one. + /// @param shortenedOut Receives whether the corridor changed. Zeroed on any + /// failure but mnav_errorBusy, which leaves it as it was. + /// @return As mnavQuery_Raycast; also `mnav_errorInvalid` for a corridor + /// not well formed or a NULL shortenedOut, and `mnav_errorLimit` for one + /// whose position or target lies more than MNAV_MAX_QUERY_DISTANCE from the + /// navmesh's origin on an axis. Also `mnav_errorBusy` when called back in /// from host code a call using the context runs (a link generation's /// clearance test, a hierarchy's allocator as it builds), one misuse /// counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// So is the corridor. - MNAV_NODISCARD MNAV_API mnavResult mnavShortcutCorridor(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavCorridor* corridor, mnavPos3 toward, - bool* shortenedOut); + MNAV_NODISCARD MNAV_API mnavResult + mnavQuery_ShortcutCorridor(mnavQuery* query, mnavCorridor* corridor, const mnavNavmesh* navmesh, + mnavPos3 toward, const mnavQueryFilter* filter, bool* shortenedOut); /// Plans a corridor again from its position to its target and loads /// the path found: from its first and last polygons while they are @@ -845,28 +901,31 @@ extern "C" /// no polygon is left as it was, the path's end saying why. /// /// @param query The context, for the search; it holds the path. + /// @param corridor The corridor. /// @param navmesh The navmesh. + /// @param halfExtents The box's half sizes for finding polygons again, in + /// meters, at least 0; checked on every call. /// @param filter The areas usable and their costs, or NULL. - /// @param corridor The corridor. - /// @param halfExtents The box's half sizes for finding polygons again. /// @param pathOut Receives the path, or an empty one ended - /// mnav_pathNotLoaded or mnav_pathNone when an end - /// found no polygon. + /// mnav_pathNotLoaded or mnav_pathNone when an end found + /// no polygon. Zeroed on any failure but mnav_errorBusy, + /// which leaves it as it was. /// @return `mnav_success` whenever the corridor was looked at; - /// `mnav_errorInvalid` for a NULL argument or a corridor not well formed; - /// `mnav_errorCapacity` when the path has more polygons than the buffer, - /// which leaves the corridor as it was; the errors of mnavFindNearest and - /// mnavFindPath otherwise. Also `mnav_errorBusy` when called back in from - /// host code a call using the context runs (a link generation's clearance - /// test, a hierarchy's allocator as it builds), one misuse counted. + /// `mnav_errorInvalid` for a NULL argument, a corridor not well formed or a + /// half size below 0 or not finite; `mnav_errorLimit` for a corridor whose + /// position or target lies more than MNAV_MAX_QUERY_DISTANCE from the + /// navmesh's origin on an axis; `mnav_errorCapacity` when the path has more + /// polygons than the buffer, which leaves the corridor as it was; the + /// errors of mnavNavmesh_FindNearest and mnavQuery_FindPath otherwise. Also + /// `mnav_errorBusy` when called back in from host code a call using the + /// context runs (a link generation's clearance test, a hierarchy's + /// allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// So is the corridor. - MNAV_NODISCARD MNAV_API mnavResult mnavReplanCorridor(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavCorridor* corridor, - mnavVec3 halfExtents, mnavPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_ReplanCorridor( + mnavQuery* query, mnavCorridor* corridor, const mnavNavmesh* navmesh, mnavVec3 halfExtents, + const mnavQueryFilter* filter, mnavPath* pathOut); // The longest side a grid may have, in cells. #define MNAV_MAX_GRID_SIDE 32768 @@ -892,6 +951,15 @@ extern "C" int32_t y; } mnavCell; + // A box of a grid's cells, from (x0, y0) to (x1, y1), both included. + typedef struct mnavCellRange + { + int32_t x0; + int32_t y0; + int32_t x1; + int32_t y1; + } mnavCellRange; + // A grid path: how its search ended, its cost and length, and the // cells it turns at, the start first and the last cell reached last, // in a query context's memory until its next query. @@ -910,16 +978,17 @@ extern "C" /// mean of its two cells' area costs. When every area the filter /// includes costs the same, the search is jump point search, else A*; /// both keep the context's node limit and path length, and end as - /// mnavFindPath does, with the path to the cell nearest the end when + /// mnavQuery_FindPath does, with the path to the cell nearest the end when /// the end is not reached. A grid search ends any sliced search. /// /// @param query The context; its memory holds the path. /// @param grid The grid. - /// @param filter The areas usable and their costs, or NULL. /// @param start The start cell. /// @param end The end cell. - /// @param pathOut Receives the path; mnav_pathNone with the start - /// alone when the start is blocked. + /// @param filter The areas usable and their costs, or NULL. + /// @param pathOut Receives the path; mnav_pathNone with the start alone when + /// the start is blocked. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a grid /// with no areas, a side below 1 or a cell size not more than 0 or not /// finite, a cell outside the grid, or a filter not built from @@ -931,10 +1000,11 @@ extern "C" /// misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavFindGridPath(mnavQuery* query, const mnavGrid* grid, - const mnavQueryFilter* filter, - mnavCell start, mnavCell end, - mnavGridPath* pathOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindGridPath(mnavQuery* query, + const mnavGrid* grid, mnavCell start, + mnavCell end, + const mnavQueryFilter* filter, + mnavGridPath* pathOut); /// Reads the height of a polygon's detail surface at a point on the /// ground. @@ -943,87 +1013,94 @@ extern "C" /// @param polygon The polygon. /// @param x The point's X. /// @param z The point's Z. - /// @param heightOut Receives the height. + /// @param heightOut Receives the height. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a /// coordinate not finite or a polygon id never handed out; - /// `mnav_errorOutside` for a point outside the polygon on the ground, - /// heightOut left as it was; `mnav_errorStale` for a polygon whose tile has - /// gone. + /// `mnav_errorLimit` for a coordinate more than MNAV_MAX_QUERY_DISTANCE + /// from the navmesh's origin on an axis; `mnav_errorOutside` for a point + /// outside the polygon on the ground; `mnav_errorStale` for a polygon whose + /// tile has gone. /// @par Thread safety /// Safe from any thread. Any number of threads may read the navmesh /// at once while no commit runs on it. - MNAV_NODISCARD MNAV_API mnavResult mnavGetHeight(const mnavNavmesh* navmesh, - mnavPolygonId polygon, double x, double z, - double* heightOut); + MNAV_NODISCARD MNAV_API mnavResult mnavPolygon_GetHeight(const mnavNavmesh* navmesh, + mnavPolygonId polygon, double x, + double z, double* heightOut); - // The nearest wall a search within a radius found. + // A wall within a radius: an edge no polygon the filter includes lies + // across. typedef struct mnavWall { - // Whether a wall lies within the radius; when not, the distance is - // the radius and the point the center. + // Its ends, in the order of its polygon's corners. + mnavPos3 start; + mnavPos3 end; + // Its point nearest the center. + mnavPos3 point; + // The unit direction on the ground from it toward its polygon, the + // walkable side; zero for an edge of no length on the ground. The + // way from the wall to the center is (center - point) / distance. + mnavVec3 normal; + // The distance on the ground from the center to its nearest point. + double distance; + } mnavWall; + + // The nearest wall a search within a radius found. + typedef struct mnavNearestWall + { + // Whether a wall lies within the radius. bool found; // Whether the context's node limit cut the search short, so that a // nearer wall may lie unseen. bool limited; - // The distance on the ground from the center to the wall point. - double distance; - // The wall's nearest point, and the unit direction on the ground - // from it to the center. - mnavPos3 point; - mnavPos3 normal; - } mnavWall; + // The wall; zeroed when none was found. + mnavWall wall; + } mnavNearestWall; /// Finds the nearest wall to a point within a radius on the ground, /// searching from its polygon across the edges that come within the /// radius, off-mesh links aside. An edge is a wall where no polygon the /// filter includes lies across it: a tile side with no tile loaded /// beyond, and the parts of a side no polygon of the tile beyond - /// covers, are walls. A wall exactly the radius away lies within it; of - /// walls at the same distance it gives one, the same on every - /// platform, but which one is not promised. + /// covers, are walls. A wall counts only on the center's floor: its + /// point nearest the center lies no more than the agent's step height + /// below the center's height on its polygon, and no more than the + /// agent's height above, for the agent the navmesh's tiles are baked + /// for; the walls of a floor over or under the center are left out. A + /// wall exactly the radius away lies within it; of walls at the same + /// distance it gives one, the same on every platform, but which one is + /// not promised. /// - /// @param query The context; its last search ends. - /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL. - /// @param polygon The polygon the center lies in. - /// @param center The point. - /// @param radius How far to look, in meters, at least 0 and finite. - /// @param wallOut Receives the wall. + /// @param query The context; its last search ends. + /// @param navmesh The navmesh. + /// @param polygon The polygon the center lies in. + /// @param center The point. + /// @param radius How far to look, in meters, at least 0 and at most + /// MNAV_MAX_QUERY_DISTANCE. + /// @param filter The areas usable, or NULL. + /// @param nearestOut Receives whether a wall was found, and the wall. Zeroed + /// on any failure but mnav_errorBusy, which leaves it as it + /// was. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument, a point /// or radius not finite, a negative radius, a polygon id never handed out, /// or a filter not built from mnavDefaultQueryFilter or with a NaN cost; /// `mnav_errorStale` for a polygon whose tile has gone; `mnav_errorLimit` - /// for a filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an - /// infinity included. Also `mnav_errorBusy` when called back in from host - /// code a call using the context runs (a link generation's clearance test, - /// a hierarchy's allocator as it builds), one misuse counted. + /// for a center more than MNAV_MAX_QUERY_DISTANCE from the navmesh's origin + /// on an axis, a radius past MNAV_MAX_QUERY_DISTANCE, or a filter cost past + /// MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity included. Also + /// `mnav_errorBusy` when called back in from host code a call using the + /// context runs (a link generation's clearance test, a hierarchy's + /// allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavFindWallDistance(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPolygonId polygon, mnavPos3 center, - double radius, mnavWall* wallOut); - - // A wall within a radius: an edge no polygon the filter includes lies - // across. - typedef struct mnavWallSegment - { - // Its ends, in the order of its polygon's corners. - mnavPos3 start; - mnavPos3 end; - // The unit direction on the ground from it toward its polygon, the - // walkable side; zero for an edge of no length on the ground. - mnavPos3 normal; - // The distance on the ground from the center to its nearest point. - double distance; - } mnavWallSegment; + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindWallDistance( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId polygon, mnavPos3 center, + double radius, const mnavQueryFilter* filter, mnavNearestWall* nearestOut); // What a search for the walls within a radius found. typedef struct mnavWallsFound { // The walls within the radius, also beyond the buffer's capacity. - int32_t count; + int32_t wallCount; // Whether the context's node limit cut the search short, so that // more walls may lie unseen. bool limited; @@ -1031,39 +1108,45 @@ extern "C" /// Lists the walls within a radius of a point on the ground, nearest /// first, for avoidance's segment obstacles: the search and the walls - /// of mnavFindWallDistance, whose wall is the first listed. Walls at - /// the same distance keep the order the search meets them in, the same - /// on every platform. + /// of mnavQuery_FindWallDistance, whose wall is the first listed: only + /// walls on the center's floor, as it counts them. Walls at the same + /// distance keep the order the search meets them in, the same on every + /// platform. /// /// @param query The context; its last search ends. /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL. /// @param polygon The polygon the center lies in. /// @param center The point. - /// @param radius How far to look, in meters, at least 0 and finite. + /// @param radius How far to look, in meters, at least 0 and at most + /// MNAV_MAX_QUERY_DISTANCE. + /// @param filter The areas usable, or NULL. /// @param wallsOut Receives up to capacity walls, the nearest; NULL when - /// capacity is 0. + /// capacity is 0. Zeroed over the capacity on any failure + /// but mnav_errorCapacity, which keeps the first capacity, + /// and mnav_errorBusy, which leaves it as it was. /// @param capacity The room, at least 0. - /// @param foundOut Receives the count and whether the node limit cut - /// the search short. + /// @param foundOut Receives the count and whether the node limit cut the + /// search short. Zeroed on any failure but + /// mnav_errorCapacity, which gives the total, and + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success`; `mnav_errorCapacity` when more walls lie within /// the radius than the buffer holds: it holds the nearest and foundOut /// counts all; `mnav_errorInvalid` for a NULL argument, a negative /// capacity, a point or radius not finite, a negative radius, a polygon id /// never handed out, or a filter not built from mnavDefaultQueryFilter or /// with a NaN cost; `mnav_errorStale` for a polygon whose tile has gone; - /// `mnav_errorLimit` for a filter cost past MNAV_MIN_AREA_COST or - /// MNAV_MAX_AREA_COST, an infinity included. Also `mnav_errorBusy` when - /// called back in from host code a call using the context runs (a link - /// generation's clearance test, a hierarchy's allocator as it builds), one - /// misuse counted. + /// `mnav_errorLimit` for a center more than MNAV_MAX_QUERY_DISTANCE from + /// the navmesh's origin on an axis, a radius past MNAV_MAX_QUERY_DISTANCE, + /// or a filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an + /// infinity included. Also `mnav_errorBusy` when called back in from host + /// code a call using the context runs (a link generation's clearance test, + /// a hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavFindWalls(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPolygonId polygon, mnavPos3 center, - double radius, mnavWallSegment* wallsOut, - int32_t capacity, mnavWallsFound* foundOut); + MNAV_NODISCARD MNAV_API mnavResult + mnavQuery_FindWalls(mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId polygon, + mnavPos3 center, double radius, const mnavQueryFilter* filter, + mnavWall* wallsOut, int32_t capacity, mnavWallsFound* foundOut); // A random point and the polygon it lies on; slot 0 when there was no // polygon to pick. @@ -1079,9 +1162,9 @@ extern "C" /// surface. The call reads every polygon twice. /// /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL. /// @param seed Any value. - /// @param pointOut Receives the point. + /// @param filter The areas usable, or NULL. + /// @param pointOut Receives the point. Zeroed on any failure. /// @return `mnav_success`; `mnav_errorInvalid` for a NULL argument or a /// filter not built from mnavDefaultQueryFilter or with a NaN cost; /// `mnav_errorLimit` for a filter cost past MNAV_MIN_AREA_COST or @@ -1089,10 +1172,10 @@ extern "C" /// @par Thread safety /// Safe from any thread. Any number of threads may read the navmesh /// at once while no commit runs on it. - MNAV_NODISCARD MNAV_API mnavResult mnavFindRandomPoint(const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - uint64_t seed, - mnavRandomPoint* pointOut); + MNAV_NODISCARD MNAV_API mnavResult mnavNavmesh_FindRandomPoint(const mnavNavmesh* navmesh, + uint64_t seed, + const mnavQueryFilter* filter, + mnavRandomPoint* pointOut); /// Picks a point reachable from a center: a polygon the walls search /// reaches within the radius, by ground area, then a point uniformly @@ -1100,13 +1183,15 @@ extern "C" /// /// @param query The context; its last search ends. /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL. /// @param polygon The polygon the center lies in. /// @param center The center. - /// @param radius How far to search, in meters, at least 0 and finite. + /// @param radius How far to search, in meters, at least 0 and at most + /// MNAV_MAX_QUERY_DISTANCE. /// @param seed Any value. - /// @param pointOut Receives the point. - /// @return As mnavFindWallDistance; also `mnav_errorLimit` when a polygon + /// @param filter The areas usable, or NULL. + /// @param pointOut Receives the point. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. + /// @return As mnavQuery_FindWallDistance; also `mnav_errorLimit` when a polygon /// clipped to the circle, its 32-gon, has more than MNAV_MAX_CLIP_CORNERS /// corners, which rounding alone can cause. Also `mnav_errorBusy` when /// called back in from host code a call using the context runs (a link @@ -1114,10 +1199,9 @@ extern "C" /// misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult - mnavFindRandomPointAround(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId polygon, mnavPos3 center, - double radius, uint64_t seed, mnavRandomPoint* pointOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_FindRandomPointAround( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId polygon, mnavPos3 center, + double radius, uint64_t seed, const mnavQueryFilter* filter, mnavRandomPoint* pointOut); /// Tells whether the end can be reached from the start: the path search /// with its limits, without a path made. mnav_pathFound when it can, @@ -1126,26 +1210,24 @@ extern "C" /// /// @param query The context; its last search ends. /// @param navmesh The navmesh. - /// @param filter The areas usable and their costs, or NULL. /// @param startPolygon The polygon the start lies in. /// @param start The start point. /// @param endPolygon The polygon the end lies in. /// @param end The end point. - /// @param endOut Receives how the search ended. - /// @return As mnavFindPath. Also `mnav_errorBusy` when called back in from + /// @param filter The areas usable and their costs, or NULL. + /// @param endOut Receives how the search ended. Zeroed on any failure + /// but mnav_errorBusy, which leaves it as it was. + /// @return As mnavQuery_FindPath. Also `mnav_errorBusy` when called back in from /// host code a call using the context runs (a link generation's clearance /// test, a hierarchy's allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. - MNAV_NODISCARD MNAV_API mnavResult mnavCheckReachable(mnavQuery* query, - const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPolygonId startPolygon, - mnavPos3 start, mnavPolygonId endPolygon, - mnavPos3 end, mnavPathEnd* endOut); + MNAV_NODISCARD MNAV_API mnavResult mnavQuery_CheckReachable( + mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, const mnavQueryFilter* filter, mnavPathEnd* endOut); // How a raycast ended. - typedef uint8_t mnavRayEnd; + typedef int32_t mnavRayEnd; enum { @@ -1168,8 +1250,7 @@ extern "C" double t; // At a wall, the wall's normal on the ground, pointing back to // where the ray came from; 0 otherwise. - double normalX; - double normalZ; + mnavVec3 normal; // The polygons crossed, from the start polygon to the one where the // ray ended, in the context's memory until its next query. const mnavPolygonId* polygons; @@ -1188,29 +1269,31 @@ extern "C" /// /// @param query The context; its memory holds the polygons. /// @param navmesh The navmesh. - /// @param filter The areas usable, or NULL for every walkable one. /// @param startPolygon The polygon the start point lies on, as - /// mnavFindNearest gives it. + /// mnavNavmesh_FindNearest gives it. /// @param start The start point. /// @param end The end point; only its ground position counts. - /// @param rayOut Receives the result. + /// @param filter The areas usable, or NULL for every walkable one. + /// @param rayOut Receives the result. Zeroed on any failure but + /// mnav_errorBusy, which leaves it as it was. /// @return `mnav_success` whenever the ray was cast, however it ended; /// `mnav_errorInvalid` for a NULL argument, a point that is not finite, a /// polygon id that never existed, or a filter not built from - /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a - /// filter cost past MNAV_MIN_AREA_COST or MNAV_MAX_AREA_COST, an infinity - /// included; `mnav_errorStale` for a polygon id whose tile has been - /// replaced or removed. Also `mnav_errorBusy` when called back in from host - /// code a call using the context runs (a link generation's clearance test, - /// a hierarchy's allocator as it builds), one misuse counted. + /// mnavDefaultQueryFilter or with a NaN cost; `mnav_errorLimit` for a point + /// more than MNAV_MAX_QUERY_DISTANCE from the navmesh's origin on an axis + /// (the end's x and z), or a filter cost past MNAV_MIN_AREA_COST or + /// MNAV_MAX_AREA_COST, an infinity included; `mnav_errorStale` for a + /// polygon id whose tile has been replaced or removed. Also + /// `mnav_errorBusy` when called back in from host code a call using the + /// context runs (a link generation's clearance test, a hierarchy's + /// allocator as it builds), one misuse counted. /// @par Thread safety /// Safe from any thread; the context is used by one thread at a time. /// Any number of contexts may cast on one navmesh at once between /// commits. - MNAV_NODISCARD MNAV_API mnavResult mnavRaycast(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, - mnavPolygonId startPolygon, mnavPos3 start, - mnavPos3 end, mnavRay* rayOut); + MNAV_NODISCARD MNAV_API mnavResult + mnavQuery_Raycast(mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, + mnavPos3 start, mnavPos3 end, const mnavQueryFilter* filter, mnavRay* rayOut); #ifdef __cplusplus } diff --git a/third_party/maul-nav/src/avoidance.c b/third_party/maul-nav/src/avoidance.c index a13a41104..4edbf13d4 100644 --- a/third_party/maul-nav/src/avoidance.c +++ b/third_party/maul-nav/src/avoidance.c @@ -203,7 +203,7 @@ mnavResult mnavCreateAvoidance(const mnavAvoidanceDef* def, mnavAvoidance** avoi return result; } -uint64_t mnavGetAvoidanceMisuse(const mnavAvoidance* avoidance) +uint64_t mnavAvoidance_GetMisuse(const mnavAvoidance* avoidance) { return avoidance != nullptr ? mnavHostMisuse(&avoidance->host) : 0; } @@ -258,10 +258,12 @@ static mnavResult CheckAgent(const mnavAgent* agent) } mnavResult result = mnavAvoidJoin(mnavAvoidCoordinate(agent->position.x), mnavAvoidCoordinate(agent->position.y)); - result = mnavAvoidJoin(result, mnavAvoidJoin(mnavAvoidSpeed(agent->velocity.x), - mnavAvoidSpeed(agent->velocity.y))); - result = mnavAvoidJoin(result, mnavAvoidJoin(mnavAvoidSpeed(agent->preferred.x), - mnavAvoidSpeed(agent->preferred.y))); + mnavPos2 velocity = mnavWide2(agent->velocity); + mnavPos2 preferred = mnavWide2(agent->preferred); + result = mnavAvoidJoin(result, + mnavAvoidJoin(mnavAvoidSpeed(velocity.x), mnavAvoidSpeed(velocity.y))); + result = mnavAvoidJoin(result, + mnavAvoidJoin(mnavAvoidSpeed(preferred.x), mnavAvoidSpeed(preferred.y))); return mnavAvoidJoin( result, mnavAvoidJoin(mnavAvoidRadius(agent->radius), mnavAvoidSpeed(agent->maxSpeed))); } @@ -306,20 +308,25 @@ static mnavPos2 Solve(mnavAvoidance* a, const mnavAgent* agents, double step, in double share = (other->ignores & self->layers) != 0 ? 1.0 : other->priority / (self->priority + other->priority); - a->lines[count++] = mnavPairLine(self->position, self->velocity, other->position, - other->velocity, self->radius + other->radius, share, - a->def.timeHorizon, step, self->id < other->id); + a->lines[count++] = mnavPairLine(self->position, mnavWide2(self->velocity), other->position, + mnavWide2(other->velocity), self->radius + other->radius, + share, a->def.timeHorizon, step, self->id < other->id); } + mnavPos2 preferred = mnavWide2(self->preferred); mnavPos2 velocity = {0.0, 0.0}; int32_t failed = - mnavLinearProgram2(a->lines, count, self->maxSpeed, self->preferred, false, &velocity); - if (failed == count && (velocity.x != self->preferred.x || velocity.y != self->preferred.y)) + mnavLinearProgram2(a->lines, count, self->maxSpeed, preferred, false, &velocity); + // The velocity with no lines: the preferred one within the speed + // circle. Only lines hold an agent back; its own speed does not. + mnavPos2 free = {0.0, 0.0}; + (void)mnavLinearProgram2(a->lines, 0, self->maxSpeed, preferred, false, &free); + if (failed == count && (velocity.x != free.x || velocity.y != free.y)) { // Held back: aim a little right of the preferred velocity, so that // agents meeting in perfect symmetry pass on their right rather // than stop face to face. - mnavPos2 biased = {self->preferred.x + MNAV_KEEP_RIGHT * self->preferred.y, - self->preferred.y - MNAV_KEEP_RIGHT * self->preferred.x}; + mnavPos2 biased = {preferred.x + MNAV_KEEP_RIGHT * preferred.y, + preferred.y - MNAV_KEEP_RIGHT * preferred.x}; failed = mnavLinearProgram2(a->lines, count, self->maxSpeed, biased, false, &velocity); } if (failed < count) @@ -328,25 +335,18 @@ static mnavPos2 Solve(mnavAvoidance* a, const mnavAgent* agents, double step, in } // The programs stay within the speed circle up to rounding, which // lines meeting at a narrow angle make larger; the maximum speed is - // kept exactly. - double speedSq = mnavDot2(velocity, velocity); - if (speedSq > self->maxSpeed * self->maxSpeed) - { - velocity = mnavScale2(velocity, self->maxSpeed / sqrt(speedSq)); - } - // Within the speed circle, but for the scaling's last bits: relative, - // and a few units of the least subnormal where the squares are - // subnormal and round absolutely. + // kept exactly. The scaling rounds too, so a velocity it leaves a unit + // outside steps toward zero until it is within. + velocity = mnavKeepSpeed2(velocity, self->maxSpeed); MNAV_ASSERT(isfinite(velocity.x) && isfinite(velocity.y) && - mnavDot2(velocity, velocity) <= - self->maxSpeed * self->maxSpeed * (1.0 + 0x1p-40) + 0x1p-1072); + mnavWithinSpeed2(velocity, self->maxSpeed)); return velocity; } static MNAV_NOINLINE mnavResult AvoidWork(mnavAvoidance* avoidance, const mnavAgent* agents, int32_t agentCount, const mnavObstacle* obstacles, int32_t obstacleCount, double step, - mnavPos2* velocitiesOut) + mnavVec2* velocitiesOut) { if (avoidance != nullptr && mnavHostCalledBack(&avoidance->host)) { @@ -384,7 +384,7 @@ static MNAV_NOINLINE mnavResult AvoidWork(mnavAvoidance* avoidance, const mnavAg mnavSortCrowd(&avoidance->crowd, agentCount); for (int32_t i = 0; i < agentCount; ++i) { - velocitiesOut[i] = Solve(avoidance, agents, step, i); + velocitiesOut[i] = mnavNarrow2(Solve(avoidance, agents, step, i), agents[i].maxSpeed); } mnavEndCrowd(&avoidance->crowd); return mnav_success; @@ -393,16 +393,18 @@ static MNAV_NOINLINE mnavResult AvoidWork(mnavAvoidance* avoidance, const mnavAg static MNAV_NOINLINE mnavResult AvoidCounted(mnavAvoidance* avoidance, const mnavAgent* agents, int32_t agentCount, const mnavObstacle* obstacles, int32_t obstacleCount, double step, - mnavPos2* velocitiesOut) + mnavVec2* velocitiesOut) { - return mnavHostCountInvalid( + + mnavResult result = mnavHostCountInvalid( MNAV_HOST(avoidance), AvoidWork(avoidance, agents, agentCount, obstacles, obstacleCount, step, velocitiesOut)); + return mnavAvoidClear(result, avoidance, agentCount, velocitiesOut, sizeof(*velocitiesOut)); } -mnavResult mnavAvoid(mnavAvoidance* avoidance, const mnavAgent* agents, int32_t agentCount, - const mnavObstacle* obstacles, int32_t obstacleCount, double step, - mnavPos2* velocitiesOut) +mnavResult mnavAvoidance_Avoid(mnavAvoidance* avoidance, const mnavAgent* agents, + int32_t agentCount, const mnavObstacle* obstacles, + int32_t obstacleCount, double step, mnavVec2* velocitiesOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -484,8 +486,8 @@ static MNAV_NOINLINE mnavResult DebugAvoidanceCounted(mnavAvoidance* avoidance, DebugAvoidanceWork(avoidance, agents, agentCount, height, buffer)); } -mnavResult mnavDebugAvoidance(mnavAvoidance* avoidance, const mnavAgent* agents, int32_t agentCount, - double height, mnavDebugBuffer* buffer) +mnavResult mnavAvoidance_Debug(mnavAvoidance* avoidance, const mnavAgent* agents, + int32_t agentCount, double height, mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = DebugAvoidanceCounted(avoidance, agents, agentCount, height, buffer); diff --git a/third_party/maul-nav/src/avoidance.h b/third_party/maul-nav/src/avoidance.h index 2a634f8eb..378c489ca 100644 --- a/third_party/maul-nav/src/avoidance.h +++ b/third_party/maul-nav/src/avoidance.h @@ -10,15 +10,19 @@ #include "allocator.h" #include "crowd.h" #include "host_code.h" +#include "invariant.h" #include "obstacle.h" #include "orca.h" #include "orca3.h" +#include "outputs.h" #include "maul-nav/avoidance.h" #include #include +#include #include +#include // How far right of its preferred velocity a held-back agent aims, as a // fraction of its speed. @@ -47,7 +51,115 @@ struct mnavAvoidance // platform has the same: the outline debug drawing gives an agent. extern const double mnavOutline[16][2]; -static inline bool mnavAvoidPositive(double v) +// A value one unit nearer zero; a zero stays. +static inline double mnavTowardZero(double v) +{ + uint64_t bits; + memcpy(&bits, &v, sizeof bits); + bits -= (bits << 1) != 0; + memcpy(&v, &bits, sizeof v); + return v; +} + +// The power of two a velocity and its maximum speed are scaled by before +// their squares are taken: 2^600 when the largest of them is below +// 2^-500, so that the squares stay far from the subnormals, which round +// absolutely and would blur both the scaling back and a step toward zero. +// The scaling is then exact and cannot overflow. +static inline double mnavSpeedScale(double largest) +{ + return largest < 0x1p-500 ? 0x1p600 : 1.0; +} + +static inline double mnavLarger(double a, double b) +{ + return a > b ? a : b; +} + +// Whether a velocity no faster than about 2^-500 m/s, or one of any speed +// at a maximum of 2^-500 or more, is within the speed circle: its squared +// speed, scaled by mnavSpeedScale, is no more than the squared maximum. +MNAV_NODISCARD static inline bool mnavWithinSpeed2(mnavPos2 v, double maxSpeed) +{ + double k = mnavSpeedScale(maxSpeed); + mnavPos2 s = mnavScale2(v, k); + return mnavDot2(s, s) <= (maxSpeed * k) * (maxSpeed * k); +} + +// The same for the speed sphere. +MNAV_NODISCARD static inline bool mnavWithinSpeed3(mnavPos3 v, double maxSpeed) +{ + double k = mnavSpeedScale(maxSpeed); + mnavPos3 s = mnavScale3(v, k); + return mnavDot3(s, s) <= (maxSpeed * k) * (maxSpeed * k); +} + +// Whether a velocity just scaled back onto the speed circle is within a +// few units of it, which a few steps close: 2^-40 of the squared maximum, +// and two units of the least subnormal on the speed, scaled, where the +// components round absolutely. A velocity further out would take up to +// 2^52 steps. +MNAV_NODISCARD static inline bool mnavNearSpeed2(mnavPos2 v, double maxSpeed) +{ + double k = mnavSpeedScale(maxSpeed); + mnavPos2 s = mnavScale2(v, k); + double bound = (maxSpeed + 0x1p-1073) * k; + return mnavDot2(s, s) <= bound * bound * (1.0 + 0x1p-40); +} + +// The same for the speed sphere. +MNAV_NODISCARD static inline bool mnavNearSpeed3(mnavPos3 v, double maxSpeed) +{ + double k = mnavSpeedScale(maxSpeed); + mnavPos3 s = mnavScale3(v, k); + double bound = (maxSpeed + 0x1p-1073) * k; + return mnavDot3(s, s) <= bound * bound * (1.0 + 0x1p-40); +} + +// A velocity kept within the speed circle. One past it is scaled back +// onto it, and since that rounds, each component then steps one unit +// toward zero until it is within. A step takes at least a unit of the +// scaling's rounding off a nonzero component, and zero fits, so a few +// steps end it: three at most over 2.2e7 random velocities and maxima. +static inline mnavPos2 mnavKeepSpeed2(mnavPos2 v, double maxSpeed) +{ + double k = mnavSpeedScale(mnavLarger(maxSpeed, mnavLarger(fabs(v.x), fabs(v.y)))); + mnavPos2 s = mnavScale2(v, k); + double speedSq = mnavDot2(s, s); + if (speedSq <= (maxSpeed * k) * (maxSpeed * k)) + { + return v; + } + v = mnavScale2(v, maxSpeed * k / sqrt(speedSq)); + MNAV_ASSERT(mnavNearSpeed2(v, maxSpeed)); + while (!mnavWithinSpeed2(v, maxSpeed)) + { + v = (mnavPos2){mnavTowardZero(v.x), mnavTowardZero(v.y)}; + } + return v; +} + +// The same within the speed sphere. +static inline mnavPos3 mnavKeepSpeed3(mnavPos3 v, double maxSpeed) +{ + double k = mnavSpeedScale( + mnavLarger(maxSpeed, mnavLarger(fabs(v.x), mnavLarger(fabs(v.y), fabs(v.z))))); + mnavPos3 s = mnavScale3(v, k); + double speedSq = mnavDot3(s, s); + if (speedSq <= (maxSpeed * k) * (maxSpeed * k)) + { + return v; + } + v = mnavScale3(v, maxSpeed * k / sqrt(speedSq)); + MNAV_ASSERT(mnavNearSpeed3(v, maxSpeed)); + while (!mnavWithinSpeed3(v, maxSpeed)) + { + v = (mnavPos3){mnavTowardZero(v.x), mnavTowardZero(v.y), mnavTowardZero(v.z)}; + } + return v; +} + +MNAV_NODISCARD static inline bool mnavAvoidPositive(double v) { return isfinite(v) && v > 0.0; } @@ -101,6 +213,64 @@ static inline mnavResult mnavAvoidRadius(double v) return v > 0.0 ? mnavAvoidBound(v, MNAV_MAX_AVOIDANCE_RADIUS) : mnav_errorInvalid; } +// The binary32 next to f toward zero: one less in the magnitude's bits, +// the sign kept. f is finite and not zero. +static inline float mnavStepTowardZero(float f) +{ + uint32_t bits = 0; + memcpy(&bits, &f, sizeof(bits)); + bits -= 1u; + memcpy(&f, &bits, sizeof(f)); + return f; +} + +// A velocity the set gives back, rounded once to binary32, to nearest. v +// is within the maximum speed; when that rounding would put it past, each +// component it grew steps back toward zero, to at most its own magnitude in +// v, so the speed check that held for v holds for the result too. +static inline mnavVec2 mnavNarrow2(mnavPos2 v, double maxSpeed) +{ + mnavVec2 f = {(float)v.x, (float)v.y}; + mnavPos2 wide = mnavWide2(f); + if (!mnavWithinSpeed2(wide, maxSpeed)) + { + f.x = fabs(wide.x) > fabs(v.x) ? mnavStepTowardZero(f.x) : f.x; + f.y = fabs(wide.y) > fabs(v.y) ? mnavStepTowardZero(f.y) : f.y; + } + MNAV_ASSERT(mnavWithinSpeed2(mnavWide2(f), maxSpeed)); + return f; +} + +static inline mnavVec3 mnavNarrow3(mnavPos3 v, double maxSpeed) +{ + mnavVec3 f = {(float)v.x, (float)v.y, (float)v.z}; + mnavPos3 wide = mnavWide3(f); + if (!mnavWithinSpeed3(wide, maxSpeed)) + { + f.x = fabs(wide.x) > fabs(v.x) ? mnavStepTowardZero(f.x) : f.x; + f.y = fabs(wide.y) > fabs(v.y) ? mnavStepTowardZero(f.y) : f.y; + f.z = fabs(wide.z) > fabs(v.z) ? mnavStepTowardZero(f.z) : f.z; + } + MNAV_ASSERT(mnavWithinSpeed3(mnavWide3(f), maxSpeed)); + return f; +} + +// Clears the velocities of a call that failed, when the set's agent limit +// holds them; a count past it, or no set, writes nothing, since the count +// is all that bounds the caller's array. A call refused as busy, which only +// the set's own allocator can make, leaves them: they may be the ones the +// call in progress fills. +static inline mnavResult mnavAvoidClear(mnavResult result, const mnavAvoidance* avoidance, + int32_t agentCount, void* velocities, size_t size) +{ + if (result != mnav_success && result != mnav_errorBusy && avoidance != nullptr && + agentCount > 0 && agentCount <= avoidance->def.limits.agents) + { + mnavClearOutput(velocities, (size_t)agentCount * size); + } + return result; +} + // Fills the set's obstacle grid with spheres, by their bounds on the // ground (X and Z), its fastest speed their fastest in space; count is // within the set's obstacle points. diff --git a/third_party/maul-nav/src/avoidance3.c b/third_party/maul-nav/src/avoidance3.c index 774131d77..0a28f6d93 100644 --- a/third_party/maul-nav/src/avoidance3.c +++ b/third_party/maul-nav/src/avoidance3.c @@ -40,8 +40,9 @@ static mnavResult CheckAgent(const mnavAgent3D* agent) { return mnav_errorInvalid; } - mnavResult result = mnavAvoidJoin(CheckPoint(agent->position), CheckVector(agent->velocity)); - result = mnavAvoidJoin(result, CheckVector(agent->preferred)); + mnavResult result = + mnavAvoidJoin(CheckPoint(agent->position), CheckVector(mnavWide3(agent->velocity))); + result = mnavAvoidJoin(result, CheckVector(mnavWide3(agent->preferred))); return mnavAvoidJoin( result, mnavAvoidJoin(mnavAvoidRadius(agent->radius), mnavAvoidSpeed(agent->maxSpeed))); } @@ -49,14 +50,15 @@ static mnavResult CheckAgent(const mnavAgent3D* agent) static mnavResult CheckSphere(const mnavSphere* sphere) { mnavResult result = mnavAvoidJoin(CheckPoint(sphere->center), mnavAvoidRadius(sphere->radius)); - return mnavAvoidJoin(result, CheckVector(sphere->velocity)); + return mnavAvoidJoin(result, CheckVector(mnavWide3(sphere->velocity))); } // A sphere's reach from an agent: the squared gap between them, or -1 // when the agent cannot meet it within the horizon. static double SphereReach(const mnavAgent3D* agent, const mnavSphere* sphere, double horizon) { - double speed = sqrt(mnavDot3(sphere->velocity, sphere->velocity)); + mnavPos3 velocity = mnavWide3(sphere->velocity); + double speed = sqrt(mnavDot3(velocity, velocity)); double range = horizon * (agent->maxSpeed + speed) + agent->radius; mnavPos3 rel = mnavSub3(sphere->center, agent->position); double gap = sqrt(mnavDot3(rel, rel)) - sphere->radius; @@ -72,14 +74,15 @@ void mnavBuildSphereGrid(mnavAvoidance* a, const mnavSphere* spheres, int32_t co const mnavSphere* sphere = &spheres[s]; a->vertices[s] = (mnavObstacleVertex){ .point = {sphere->center.x, sphere->center.z}, - .velocity = {sphere->velocity.x, sphere->velocity.z}, + .velocity = {(double)sphere->velocity.x, (double)sphere->velocity.z}, .radius = sphere->radius, .id = sphere->id, .index = s, .next = s, .previous = s, }; - double speed = sqrt(mnavDot3(sphere->velocity, sphere->velocity)); + mnavPos3 velocity = mnavWide3(sphere->velocity); + double speed = sqrt(mnavDot3(velocity, velocity)); fastest = speed > fastest ? speed : fastest; } mnavBuildObstacleGrid(&a->grid, a->vertices, count, a->def.neighborDistance); @@ -135,8 +138,9 @@ static mnavPlane SpherePlane(const mnavAgent3D* agent, const mnavSphere* sphere, double distanceSq = mnavDot3(rel, rel); if (distanceSq > combined * combined) { - return mnavPairPlane(agent->position, agent->velocity, sphere->center, sphere->velocity, - combined, 1.0, horizon, step, agent->id < sphere->id); + return mnavPairPlane(agent->position, mnavWide3(agent->velocity), sphere->center, + mnavWide3(sphere->velocity), combined, 1.0, horizon, step, + agent->id < sphere->id); } // On the center itself no way is out; a fixed one, forward, keeps it // deterministic. @@ -145,7 +149,8 @@ static mnavPlane SpherePlane(const mnavAgent3D* agent, const mnavSphere* sphere, { away = (mnavPos3){0.0, 0.0, -1.0}; } - double leave = mnavDot3(sphere->velocity, away) + (combined - sqrt(distanceSq)) / step; + double leave = + mnavDot3(mnavWide3(sphere->velocity), away) + (combined - sqrt(distanceSq)) / step; double offset = leave < agent->maxSpeed ? leave : agent->maxSpeed; return (mnavPlane){mnavScale3(away, offset), away}; } @@ -188,20 +193,24 @@ static mnavPos3 Solve(mnavAvoidance* a, const mnavAgent3D* agents, const mnavSph double share = (other->ignores & self->layers) != 0 ? 1.0 : other->priority / (self->priority + other->priority); - a->planes[count++] = mnavPairPlane(self->position, self->velocity, other->position, - other->velocity, self->radius + other->radius, share, - a->def.timeHorizon, step, self->id < other->id); + a->planes[count++] = mnavPairPlane( + self->position, mnavWide3(self->velocity), other->position, mnavWide3(other->velocity), + self->radius + other->radius, share, a->def.timeHorizon, step, self->id < other->id); } + mnavPos3 preferred = mnavWide3(self->preferred); mnavPos3 velocity = {0.0, 0.0, 0.0}; int32_t failed = - mnavPlaneProgram3(a->planes, count, self->maxSpeed, self->preferred, false, &velocity); - if (failed == count && (velocity.x != self->preferred.x || velocity.y != self->preferred.y || - velocity.z != self->preferred.z)) + mnavPlaneProgram3(a->planes, count, self->maxSpeed, preferred, false, &velocity); + // The velocity with no planes: the preferred one within the speed + // sphere. Only planes hold an agent back; its own speed does not. + mnavPos3 free = {0.0, 0.0, 0.0}; + (void)mnavPlaneProgram3(a->planes, 0, self->maxSpeed, preferred, false, &free); + if (failed == count && (velocity.x != free.x || velocity.y != free.y || velocity.z != free.z)) { // Held back: aim a little right of the preferred velocity, so that // agents meeting in perfect symmetry pass rather than stop. - failed = mnavPlaneProgram3(a->planes, count, self->maxSpeed, KeepRight(self->preferred), - false, &velocity); + failed = mnavPlaneProgram3(a->planes, count, self->maxSpeed, KeepRight(preferred), false, + &velocity); } if (failed < count) { @@ -209,17 +218,9 @@ static mnavPos3 Solve(mnavAvoidance* a, const mnavAgent3D* agents, const mnavSph &velocity); } // The maximum speed is kept exactly, as on the ground. - double speedSq = mnavDot3(velocity, velocity); - if (speedSq > self->maxSpeed * self->maxSpeed) - { - velocity = mnavScale3(velocity, self->maxSpeed / sqrt(speedSq)); - } - // Within the speed sphere, but for the scaling's last bits: relative, - // and a few units of the least subnormal where the squares are - // subnormal and round absolutely. + velocity = mnavKeepSpeed3(velocity, self->maxSpeed); MNAV_ASSERT(isfinite(velocity.x) && isfinite(velocity.y) && isfinite(velocity.z) && - mnavDot3(velocity, velocity) <= - self->maxSpeed * self->maxSpeed * (1.0 + 0x1p-40) + 0x1p-1072); + mnavWithinSpeed3(velocity, self->maxSpeed)); return velocity; } @@ -261,7 +262,7 @@ static mnavResult Prepare(mnavAvoidance* avoidance, const mnavAgent3D* agents, i static MNAV_NOINLINE mnavResult Avoid3DWork(mnavAvoidance* avoidance, const mnavAgent3D* agents, int32_t agentCount, const mnavSphere* spheres, int32_t sphereCount, double step, - mnavPos3* velocitiesOut) + mnavVec3* velocitiesOut) { if (avoidance != nullptr && mnavHostCalledBack(&avoidance->host)) { @@ -286,7 +287,8 @@ static MNAV_NOINLINE mnavResult Avoid3DWork(mnavAvoidance* avoidance, const mnav mnavBuildSphereGrid(avoidance, spheres, sphereCount); for (int32_t i = 0; i < agentCount; ++i) { - velocitiesOut[i] = Solve(avoidance, agents, spheres, sphereCount, step, i); + velocitiesOut[i] = mnavNarrow3(Solve(avoidance, agents, spheres, sphereCount, step, i), + agents[i].maxSpeed); } return mnav_success; } @@ -294,16 +296,18 @@ static MNAV_NOINLINE mnavResult Avoid3DWork(mnavAvoidance* avoidance, const mnav static MNAV_NOINLINE mnavResult Avoid3DCounted(mnavAvoidance* avoidance, const mnavAgent3D* agents, int32_t agentCount, const mnavSphere* spheres, int32_t sphereCount, double step, - mnavPos3* velocitiesOut) + mnavVec3* velocitiesOut) { - return mnavHostCountInvalid( + + mnavResult result = mnavHostCountInvalid( MNAV_HOST(avoidance), Avoid3DWork(avoidance, agents, agentCount, spheres, sphereCount, step, velocitiesOut)); + return mnavAvoidClear(result, avoidance, agentCount, velocitiesOut, sizeof(*velocitiesOut)); } -mnavResult mnavAvoid3D(mnavAvoidance* avoidance, const mnavAgent3D* agents, int32_t agentCount, - const mnavSphere* spheres, int32_t sphereCount, double step, - mnavPos3* velocitiesOut) +mnavResult mnavAvoidance_Avoid3D(mnavAvoidance* avoidance, const mnavAgent3D* agents, + int32_t agentCount, const mnavSphere* spheres, int32_t sphereCount, + double step, mnavVec3* velocitiesOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -377,9 +381,9 @@ DebugAvoidance3DCounted(mnavAvoidance* avoidance, const mnavAgent3D* agents, int DebugAvoidance3DWork(avoidance, agents, agentCount, spheres, sphereCount, buffer)); } -mnavResult mnavDebugAvoidance3D(mnavAvoidance* avoidance, const mnavAgent3D* agents, - int32_t agentCount, const mnavSphere* spheres, int32_t sphereCount, - mnavDebugBuffer* buffer) +mnavResult mnavAvoidance_Debug3D(mnavAvoidance* avoidance, const mnavAgent3D* agents, + int32_t agentCount, const mnavSphere* spheres, int32_t sphereCount, + mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = diff --git a/third_party/maul-nav/src/bake_def.c b/third_party/maul-nav/src/bake_def.c index db8407264..311ef5c12 100644 --- a/third_party/maul-nav/src/bake_def.c +++ b/third_party/maul-nav/src/bake_def.c @@ -37,9 +37,9 @@ static mnavBakeDefResult Refuse(mnavResult result, mnavBakeSetting setting) return (mnavBakeDefResult){result, setting}; } -static const mnavBakeDefResult s_accept = {mnav_success, mnav_settingNone}; +static const mnavBakeDefResult s_accept = {mnav_success, mnav_bakeSettingNone}; -static bool InRange(float value, float low, float high) +MNAV_NODISCARD static bool InRange(float value, float low, float high) { // A NaN fails both comparisons. return value >= low && value <= high; @@ -77,14 +77,11 @@ static mnavBakeDefResult CheckLimits(const mnavBakeLimits* limits) int32_t max; mnavBakeSetting setting; } counts[] = { - {limits->inputTriangles, MNAV_MAX_INPUT_TRIANGLES, mnav_settingInputTriangles}, - {limits->tileTriangles, MNAV_MAX_INPUT_TRIANGLES, mnav_settingTileTriangles}, - {limits->tileSpans, MNAV_MAX_TILE_SPANS, mnav_settingTileSpans}, - {limits->tilePolygons, MNAV_MAX_TILE_POLYGONS, mnav_settingTilePolygons}, - {limits->tileVertices, MNAV_MAX_TILE_VERTICES, mnav_settingTileVertices}, - {limits->tileLinks, MNAV_MAX_TILE_LINKS, mnav_settingTileLinks}, - {limits->tiles, MNAV_MAX_TILES, mnav_settingTiles}, - {limits->links, MNAV_MAX_LINKS, mnav_settingLinks}, + {limits->inputTriangles, MNAV_MAX_INPUT_TRIANGLES, mnav_bakeSettingInputTriangles}, + {limits->tileTriangles, MNAV_MAX_INPUT_TRIANGLES, mnav_bakeSettingTileTriangles}, + {limits->tileSpans, MNAV_MAX_TILE_SPANS, mnav_bakeSettingTileSpans}, + {limits->tilePolygons, MNAV_MAX_TILE_POLYGONS, mnav_bakeSettingTilePolygons}, + {limits->tileVertices, MNAV_MAX_TILE_VERTICES, mnav_bakeSettingTileVertices}, }; for (size_t i = 0; i < sizeof(counts) / sizeof(counts[0]); ++i) { @@ -99,7 +96,7 @@ static mnavBakeDefResult CheckLimits(const mnavBakeLimits* limits) } if (limits->memoryBytes == 0) { - return Refuse(mnav_errorInvalid, mnav_settingMemoryBytes); + return Refuse(mnav_errorInvalid, mnav_bakeSettingMemoryBytes); } return s_accept; } @@ -108,18 +105,18 @@ static mnavBakeDefResult CheckGrid(const mnavBakeDef* def) { if ((def->allocator.alloc == nullptr) != (def->allocator.free == nullptr)) { - return Refuse(mnav_errorInvalid, mnav_settingAllocator); + return Refuse(mnav_errorInvalid, mnav_bakeSettingAllocator); } if (!isfinite(def->origin.x) || !isfinite(def->origin.y) || !isfinite(def->origin.z)) { - return Refuse(mnav_errorInvalid, mnav_settingOrigin); + return Refuse(mnav_errorInvalid, mnav_bakeSettingOrigin); } mnavBakeDefResult r = - Bounded(def->cellSize, MNAV_MIN_CELL_SIZE, MNAV_MAX_CELL_SIZE, mnav_settingCellSize); + Bounded(def->cellSize, MNAV_MIN_CELL_SIZE, MNAV_MAX_CELL_SIZE, mnav_bakeSettingCellSize); if (r.result == mnav_success) { r = Bounded(def->cellHeight, MNAV_MIN_CELL_SIZE, MNAV_MAX_CELL_SIZE, - mnav_settingCellHeight); + mnav_bakeSettingCellHeight); } if (r.result != mnav_success) { @@ -127,42 +124,38 @@ static mnavBakeDefResult CheckGrid(const mnavBakeDef* def) } if (def->tileCells < MNAV_MIN_TILE_CELLS || def->tileCells > MNAV_MAX_TILE_CELLS) { - return Refuse(mnav_errorLimit, mnav_settingTileCells); - } - if (def->tier > mnav_tierDynamic) - { - return Refuse(mnav_errorInvalid, mnav_settingTier); + return Refuse(mnav_errorLimit, mnav_bakeSettingTileCells); } return s_accept; } // Converts the agent; the grid settings are already valid. Bounds the def -// sets for itself (the radius within a tile) and the unnamed 2^30 cells are -// mnav_errorInvalid; MNAV_MAX_ ones are mnav_errorLimit. +// sets for itself (the radius within a tile) are mnav_errorInvalid; +// MNAV_MAX_ ones are mnav_errorLimit. static mnavBakeDefResult ConvertAgent(const mnavBakeDef* def, mnavBakeCells* cells) { const mnavAgentProfile* agent = &def->agent; mnavBakeDefResult r = Cells(agent->radius, def->cellSize, true, def->tileCells, mnav_errorInvalid, false, - mnav_settingAgentRadius, &cells->agentRadius); + mnav_bakeSettingAgentRadius, &cells->agentRadius); if (r.result == mnav_success && cells->agentRadius + BORDER_MARGIN_CELLS > def->tileCells) { - r = Refuse(mnav_errorInvalid, mnav_settingAgentRadius); + r = Refuse(mnav_errorInvalid, mnav_bakeSettingAgentRadius); } if (r.result == mnav_success) { r = Cells(agent->height, def->cellHeight, true, MNAV_MAX_HEIGHT_CELLS, mnav_errorLimit, - true, mnav_settingAgentHeight, &cells->agentHeight); + true, mnav_bakeSettingAgentHeight, &cells->agentHeight); } if (r.result == mnav_success) { r = Cells(agent->stepHeight, def->cellHeight, false, MNAV_MAX_HEIGHT_CELLS, mnav_errorLimit, - false, mnav_settingAgentStepHeight, &cells->agentStep); + false, mnav_bakeSettingAgentStepHeight, &cells->agentStep); } if (r.result == mnav_success && !(agent->maxSlopeDegrees >= 0.0f && agent->maxSlopeDegrees < 90.0f)) { - r = Refuse(mnav_errorInvalid, mnav_settingAgentMaxSlope); + r = Refuse(mnav_errorInvalid, mnav_bakeSettingAgentMaxSlope); } if (r.result != mnav_success) { @@ -173,19 +166,30 @@ static mnavBakeDefResult ConvertAgent(const mnavBakeDef* def, mnavBakeCells* cel { cells->agentHeight = 1; } + cells->border = cells->agentRadius + BORDER_MARGIN_CELLS; + cells->cosMaxSlope = mnavCosDegrees(agent->maxSlopeDegrees); + cells->tileSize = (float)def->tileCells * def->cellSize; + return s_accept; +} + +// Converts the regions', walls' and detail's settings; the grid settings +// are already valid. The unnamed 2^30 cells are mnav_errorInvalid; +// MNAV_MAX_EDGE_ERROR is mnav_errorLimit. +static mnavBakeDefResult ConvertSurface(const mnavBakeDef* def, mnavBakeCells* cells) +{ // A cell's area rounds once; every platform rounds it the same. An // area larger than any tile is valid (it drops every island) up to // 2^30 cells, past which it can only be a mistake. float cellArea = def->cellSize * def->cellSize; - r = Cells(def->minRegionArea, cellArea, true, 1 << 30, mnav_errorInvalid, false, - mnav_settingMinRegionArea, &cells->minRegion); + mnavBakeDefResult r = Cells(def->minRegionArea, cellArea, true, 1 << 30, mnav_errorInvalid, + false, mnav_bakeSettingMinRegionArea, &cells->minRegion); if (r.result == mnav_success && !InRange(def->maxEdgeError, 0.0f, INFINITY)) { - r = Refuse(mnav_errorInvalid, mnav_settingMaxEdgeError); + r = Refuse(mnav_errorInvalid, mnav_bakeSettingMaxEdgeError); } - if (r.result == mnav_success && def->maxEdgeError > MNAV_MAX_CELL_SIZE * MNAV_MAX_TILE_CELLS) + if (r.result == mnav_success && def->maxEdgeError > MNAV_MAX_EDGE_ERROR) { - r = Refuse(mnav_errorLimit, mnav_settingMaxEdgeError); + r = Refuse(mnav_errorLimit, mnav_bakeSettingMaxEdgeError); } if (r.result != mnav_success) { @@ -193,31 +197,36 @@ static mnavBakeDefResult ConvertAgent(const mnavBakeDef* def, mnavBakeCells* cel } cells->edgeError = def->maxEdgeError / def->cellSize; r = Cells(def->maxEdgeLength, def->cellSize, false, 1 << 30, mnav_errorInvalid, false, - mnav_settingMaxEdgeLength, &cells->edgeLength); + mnav_bakeSettingMaxEdgeLength, &cells->edgeLength); // Sixteenths of a cell and of a cell height are exact: the division // only lowers the exponent. if (r.result == mnav_success) { r = Cells(def->detailSampleDistance, def->cellSize / 16.0f, false, 1 << 30, - mnav_errorInvalid, false, mnav_settingDetailSampleDistance, &cells->detailSample); + mnav_errorInvalid, false, mnav_bakeSettingDetailSampleDistance, + &cells->detailSample); } if (r.result == mnav_success) { r = Cells(def->detailMaxError, def->cellHeight / 16.0f, false, 1 << 30, mnav_errorInvalid, - false, mnav_settingDetailMaxError, &cells->detailError); + false, mnav_bakeSettingDetailMaxError, &cells->detailError); } if (r.result != mnav_success) { return r; } // Heights come one per cell; sampling closer than a cell adds nothing. - if (cells->detailSample > 0 && cells->detailSample < 16) + // Any distance above 0 samples, and any length above 0 limits walls, + // however few sixteenths or cells it rounds down to: 0 in cells means + // none. + if (def->detailSampleDistance > 0.0f && cells->detailSample < 16) { cells->detailSample = 16; } - cells->border = cells->agentRadius + BORDER_MARGIN_CELLS; - cells->cosMaxSlope = mnavCosDegrees(agent->maxSlopeDegrees); - cells->tileSize = (float)def->tileCells * def->cellSize; + if (def->maxEdgeLength > 0.0f && cells->edgeLength < 1) + { + cells->edgeLength = 1; + } return s_accept; } @@ -225,15 +234,15 @@ mnavBakeDefResult mnavCheckBakeDef(const mnavBakeDef* def, mnavBakeCells* cells) { if (def == nullptr) { - return Refuse(mnav_errorInvalid, mnav_settingNone); + return Refuse(mnav_errorInvalid, mnav_bakeSettingNone); } if (def->cookie != MNAV_BAKE_DEF_COOKIE) { - return Refuse(mnav_errorInvalid, mnav_settingCookie); + return Refuse(mnav_errorInvalid, mnav_bakeSettingCookie); } if (def->version != MNAV_ABI_VERSION) { - return Refuse(mnav_errorVersion, mnav_settingNone); + return Refuse(mnav_errorVersion, mnav_bakeSettingNone); } mnavBakeDefResult r = CheckGrid(def); if (r.result != mnav_success) @@ -243,6 +252,10 @@ mnavBakeDefResult mnavCheckBakeDef(const mnavBakeDef* def, mnavBakeCells* cells) mnavBakeCells converted = {0}; r = ConvertAgent(def, &converted); if (r.result == mnav_success) + { + r = ConvertSurface(def, &converted); + } + if (r.result == mnav_success) { r = CheckLimits(&def->limits); } diff --git a/third_party/maul-nav/src/bake_input.c b/third_party/maul-nav/src/bake_input.c index f5fb661be..2ea858275 100644 --- a/third_party/maul-nav/src/bake_input.c +++ b/third_party/maul-nav/src/bake_input.c @@ -1,13 +1,16 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 Sirac Ozmen // -// A 3D bake's whole input checked: its arrays, then each mesh, terrain -// and volume, and their size against the input limit. +// A bake's whole input checked: its arrays, then each mesh, terrain and +// volume, or each outline, and their size against the input limit. #include "bake_input.h" +#include "bake_def.h" +#include "fpenv.h" #include "input.h" #include "terrain.h" +#include "tile_index.h" #include "volume.h" #include "maul-nav/bake.h" @@ -22,39 +25,35 @@ bool mnavCheckInputArrays(const mnavBakeInput* input) input->volumeCount >= 0 && (input->volumeCount == 0 || input->volumes != nullptr); } -// Notes a refused element and passes its outcome on. -static mnavResult Refused(int32_t index, mnavInputResult input, int32_t* meshOut, +// Notes a refused input and passes its outcome on. +static mnavResult Refused(mnavInputResult checked, mnavInputKind kind, int32_t index, mnavInputResult* inputOut) { - *meshOut = index; - *inputOut = input; - return input.result; + *inputOut = mnavInputNamed(checked, kind, index); + return checked.result; } mnavResult mnavCheckSolidInput(const mnavBakeDef* def, const mnavBakeInput* input, - bool meshesChecked, int32_t* meshOut, mnavInputResult* inputOut) + bool meshesChecked, mnavInputResult* inputOut) { int64_t total = 0; for (int32_t m = 0; m < input->meshCount; ++m) { mnavInputResult checked = meshesChecked - ? (mnavInputResult){mnav_success, mnav_elementNone, -1} + ? mnavInputElementResult(mnav_success, mnav_elementNone, -1) : mnavCheckTriangleMesh(def, &input->meshes[m]); if (checked.result != mnav_success) { - return Refused(m, checked, meshOut, inputOut); + return Refused(checked, mnav_inputMesh, m, inputOut); } total += input->meshes[m].triangleCount; } - // An element's index fits in int32_t: every mesh, terrain and volume - // before it was read, at least 24 bytes each, and 2^31 of them would - // take 48 GB. for (int32_t i = 0; i < input->terrainCount; ++i) { mnavInputResult checked = mnavCheckTerrain(def, &input->terrains[i]); if (checked.result != mnav_success) { - return Refused(input->meshCount + i, checked, meshOut, inputOut); + return Refused(checked, mnav_inputTerrain, i, inputOut); } total += mnavTerrainTriangles(&input->terrains[i]); } @@ -63,9 +62,89 @@ mnavResult mnavCheckSolidInput(const mnavBakeDef* def, const mnavBakeInput* inpu mnavInputResult checked = mnavCheckVolume(def, &input->volumes[i]); if (checked.result != mnav_success) { - return Refused(input->meshCount + input->terrainCount + i, checked, meshOut, inputOut); + return Refused(checked, mnav_inputVolume, i, inputOut); } total += input->volumes[i].pointCount; } return total > def->limits.inputTriangles ? mnav_errorCapacity : mnav_success; } + +// The outcome of a whole input's check that refuses no one input. +static mnavInputResult Whole(mnavResult result) +{ + return mnavInputElementResult(result, mnav_elementNone, -1); +} + +static MNAV_NOINLINE mnavInputResult ValidateBakeInputWork(const mnavBakeDef* def, + const mnavBakeInput* input) +{ + if (def == nullptr || input == nullptr) + { + return Whole(mnav_errorInvalid); + } + mnavBakeCells cells = {0}; + mnavResult checked = mnavCheckBakeDef(def, &cells).result; + if (checked != mnav_success) + { + return Whole(checked); + } + if (!mnavCheckInputArrays(input) || + (input->index != nullptr && + !mnavTileIndexFits(input->index, def, &cells, input->meshes, input->meshCount))) + { + return Whole(mnav_errorInvalid); + } + mnavInputResult refused = Whole(mnav_success); + mnavResult result = mnavCheckSolidInput(def, input, false, &refused); + return refused.result != mnav_success ? refused : Whole(result); +} + +mnavInputResult mnavValidateBakeInput(const mnavBakeDef* def, const mnavBakeInput* input) +{ + uint64_t env = mnavFloatEnvEnter(); + mnavInputResult result = ValidateBakeInputWork(def, input); + mnavFloatEnvLeave(env); + return result; +} + +static MNAV_NOINLINE mnavInputResult ValidateBake2DInputWork(const mnavBakeDef* def, + const mnavBake2DInput* input) +{ + if (def == nullptr || input == nullptr) + { + return Whole(mnav_errorInvalid); + } + mnavBakeCells cells = {0}; + mnavResult checked = mnavCheckBakeDef(def, &cells).result; + if (checked != mnav_success) + { + return Whole(checked); + } + const mnavOutline* outlines = input->outlines; + int32_t outlineCount = input->outlineCount; + if (outlineCount < 0 || (outlineCount > 0 && outlines == nullptr) || + (input->index != nullptr && + !mnavTileIndexFits2D(input->index, def, &cells, outlines, outlineCount))) + { + return Whole(mnav_errorInvalid); + } + int64_t total = 0; + for (int32_t o = 0; o < outlineCount; ++o) + { + mnavInputResult refused = mnavCheckOutline(def, &outlines[o]); + if (refused.result != mnav_success) + { + return mnavInputNamed(refused, mnav_inputOutline, o); + } + total += outlines[o].pointCount; + } + return Whole(total > def->limits.inputTriangles ? mnav_errorCapacity : mnav_success); +} + +mnavInputResult mnavValidateBake2DInput(const mnavBakeDef* def, const mnavBake2DInput* input) +{ + uint64_t env = mnavFloatEnvEnter(); + mnavInputResult result = ValidateBake2DInputWork(def, input); + mnavFloatEnvLeave(env); + return result; +} diff --git a/third_party/maul-nav/src/bake_input.h b/third_party/maul-nav/src/bake_input.h index 8cc52fe98..ba2ed943c 100644 --- a/third_party/maul-nav/src/bake_input.h +++ b/third_party/maul-nav/src/bake_input.h @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2026 Sirac Ozmen // -// A 3D bake's whole input, meshes, terrains and volumes, checked as -// hostile input, for the navmesh and flight bakers. +// A bake's whole input, meshes, terrains and volumes, or outlines, +// checked as hostile input, for the navmesh and flight bakers. #ifndef MAUL_NAV_SRC_BAKE_INPUT_H #define MAUL_NAV_SRC_BAKE_INPUT_H @@ -14,15 +14,14 @@ // Whether input's counts are at least 0 and its arrays given where its // counts are positive. -bool mnavCheckInputArrays(const mnavBakeInput* input); +MNAV_NODISCARD bool mnavCheckInputArrays(const mnavBakeInput* input); // Checks input whose arrays are checked against a def that is already // valid: each mesh unless meshesChecked, each terrain, each volume, and // their triangles, terrains' and volumes' points counted, against the -// def's input triangles limit. A refused element's index (terrains after -// the meshes, volumes after the terrains) goes to meshOut and its check's -// outcome to inputOut. +// def's input triangles limit. A refused input's outcome, with its kind +// and index, goes to inputOut. mnavResult mnavCheckSolidInput(const mnavBakeDef* def, const mnavBakeInput* input, - bool meshesChecked, int32_t* meshOut, mnavInputResult* inputOut); + bool meshesChecked, mnavInputResult* inputOut); #endif // MAUL_NAV_SRC_BAKE_INPUT_H diff --git a/third_party/maul-nav/src/baker.c b/third_party/maul-nav/src/baker.c index a605d5d73..3fa0f95bf 100644 --- a/third_party/maul-nav/src/baker.c +++ b/third_party/maul-nav/src/baker.c @@ -22,6 +22,7 @@ #include "input.h" #include "invariant.h" #include "outline.h" +#include "outputs.h" #include "polymesh.h" #include "raster.h" #include "region.h" @@ -78,7 +79,7 @@ static MNAV_NOINLINE mnavBakeDefResult CreateBakerWork(const mnavBakeDef* def, m { if (bakerOut == nullptr) { - return (mnavBakeDefResult){mnav_errorInvalid, mnav_settingNone}; + return (mnavBakeDefResult){mnav_errorInvalid, mnav_bakeSettingNone}; } *bakerOut = nullptr; mnavBakeCells cells = {0}; @@ -93,12 +94,12 @@ static MNAV_NOINLINE mnavBakeDefResult CreateBakerWork(const mnavBakeDef* def, m mnavAllocate(&memory, 1, sizeof(mnavBaker), alignof(mnavBaker), (void**)&baker); if (result != mnav_success) { - return (mnavBakeDefResult){result, mnav_settingNone}; + return (mnavBakeDefResult){result, mnav_bakeSettingNone}; } *baker = (mnavBaker){*def, cells, memory, nullptr, 0, 0, {0}}; baker->memory.host = &baker->host; *bakerOut = baker; - return (mnavBakeDefResult){mnav_success, mnav_settingNone}; + return (mnavBakeDefResult){mnav_success, mnav_bakeSettingNone}; } mnavBakeDefResult mnavCreateBaker(const mnavBakeDef* def, mnavBaker** bakerOut) @@ -116,7 +117,7 @@ static void DropTile(mnavBaker* baker) baker->tileSize = 0; } -uint64_t mnavGetBakerMisuse(const mnavBaker* baker) +uint64_t mnavBaker_GetMisuse(const mnavBaker* baker) { return baker != nullptr ? mnavHostMisuse(&baker->host) : 0; } @@ -161,8 +162,7 @@ static mnavResult CheckOutlines(const mnavBaker* baker, const Input* in, int32_t mnavInputResult input = mnavCheckOutline(&baker->def, outline); if (input.result != mnav_success) { - report->mesh = (int32_t)(outline - outlines); - report->input = input; + report->input = mnavInputNamed(input, mnav_inputOutline, (int32_t)(outline - outlines)); return input.result; } } @@ -189,8 +189,7 @@ static mnavResult CheckIndexed(const mnavBaker* baker, const mnavBakeInput* soli mnavInputResult input = mnavCheckMeshShape(&baker->def, &solid->meshes[m]); if (input.result != mnav_success) { - report->mesh = m; - report->input = input; + report->input = mnavInputNamed(input, mnav_inputMesh, m); return input.result; } } @@ -203,8 +202,7 @@ static mnavResult CheckIndexed(const mnavBaker* baker, const mnavBakeInput* soli mnavCheckMeshTriangle(&baker->def, &solid->meshes[list[k].mesh], list[k].triangle); if (input.result != mnav_success) { - report->mesh = list[k].mesh; - report->input = input; + report->input = mnavInputNamed(input, mnav_inputMesh, list[k].mesh); return input.result; } } @@ -230,8 +228,7 @@ static mnavResult CheckInput(const mnavBaker* baker, const Input* in, int32_t ti return result; } } - return mnavCheckSolidInput(&baker->def, &in->solid, in->solid.index != nullptr, &report->mesh, - &report->input); + return mnavCheckSolidInput(&baker->def, &in->solid, in->solid.index != nullptr, &report->input); } // The generator, the settings that shape the tile and its place. @@ -482,17 +479,18 @@ static void ReleaseStages(mnavMemory* memory, Stages* s) // Writes the stages' counts into the report. static void Count(const Stages* s, mnavBakeReport* report) { - report->spans = s->compact.spanCount; - report->regions = (int32_t)s->regions.count; - report->droppedRegions = (int32_t)s->regions.dropped; - report->polygons = s->mesh.polygonCount; - report->vertices = s->mesh.vertexCount; - report->detailVertices = s->detail.vertexCount; - report->detailTriangles = s->detail.triangleCount; - report->droppedHoles = s->set.droppedHoles; - report->partialRings = s->mesh.failedRings; - report->fallbackHeights = s->detail.fallbackHeights; - report->cappedDetail = s->detail.cappedPolygons; + report->spanCount = s->compact.spanCount; + report->regionCount = (int32_t)s->regions.count; + report->regionsDropped = (int32_t)s->regions.dropped; + report->polygonCount = s->mesh.polygonCount; + report->vertexCount = s->mesh.vertexCount; + report->detailVertexCount = s->detail.vertexCount; + report->detailTriangleCount = s->detail.triangleCount; + report->holesDropped = s->set.droppedHoles; + report->partialRingCount = s->mesh.failedRings; + report->fallbackHeightCount = s->detail.fallbackHeights; + report->cappedDetailCount = s->detail.cappedPolygons; + report->coarseEdgeCount = s->detail.coarseEdges; } // Encodes the tile from the stages. @@ -511,11 +509,11 @@ static mnavResult Encode(mnavBaker* baker, int32_t tileX, int32_t tileZ, const S // Starts a bake or a rebuild: a fresh report, the last tile dropped and // the input stage begun. False, with the report given out, for a NULL // baker. -static bool Start(mnavBaker* baker, mnavBakeReport* report, mnavBakeReport* reportOut) +MNAV_NODISCARD static bool Start(mnavBaker* baker, mnavBakeReport* report, + mnavBakeReport* reportOut) { *report = (mnavBakeReport){0}; - report->mesh = -1; - report->input = (mnavInputResult){mnav_success, mnav_elementNone, -1}; + report->input = mnavInputElementResult(mnav_success, mnav_elementNone, -1); if (baker == nullptr) { report->result = mnav_errorInvalid; @@ -583,13 +581,14 @@ static mnavResult RunBake(mnavBaker* baker, const Input* in, int32_t tileX, int3 { if (in->flat) { - report.fingerprint = - HashOutlines(&frame, in, tileX, tileZ, HashSettings(&settings), &report.triangles); + report.fingerprint = HashOutlines(&frame, in, tileX, tileZ, HashSettings(&settings), + &report.triangleCount); } else { - geometry = mnavFingerprintGeometry(&frame, baker->cells.cosMaxSlope, &in->solid, tileX, - tileZ, HashSettings(&settings), &report.triangles); + geometry = + mnavFingerprintGeometry(&frame, baker->cells.cosMaxSlope, &in->solid, tileX, tileZ, + HashSettings(&settings), &report.triangleCount); report.fingerprint = mnavFingerprintVolumes(&frame, in->solid.volumes, in->solid.volumeCount, geometry); } @@ -601,7 +600,7 @@ static mnavResult RunBake(mnavBaker* baker, const Input* in, int32_t tileX, int3 .tileZ = tileZ, .settings = settings, .geometry = geometry, - .triangles = report.triangles}; + .triangles = report.triangleCount}; result = mnavCacheTile(cache, &baker->memory, &tile, &stages.compact, in->solid.volumes, in->solid.volumeCount); } @@ -665,8 +664,8 @@ static MNAV_NOINLINE mnavResult BakeTileWork(mnavBaker* baker, const mnavBakeInp return Bake(baker, &in, tileX, tileZ, nullptr, reportOut); } -mnavResult mnavBakeTile(mnavBaker* baker, const mnavBakeInput* input, int32_t tileX, int32_t tileZ, - mnavBakeReport* reportOut) +mnavResult mnavBaker_BakeTile(mnavBaker* baker, const mnavBakeInput* input, int32_t tileX, + int32_t tileZ, mnavBakeReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BakeTileWork(baker, input, tileX, tileZ, reportOut); @@ -692,8 +691,8 @@ static MNAV_NOINLINE mnavResult BakeTile2DWork(mnavBaker* baker, const mnavBake2 return Bake(baker, &in, tileX, tileZ, nullptr, reportOut); } -mnavResult mnavBakeTile2D(mnavBaker* baker, const mnavBake2DInput* input, int32_t tileX, - int32_t tileZ, mnavBakeReport* reportOut) +mnavResult mnavBaker_BakeTile2D(mnavBaker* baker, const mnavBake2DInput* input, int32_t tileX, + int32_t tileZ, mnavBakeReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BakeTile2DWork(baker, input, tileX, tileZ, reportOut); @@ -719,8 +718,9 @@ static MNAV_NOINLINE mnavResult BakeTileCachedWork(mnavBaker* baker, mnavTileCac return Bake(baker, &in, tileX, tileZ, cache, reportOut); } -mnavResult mnavBakeTileCached(mnavBaker* baker, mnavTileCache* cache, const mnavBakeInput* input, - int32_t tileX, int32_t tileZ, mnavBakeReport* reportOut) +mnavResult mnavBaker_BakeTileCached(mnavBaker* baker, mnavTileCache* cache, + const mnavBakeInput* input, int32_t tileX, int32_t tileZ, + mnavBakeReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BakeTileCachedWork(baker, cache, input, tileX, tileZ, reportOut); @@ -745,8 +745,7 @@ static mnavResult CheckObstacles(const mnavBaker* baker, const mnavCachedTile* t mnavInputResult input = mnavCheckVolume(&baker->def, &obstacles[i]); if (input.result != mnav_success) { - report->mesh = i; - report->input = input; + report->input = mnavInputNamed(input, mnav_inputObstacle, i); return input.result; } total += obstacles[i].pointCount; @@ -795,7 +794,7 @@ static mnavResult Rebuild(mnavBaker* baker, mnavTileCache* cache, int32_t tileX, memcpy(&volumes[tile->volumeCount], obstacles, (size_t)obstacleCount * sizeof(mnavBakeVolume)); } - report.triangles = tile->triangles; + report.triangleCount = tile->triangles; report.fingerprint = mnavFingerprintVolumes(&tile->field.frame, volumes, count, tile->geometry); Enter(baker, &report, mnav_stageCompact); @@ -832,9 +831,9 @@ static MNAV_NOINLINE mnavResult RebuildTileWork(mnavBaker* baker, mnavTileCache* return Counted(baker, cache, &report, reportOut, result); } -mnavResult mnavRebuildTile(mnavBaker* baker, mnavTileCache* cache, int32_t tileX, int32_t tileZ, - const mnavBakeVolume* obstacles, int32_t obstacleCount, - mnavBakeReport* reportOut) +mnavResult mnavBaker_RebuildTile(mnavBaker* baker, mnavTileCache* cache, int32_t tileX, + int32_t tileZ, const mnavBakeVolume* obstacles, + int32_t obstacleCount, mnavBakeReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -846,10 +845,14 @@ mnavResult mnavRebuildTile(mnavBaker* baker, mnavTileCache* cache, int32_t tileX static mnavResult CopyBakedTileWork(const mnavBaker* baker, uint8_t* bufferOut, size_t capacity, size_t* sizeOut) { - if (baker == nullptr || (bufferOut == nullptr && capacity > 0) || baker->tile == nullptr) + if (baker == nullptr || (bufferOut == nullptr && capacity > 0)) { return mnav_errorInvalid; } + if (baker->tile == nullptr) + { + return mnav_errorState; + } if (sizeOut != nullptr) { *sizeOut = baker->tileSize; @@ -865,9 +868,13 @@ static mnavResult CopyBakedTileWork(const mnavBaker* baker, uint8_t* bufferOut, return mnav_success; } -mnavResult mnavCopyBakedTile(const mnavBaker* baker, uint8_t* bufferOut, size_t capacity, - size_t* sizeOut) +mnavResult mnavBaker_CopyTile(const mnavBaker* baker, uint8_t* bufferOut, size_t capacity, + size_t* sizeOut) { - return mnavHostCountInvalid(MNAV_HOST(baker), - CopyBakedTileWork(baker, bufferOut, capacity, sizeOut)); + mnavClearOutput(sizeOut, sizeof(*sizeOut)); + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(baker), CopyBakedTileWork(baker, bufferOut, capacity, sizeOut)); + // A buffer too small for the tile gets none of it. + mnavClearArray(result, false, bufferOut, mnavByteCapacity(capacity), 1); + return mnavClearUnlessCapacity(result, sizeOut, sizeof(*sizeOut)); } diff --git a/third_party/maul-nav/src/base.c b/third_party/maul-nav/src/base.c index 1dacd745a..cd06b2101 100644 --- a/third_party/maul-nav/src/base.c +++ b/third_party/maul-nav/src/base.c @@ -10,7 +10,7 @@ mnavVersion mnavGetVersion(void) return (mnavVersion){MNAV_VERSION_MAJOR, MNAV_VERSION_MINOR, MNAV_VERSION_PATCH}; } -const char* mnavResultName(mnavResult result) +const char* mnavGetResultName(mnavResult result) { switch (result) { diff --git a/third_party/maul-nav/src/border_vertices.c b/third_party/maul-nav/src/border_vertices.c index c80d835ce..1dda5703f 100644 --- a/third_party/maul-nav/src/border_vertices.c +++ b/third_party/maul-nav/src/border_vertices.c @@ -85,7 +85,7 @@ static mnavResult CollectTouching(Work* work, uint16_t vertex) } // Whether the polygons around the vertex share one area type. -static bool SameArea(const Work* work) +MNAV_NODISCARD static bool SameArea(const Work* work) { const mnavPolygon* polygons = work->mesh->polygons; for (int32_t t = 1; t < work->touchingCount; ++t) @@ -207,7 +207,7 @@ static mnavResult CollectEdges(Work* work, uint16_t vertex) // Chains the edges into the hole's outline, each edge extending its end // or its start, the first fitting edge in order each time. Returns false // when the edges do not form one chain. -static bool Chain(Work* work) +MNAV_NODISCARD static bool Chain(Work* work) { HoleEdge* edges = work->edges; uint16_t* hole = work->hole; @@ -260,7 +260,7 @@ static mnavContourVertex Ground(const mnavPolyMesh* mesh, uint16_t index) // a vertex inside the mesh; an open one must close across the vertex's // own place, the vertex lying on the segment from the chain's end back // to its start. -static bool KeepsOutline(const Work* work, uint16_t vertex) +MNAV_NODISCARD static bool KeepsOutline(const Work* work, uint16_t vertex) { if (work->closed) { @@ -279,9 +279,11 @@ static bool KeepsOutline(const Work* work, uint16_t vertex) // polygons, or -1 when the triangulation does not finish. The hole's ring // is made of the outer edges of convex polygons round the vertex, so it // is star-shaped round the vertex's place and triangulates fully unless -// it passes some vertex twice; the checks for an unfinished -// triangulation and for triangles with a corner twice guard that case, -// which no input has yet made. +// it passes some vertex twice; the check for an unfinished triangulation +// guards that case, which no input has yet made. A triangle with no area +// on the ground is dropped, as the polygon stage drops one: it covers +// nothing, and two vertices welded apart at one ground place would give +// it an edge with no length there, which the detail stage divides by. static int32_t Fill(Work* work) { const mnavPolyMesh* mesh = work->mesh; @@ -309,7 +311,7 @@ static int32_t Fill(Work* work) uint16_t a = work->hole[corner[0]]; uint16_t b = work->hole[corner[1]]; uint16_t c = work->hole[corner[2]]; - if (a == b || a == c || b == c) + if (mnavArea2(&work->ring[corner[0]], &work->ring[corner[1]], &work->ring[corner[2]]) == 0) { continue; } diff --git a/third_party/maul-nav/src/commit.c b/third_party/maul-nav/src/commit.c index 6919391db..77fb2a200 100644 --- a/third_party/maul-nav/src/commit.c +++ b/third_party/maul-nav/src/commit.c @@ -148,7 +148,7 @@ static MNAV_NOINLINE mnavResult CommitCounted(mnavNavmesh* navmesh) return mnavHostCountInvalid(MNAV_HOST(navmesh), CommitWork(navmesh)); } -mnavResult mnavCommit(mnavNavmesh* navmesh) +mnavResult mnavNavmesh_Commit(mnavNavmesh* navmesh) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = CommitCounted(navmesh); diff --git a/third_party/maul-nav/src/contour.c b/third_party/maul-nav/src/contour.c index ca32b8d41..09ae97405 100644 --- a/third_party/maul-nav/src/contour.c +++ b/third_party/maul-nav/src/contour.c @@ -43,17 +43,30 @@ typedef struct Work int32_t* kept; int32_t keptCount; int32_t keptCapacity; + // The region traced, and, in the second pass, what the first found: + // the regions one of whose contours kept fewer than three vertices, + // by id, which keep their cells along every stretch, as the regions + // across those stretches do, so that both sides of a stretch keep the + // same vertices; and each region's outline bounds on the ground, four + // per id (x0, z0, x1, z1), to find what a hole of walls holds. + uint32_t region; + const uint8_t* fell; + const int32_t* bounds; + // The regions that fell back, by id. + const uint32_t* fallen; + uint32_t fallenCount; } Work; typedef struct Tracer { const mnavCompactField* field; const uint32_t* ids; + uint32_t regionCount; uint8_t* edges; int32_t border; } Tracer; -static bool IsRegion(uint32_t id) +MNAV_NODISCARD static bool IsRegion(uint32_t id) { return id != 0 && (id & MNAV_BORDER_REGION) == 0; } @@ -270,7 +283,7 @@ static float SegmentDistance(int32_t x, int32_t z, int32_t ax, int32_t az, int32 return px * px + pz * pz; } -static bool LexicallyAfter(const RawVertex* b, const RawVertex* a) +MNAV_NODISCARD static bool LexicallyAfter(const RawVertex* b, const RawVertex* a) { return b->x > a->x || (b->x == a->x && b->z > a->z); } @@ -325,6 +338,65 @@ static void CheckStraight(const Work* work, int32_t at, int32_t end, int32_t ste (void)neighbor; } +// The first raw vertex past the kept segment at position i's start, in +// its lexical direction: every raw vertex of the segment has its +// neighbor, since the seeds are where the neighbor changes. +static int32_t FirstOf(const Work* work, int32_t i) +{ + int32_t n = work->rawCount; + int32_t ai = work->kept[i]; + int32_t bi = work->kept[(i + 1) % work->keptCount]; + bool forward = LexicallyAfter(&work->raw[bi], &work->raw[ai]); + return forward ? (ai + 1) % n : (bi + n - 1) % n; +} + +// Whether a region fell back in the first pass. +MNAV_NODISCARD static bool Fell(const Work* work, uint32_t id) +{ + return work->fell != nullptr && IsRegion(id) && work->fell[id] != 0; +} + +// Whether the raw vertices of the kept segment at position i, its ends +// included, reach within the outline bounds of a region that fell back +// other than its own: a wall simplified there could cut across the +// region's cells, which it keeps. +MNAV_NODISCARD static bool NearFallen(const Work* work, int32_t i) +{ + if (work->fallenCount == 0) + { + return false; + } + int32_t n = work->rawCount; + int32_t ai = work->kept[i]; + int32_t bi = work->kept[(i + 1) % work->keptCount]; + int32_t x0 = INT32_MAX; + int32_t z0 = INT32_MAX; + int32_t x1 = INT32_MIN; + int32_t z1 = INT32_MIN; + for (int32_t at = ai;; at = (at + 1) % n) + { + const RawVertex* v = &work->raw[at]; + x0 = v->x < x0 ? v->x : x0; + z0 = v->z < z0 ? v->z : z0; + x1 = v->x > x1 ? v->x : x1; + z1 = v->z > z1 ? v->z : z1; + if (at == bi) + { + break; + } + } + for (uint32_t k = 0; k < work->fallenCount; ++k) + { + uint32_t id = work->fallen[k]; + const int32_t* b = &work->bounds[4 * id]; + if (id != work->region && b[0] <= x1 && b[2] >= x0 && b[1] <= z1 && b[3] >= z0) + { + return true; + } + } + return false; +} + // The raw vertex farthest from the kept segment at position i, or -1, // for walls and area borders; the segment is measured in its lexical // direction so the regions on both sides of an edge agree. Every other @@ -342,7 +414,7 @@ static int32_t Farthest(const Work* work, int32_t i, float* distanceOut) const RawVertex* b = &work->raw[bi]; bool forward = LexicallyAfter(b, a); int32_t step = forward ? 1 : n - 1; - int32_t at = forward ? (ai + 1) % n : (bi + n - 1) % n; + int32_t at = FirstOf(work, i); int32_t end = forward ? bi : ai; const RawVertex* from = forward ? a : b; const RawVertex* to = forward ? b : a; @@ -370,14 +442,84 @@ static int32_t Farthest(const Work* work, int32_t i, float* distanceOut) return farthest; } +// Twice the signed area of the raw contour on the ground: positive for a +// region's outline, negative for a hole. +static int64_t RawArea(const Work* work) +{ + int64_t area = 0; + for (int32_t i = 0, j = work->rawCount - 1; i < work->rawCount; j = i++) + { + const RawVertex* a = &work->raw[i]; + const RawVertex* b = &work->raw[j]; + area += (int64_t)a->x * b->z - (int64_t)b->x * a->z; + } + return area; +} + +// Whether a contour may go when it simplifies to fewer than three vertices: +// a hole all of whose edges are walls, which its region then covers, +// when it holds no other region (HoldsAnother). +MNAV_NODISCARD static bool MayGo(const Work* work) +{ + if (RawArea(work) > 0) + { + return false; + } + for (int32_t i = 0; i < work->rawCount; ++i) + { + if (work->raw[i].neighbor != 0) + { + return false; + } + } + return true; +} + +// Whether a hole of walls holds another region: one whose outline's bounds +// lie inside the hole's, which it would cover if it went. Layers above or +// below count too; a hole kept for one is only more exact. +MNAV_NODISCARD static bool HoldsAnother(const Work* work, uint32_t count) +{ + int32_t x0 = INT32_MAX; + int32_t z0 = INT32_MAX; + int32_t x1 = INT32_MIN; + int32_t z1 = INT32_MIN; + for (int32_t i = 0; i < work->rawCount; ++i) + { + const RawVertex* v = &work->raw[i]; + x0 = v->x < x0 ? v->x : x0; + z0 = v->z < z0 ? v->z : z0; + x1 = v->x > x1 ? v->x : x1; + z1 = v->z > z1 ? v->z : z1; + } + for (uint32_t id = 1; work->bounds != nullptr && id <= count; ++id) + { + const int32_t* b = &work->bounds[4 * id]; + if (id != work->region && b[0] <= b[2] && b[0] > x0 && b[1] > z0 && b[2] < x1 && b[3] < z1) + { + return true; + } + } + return false; +} + +// Keeps the raw vertices the walls need to stay within edgeError of their +// cells: within 0 a stretch of a region that fell back, across from one, +// or reaching within one's bounds (Work), so that no wall simplified past +// a cell cuts into a region kept to its cells. static mnavResult Simplify(mnavMemory* memory, Work* work, float edgeError) { mnavResult result = Seed(memory, work); + bool fell = Fell(work, work->region); for (int32_t i = 0; result == mnav_success && i < work->keptCount;) { float distance = 0.0f; int32_t farthest = Farthest(work, i, &distance); - if (farthest >= 0 && distance > edgeError * edgeError) + float error = + fell || Fell(work, work->raw[FirstOf(work, i)].neighbor) || NearFallen(work, i) + ? 0.0f + : edgeError; + if (farthest >= 0 && distance > error * error) { result = Keep(memory, work, i, farthest); } @@ -488,8 +630,103 @@ static mnavResult Store(mnavMemory* memory, const Work* work, uint32_t region, m return mnav_success; } +// What the first pass finds for the second (Work), and whether a second +// pass is needed: a region fell back, or a hole of walls wide enough to +// hold another region went. +typedef struct Findings +{ + uint8_t* fell; + int32_t* bounds; + uint32_t* fallen; + uint32_t fallenCount; + uint32_t count; + bool again; +} Findings; + +// Notes a contour's outline bounds and, when it kept fewer than three +// vertices, whether it falls back, in the first pass. +static void Note(const Work* work, bool goes, Findings* f) +{ + int32_t* b = &f->bounds[4 * work->region]; + bool outline = RawArea(work) > 0; + int32_t x0 = INT32_MAX; + int32_t z0 = INT32_MAX; + int32_t x1 = INT32_MIN; + int32_t z1 = INT32_MIN; + for (int32_t i = 0; i < work->rawCount; ++i) + { + const RawVertex* v = &work->raw[i]; + x0 = v->x < x0 ? v->x : x0; + z0 = v->z < z0 ? v->z : z0; + x1 = v->x > x1 ? v->x : x1; + z1 = v->z > z1 ? v->z : z1; + } + if (outline) + { + b[0] = x0 < b[0] ? x0 : b[0]; + b[1] = z0 < b[1] ? z0 : b[1]; + b[2] = x1 > b[2] ? x1 : b[2]; + b[3] = z1 > b[3] ? z1 : b[3]; + } + if (work->keptCount >= 3) + { + return; + } + if (!goes) + { + if (f->fell[work->region] == 0) + { + f->fallen[f->fallenCount++] = work->region; + } + f->fell[work->region] = 1; + f->again = true; + } + // A hole of walls holds another region only round at least a cell of + // it and a cell of wall on each side. + else if (x1 - x0 >= 3 && z1 - z0 >= 3) + { + f->again = true; + } +} + +// Simplifies the contour just walked. An outline so thin that no wall +// strays edgeError would keep two vertices and its region be lost, though +// it is walkable and a neighboring tile keeps the same cells in a wider +// region up to the shared side: it keeps its cells' outline, and in the +// second pass so do the stretches across from it. A hole of walls that +// thin goes, and its region covers it, unless it holds another region. +// The first pass, with findings to fill, simplifies each contour alone. +static mnavResult SimplifyOne(mnavMemory* memory, const Tracer* tracer, Work* work, float edgeError, + Findings* first) +{ + mnavResult result = Simplify(memory, work, edgeError); + if (result != mnav_success || work->keptCount >= 3 || edgeError == 0.0f) + { + if (result == mnav_success && first != nullptr) + { + Note(work, true, first); + } + return result; + } + bool goes = MayGo(work) && (first != nullptr || !HoldsAnother(work, tracer->regionCount)); + if (first != nullptr) + { + Note(work, goes, first); + } + if (goes) + { + return mnav_success; + } + work->keptCount = 0; + return Simplify(memory, work, 0.0f); +} + +// Traces, simplifies and stores every contour. The first pass, with +// findings to fill, simplifies each contour alone; the second, with the +// first's, simplifies the stretches of regions that fell back, across +// from them or near them, within 0. static mnavResult TraceAll(mnavMemory* memory, const Tracer* tracer, Work* work, float edgeError, - int32_t edgeLength, mnavContourSet* set) + int32_t edgeLength, Findings* first, mnavContourSet* set) { const mnavCompactField* field = tracer->field; int32_t width = field->frame.width; @@ -503,10 +740,11 @@ static mnavResult TraceAll(mnavMemory* memory, const Tracer* tracer, Work* work, } work->rawCount = 0; work->keptCount = 0; + work->region = tracer->ids[i]; mnavResult result = Walk(memory, tracer, work, c % width, c / width, i); if (result == mnav_success) { - result = Simplify(memory, work, edgeError); + result = SimplifyOne(memory, tracer, work, edgeError, first); } if (result == mnav_success) { @@ -556,16 +794,49 @@ mnavResult mnavBuildContours(mnavMemory* memory, const mnavCompactField* field, int32_t edgeLength, mnavContourSet* set) { *set = (mnavContourSet){0}; - Tracer tracer = {field, regions->ids, nullptr, border}; + Tracer tracer = {field, regions->ids, regions->count, nullptr, border}; + size_t ids = (size_t)regions->count + 1; + Findings findings = {nullptr, nullptr, nullptr, 0, regions->count, false}; mnavResult result = mnavAllocate(memory, (size_t)field->spanCount, sizeof(uint8_t), alignof(uint8_t), (void**)&tracer.edges); - if (result != mnav_success) + result = result == mnav_success ? mnavAllocate(memory, ids, sizeof(uint8_t), alignof(uint8_t), + (void**)&findings.fell) + : result; + result = result == mnav_success ? mnavAllocate(memory, 4 * ids, sizeof(int32_t), + alignof(int32_t), (void**)&findings.bounds) + : result; + result = result == mnav_success ? mnavAllocate(memory, ids, sizeof(uint32_t), alignof(uint32_t), + (void**)&findings.fallen) + : result; + Work work = {0}; + if (result == mnav_success) { - return result; + memset(findings.fell, 0, ids); + for (size_t id = 0; id < ids; ++id) + { + int32_t* b = &findings.bounds[4 * id]; + b[0] = INT32_MAX; + b[1] = INT32_MAX; + b[2] = INT32_MIN; + b[3] = INT32_MIN; + } + MarkEdges(&tracer); + result = TraceAll(memory, &tracer, &work, edgeError, edgeLength, &findings, set); } - MarkEdges(&tracer); - Work work = {0}; - result = TraceAll(memory, &tracer, &work, edgeError, edgeLength, set); + if (result == mnav_success && findings.again) + { + set->count = 0; + set->vertexCount = 0; + work.fell = findings.fell; + work.bounds = findings.bounds; + work.fallen = findings.fallen; + work.fallenCount = findings.fallenCount; + MarkEdges(&tracer); + result = TraceAll(memory, &tracer, &work, edgeError, edgeLength, nullptr, set); + } + mnavRelease(memory, findings.fallen, ids, sizeof(uint32_t), alignof(uint32_t)); + mnavRelease(memory, findings.bounds, 4 * ids, sizeof(int32_t), alignof(int32_t)); + mnavRelease(memory, findings.fell, ids, sizeof(uint8_t), alignof(uint8_t)); mnavRelease(memory, work.kept, (size_t)work.keptCapacity, sizeof(int32_t), alignof(int32_t)); mnavRelease(memory, work.raw, (size_t)work.rawCapacity, sizeof(RawVertex), alignof(RawVertex)); mnavRelease(memory, tracer.edges, (size_t)field->spanCount, sizeof(uint8_t), alignof(uint8_t)); diff --git a/third_party/maul-nav/src/contour.h b/third_party/maul-nav/src/contour.h index c7b856cf3..b3d1f3ac0 100644 --- a/third_party/maul-nav/src/contour.h +++ b/third_party/maul-nav/src/contour.h @@ -67,6 +67,6 @@ void mnavReleaseContours(mnavMemory* memory, mnavContourSet* set); // region, 0 for none), two cells of one border region are followed by two // interior cells of one area: a vertex on the tile's border that polygons // later remove so that neighboring tiles meet. -bool mnavIsTileBorderCorner(const uint64_t keys[4]); +MNAV_NODISCARD bool mnavIsTileBorderCorner(const uint64_t keys[4]); #endif // MAUL_NAV_SRC_CONTOUR_H diff --git a/third_party/maul-nav/src/corridor.c b/third_party/maul-nav/src/corridor.c index 286930626..ed2d8e2dd 100644 --- a/third_party/maul-nav/src/corridor.c +++ b/third_party/maul-nav/src/corridor.c @@ -10,6 +10,7 @@ #include "navmesh.h" #include "nearest.h" #include "offmesh.h" +#include "outputs.h" #include "polymesh.h" #include "query.h" #include "query_filter.h" @@ -22,23 +23,34 @@ #include #include -static bool Finite(mnavPos3 p) +MNAV_NODISCARD static bool Finite(mnavPos3 p) { return isfinite(p.x) && isfinite(p.y) && isfinite(p.z); } -static bool SameId(mnavPolygonId a, mnavPolygonId b) +MNAV_NODISCARD static bool SameId(mnavPolygonId a, mnavPolygonId b) { return a.slot == b.slot && a.generation == b.generation && a.polygon == b.polygon; } -// Whether a corridor is well formed: a buffer, room for a polygon at -// least, and from one polygon to its room. Its fields are the caller's, -// so every call checks them before it reads the buffer. -static bool WellFormed(const mnavCorridor* corridor) +// Whether a corridor is well formed: finite ends, a buffer, room for a +// polygon at least, and from one polygon to its room. Its fields are the +// caller's, so every call checks them before it reads the buffer. +MNAV_NODISCARD static bool WellFormed(const mnavCorridor* corridor) { - return corridor != nullptr && corridor->polygons != nullptr && corridor->capacity >= 1 && - corridor->count >= 1 && corridor->count <= corridor->capacity; + return corridor != nullptr && Finite(corridor->position) && Finite(corridor->target) && + corridor->polygons != nullptr && corridor->capacity >= 1 && + corridor->polygonCount >= 1 && corridor->polygonCount <= corridor->capacity; +} + +// Whether a well formed corridor's ends lie within MNAV_MAX_QUERY_DISTANCE +// of the navmesh's origin: every call that takes a navmesh refuses one +// that does not with mnav_errorLimit. A NULL navmesh passes here, for the +// call to refuse as invalid. +MNAV_NODISCARD static bool InReach(const mnavNavmesh* navmesh, const mnavCorridor* corridor) +{ + return navmesh == nullptr || (mnavInQueryReach(navmesh, corridor->position) && + mnavInQueryReach(navmesh, corridor->target)); } static MNAV_NOINLINE mnavResult ResetCorridorWork(mnavCorridor* corridor, mnavPolygonId* buffer, @@ -63,20 +75,51 @@ mnavResult mnavResetCorridor(mnavCorridor* corridor, mnavPolygonId* buffer, int3 return result; } +// Whether a path is the one its context keeps, within the counts it +// kept: mnav_errorStale when a later search's path has replaced it, +// mnav_errorInvalid for a path no search gave or one whose arrays or +// counts were changed. Only the context's own fields are read, so no +// copied field reaches outside its arrays. +static mnavResult CheckPath(const mnavPath* path) +{ + const mnavQuery* query = path->query; + if (query == nullptr) + { + return mnav_errorInvalid; + } + if (path->generation != query->generation) + { + return mnav_errorStale; + } + bool links = path->linkCount == 0 || path->links == query->pathLinks; + return path->polygons == query->pathPolygons && path->points == query->pathPoints && links && + path->polygonCount >= 1 && path->polygonCount <= query->pathPolygonCount && + path->pointCount >= 1 && path->pointCount <= query->pathPointCount && + path->linkCount >= 0 && path->linkCount <= query->pathLinkCount + ? mnav_success + : mnav_errorInvalid; +} + static MNAV_NOINLINE mnavResult SetCorridorWork(mnavCorridor* corridor, const mnavPath* path) { // The count is replaced, so only the buffer and its room are read. if (corridor == nullptr || path == nullptr || corridor->polygons == nullptr || - corridor->capacity < 1 || path->polygonCount < 1 || path->pointCount < 1) + corridor->capacity < 1 || path->polygonCount < 1 || path->polygons == nullptr || + path->pointCount < 1 || path->points == nullptr) { return mnav_errorInvalid; } + mnavResult checked = CheckPath(path); + if (checked != mnav_success) + { + return checked; + } if (path->polygonCount > corridor->capacity) { return mnav_errorCapacity; } memcpy(corridor->polygons, path->polygons, (size_t)path->polygonCount * sizeof(mnavPolygonId)); - corridor->count = path->polygonCount; + corridor->polygonCount = path->polygonCount; corridor->position = path->points[0]; corridor->target = path->points[path->pointCount - 1]; return mnav_success; @@ -100,9 +143,10 @@ static mnavPos3 AlongSide(mnavPos3 a, mnavPos3 b, double au, double bu, double u // The portal where edge j of polygon p (a to b) meets polygon q across a // tile side: the part of the side their link covers, in the edge's // direction. False when no link of the edge reaches q. -static bool SidePortal(const mnavTile* tile, mnavPolygonId p, int32_t j, mnavPolygonId q, - const mnavMeshVertex* va, const mnavMeshVertex* vb, mnavPos3 a, mnavPos3 b, - mnavPortal* out) +MNAV_NODISCARD static bool SidePortal(const mnavTile* tile, mnavPolygonId p, int32_t j, + mnavPolygonId q, const mnavMeshVertex* va, + const mnavMeshVertex* vb, mnavPos3 a, mnavPos3 b, + mnavPortal* out) { for (int32_t l = tile->firstLink[p.polygon]; l < tile->firstLink[p.polygon + 1]; ++l) { @@ -124,8 +168,8 @@ static bool SidePortal(const mnavTile* tile, mnavPolygonId p, int32_t j, mnavPol // The portal from polygon p to polygon q across one of p's edges: the // shared edge, or the part of a tile side a link covers. -static bool EdgePortal(const mnavNavmesh* navmesh, mnavPolygonId p, mnavPolygonId q, - mnavPortal* out) +MNAV_NODISCARD static bool EdgePortal(const mnavNavmesh* navmesh, mnavPolygonId p, mnavPolygonId q, + mnavPortal* out) { const mnavTile* tile = nullptr; const mnavPolygon* polygon = mnavPolygonOf(navmesh, p, &tile); @@ -153,8 +197,8 @@ static bool EdgePortal(const mnavNavmesh* navmesh, mnavPolygonId p, mnavPolygonI // The portals from polygon p to polygon q over an off-mesh link of a kind // the filter allows: its takeoff point, then its landing point. -static bool LinkPortals(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPolygonId p, - mnavPolygonId q, mnavPortal* out) +MNAV_NODISCARD static bool LinkPortals(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, + mnavPolygonId p, mnavPolygonId q, mnavPortal* out) { int32_t first = 0; int32_t count = mnavAttachmentsFrom(navmesh, (int32_t)p.slot - 1, (int32_t)p.polygon, &first); @@ -196,7 +240,7 @@ static int32_t Valid(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, { mnavPortal scratch[2]; *stale = false; - for (int32_t i = 0; i < corridor->count; ++i) + for (int32_t i = 0; i < corridor->polygonCount; ++i) { mnavPolygonId id = corridor->polygons[i]; mnavResult checked = mnavCheckPolygon(navmesh, id); @@ -214,17 +258,22 @@ static int32_t Valid(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, return i; } } - return corridor->count; + return corridor->polygonCount; } static MNAV_NOINLINE mnavResult CheckCorridorWork(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavCorridor* corridor, int32_t* validOut) + const mnavCorridor* corridor, + int32_t* validCountOut) { - if (navmesh == nullptr || validOut == nullptr || !WellFormed(corridor)) + if (navmesh == nullptr || validCountOut == nullptr || !WellFormed(corridor)) { return mnav_errorInvalid; } + if (!InReach(navmesh, corridor)) + { + return mnav_errorLimit; + } const mnavQueryFilter* usable = nullptr; mnavResult result = mnavCheckFilter(filter, &usable); if (result != mnav_success) @@ -232,24 +281,26 @@ static MNAV_NOINLINE mnavResult CheckCorridorWork(const mnavNavmesh* navmesh, return result; } bool stale = false; - *validOut = Valid(navmesh, usable, corridor, &stale); + *validCountOut = Valid(navmesh, usable, corridor, &stale); return mnav_success; } static MNAV_NOINLINE mnavResult CheckCorridorCounted(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, const mnavCorridor* corridor, - int32_t* validOut) + int32_t* validCountOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), - CheckCorridorWork(navmesh, filter, corridor, validOut)); + mnavClearOutput(validCountOut, sizeof(*validCountOut)); + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(navmesh), CheckCorridorWork(navmesh, filter, corridor, validCountOut)); + return mnavClearOnFailure(result, validCountOut, sizeof(*validCountOut)); } -mnavResult mnavCheckCorridor(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavCorridor* corridor, int32_t* validOut) +mnavResult mnavNavmesh_CheckCorridor(const mnavNavmesh* navmesh, const mnavCorridor* corridor, + const mnavQueryFilter* filter, int32_t* validCountOut) { uint64_t env = mnavFloatEnvEnter(); - mnavResult result = CheckCorridorCounted(navmesh, filter, corridor, validOut); + mnavResult result = CheckCorridorCounted(navmesh, filter, corridor, validCountOut); mnavFloatEnvLeave(env); return result; } @@ -266,7 +317,11 @@ static MNAV_NOINLINE mnavResult CorridorCornersWork(mnavQuery* query, const mnav { return mnav_errorInvalid; } - if (corridor->count > query->limits.nodes) + if (!InReach(navmesh, corridor)) + { + return mnav_errorLimit; + } + if (corridor->polygonCount > query->limits.nodes) { return mnav_errorCapacity; } @@ -274,7 +329,7 @@ static MNAV_NOINLINE mnavResult CorridorCornersWork(mnavQuery* query, const mnav const mnavQueryFilter* all = nullptr; (void)mnavCheckFilter(nullptr, &all); bool stale = false; - if (Valid(navmesh, all, corridor, &stale) < corridor->count) + if (Valid(navmesh, all, corridor, &stale) < corridor->polygonCount) { return stale ? mnav_errorStale : mnav_errorInvalid; } @@ -282,7 +337,7 @@ static MNAV_NOINLINE mnavResult CorridorCornersWork(mnavQuery* query, const mnav // most twice the node limit and one, the context's room. int32_t count = 0; query->portals[count++] = (mnavPortal){corridor->position, corridor->position, -1}; - for (int32_t i = 1; i < corridor->count; ++i) + for (int32_t i = 1; i < corridor->polygonCount; ++i) { count += Join(navmesh, all, corridor->polygons[i - 1], corridor->polygons[i], &query->portals[count]); @@ -298,12 +353,17 @@ static MNAV_NOINLINE mnavResult CorridorCornersCounted(mnavQuery* query, const m const mnavCorridor* corridor, mnavCorners* cornersOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - CorridorCornersWork(query, navmesh, corridor, cornersOut)); + mnavCorners cornersMade = {0}; + mnavResult result = + mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + CorridorCornersWork(query, navmesh, corridor, + cornersOut != nullptr ? &cornersMade : nullptr)); + mnavGiveOutput(result, false, cornersOut, &cornersMade, sizeof(cornersMade)); + return result; } -mnavResult mnavCorridorCorners(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavCorridor* corridor, mnavCorners* cornersOut) +mnavResult mnavQuery_FindCorridorCorners(mnavQuery* query, const mnavCorridor* corridor, + const mnavNavmesh* navmesh, mnavCorners* cornersOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = CorridorCornersCounted(query, navmesh, corridor, cornersOut); @@ -414,7 +474,7 @@ static MNAV_NOINLINE mnavResult CorridorAreasWork(mnavQuery* query, const mnavNa int32_t corner = 0; mnavAreaType area = AreaOf(navmesh, corridor->polygons[0]); Run(runsOut, capacity, &count, (mnavAreaRun){corridor->position, 0, area}); - for (int32_t i = 1; i < corridor->count; ++i) + for (int32_t i = 1; i < corridor->polygonCount; ++i) { // Validated above: the polygons join, and the portals are written. mnavPortal portals[2] = {0}; @@ -445,14 +505,21 @@ static MNAV_NOINLINE mnavResult CorridorAreasCounted(mnavQuery* query, const mna mnavCorners* cornersOut, mnavAreaRun* runsOut, int32_t capacity, int32_t* countOut) { - return mnavHostCountInvalid( + mnavCorners cornersMade = {0}; + int32_t countMade = 0; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - CorridorAreasWork(query, navmesh, corridor, cornersOut, runsOut, capacity, countOut)); + CorridorAreasWork(query, navmesh, corridor, cornersOut != nullptr ? &cornersMade : nullptr, + runsOut, capacity, countOut != nullptr ? &countMade : nullptr)); + mnavGiveOutput(result, false, cornersOut, &cornersMade, sizeof(cornersMade)); + mnavClearArray(result, result == mnav_errorCapacity, runsOut, capacity, sizeof(*runsOut)); + mnavGiveOutput(result, result == mnav_errorCapacity, countOut, &countMade, sizeof(countMade)); + return result; } -mnavResult mnavCorridorAreas(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavCorridor* corridor, mnavCorners* cornersOut, - mnavAreaRun* runsOut, int32_t capacity, int32_t* countOut) +mnavResult mnavQuery_FindCorridorAreas(mnavQuery* query, const mnavCorridor* corridor, + const mnavNavmesh* navmesh, mnavCorners* cornersOut, + mnavAreaRun* runsOut, int32_t capacity, int32_t* countOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -483,7 +550,7 @@ static mnavResult MergeStart(mnavCorridor* corridor, const mnavMove* move) MNAV_ASSERT(move->polygonCount >= 1 && SameId(walked[0], corridor->polygons[0])); int32_t farthest = -1; int32_t at = -1; - for (int32_t i = corridor->count - 1; i >= 0 && farthest < 0; --i) + for (int32_t i = corridor->polygonCount - 1; i >= 0 && farthest < 0; --i) { int32_t j = IndexOf(walked, move->polygonCount, corridor->polygons[i]); farthest = j >= 0 ? i : -1; @@ -492,7 +559,7 @@ static mnavResult MergeStart(mnavCorridor* corridor, const mnavMove* move) // The walk began on the first polygon, so some polygon is shared. MNAV_ASSERT(farthest >= 0 && at >= 0); int32_t lead = move->polygonCount - at; - int32_t rest = corridor->count - farthest - 1; + int32_t rest = corridor->polygonCount - farthest - 1; if (lead + rest > corridor->capacity) { return mnav_errorCapacity; @@ -503,7 +570,7 @@ static mnavResult MergeStart(mnavCorridor* corridor, const mnavMove* move) { corridor->polygons[i] = walked[move->polygonCount - 1 - i]; } - corridor->count = lead + rest; + corridor->polygonCount = lead + rest; return mnav_success; } @@ -513,10 +580,10 @@ static mnavResult MergeEnd(mnavCorridor* corridor, const mnavMove* move) { const mnavPolygonId* walked = move->polygons; MNAV_ASSERT(move->polygonCount >= 1 && - SameId(walked[0], corridor->polygons[corridor->count - 1])); + SameId(walked[0], corridor->polygons[corridor->polygonCount - 1])); int32_t first = -1; int32_t at = -1; - for (int32_t i = 0; i < corridor->count && first < 0; ++i) + for (int32_t i = 0; i < corridor->polygonCount && first < 0; ++i) { at = IndexOf(walked, move->polygonCount, corridor->polygons[i]); first = at >= 0 ? i : -1; @@ -529,7 +596,7 @@ static mnavResult MergeEnd(mnavCorridor* corridor, const mnavMove* move) return mnav_errorCapacity; } memcpy(corridor->polygons + first + 1, walked + at + 1, (size_t)tail * sizeof(mnavPolygonId)); - corridor->count = first + 1 + tail; + corridor->polygonCount = first + 1 + tail; return mnav_success; } @@ -546,6 +613,10 @@ static MNAV_NOINLINE mnavResult MoveCorridorWork(mnavQuery* query, const mnavNav { return mnav_errorInvalid; } + if (!InReach(navmesh, corridor)) + { + return mnav_errorLimit; + } mnavMove move; mnavResult result = mnavMoveAlongSurfaceWork(query, navmesh, filter, corridor->polygons[0], corridor->position, wanted, &move); @@ -567,14 +638,18 @@ static MNAV_NOINLINE mnavResult MoveCorridorCounted(mnavQuery* query, const mnav mnavCorridor* corridor, mnavPos3 wanted, mnavMove* moveOut) { - return mnavHostCountInvalid( - mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - MoveCorridorWork(query, navmesh, filter, corridor, wanted, moveOut)); + mnavMove moveMade = {0}; + mnavResult result = + mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + MoveCorridorWork(query, navmesh, filter, corridor, wanted, + moveOut != nullptr ? &moveMade : nullptr)); + mnavGiveOutput(result, false, moveOut, &moveMade, sizeof(moveMade)); + return result; } -mnavResult mnavMoveCorridor(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavCorridor* corridor, mnavPos3 wanted, - mnavMove* moveOut) +mnavResult mnavQuery_MoveCorridor(mnavQuery* query, mnavCorridor* corridor, + const mnavNavmesh* navmesh, mnavPos3 wanted, + const mnavQueryFilter* filter, mnavMove* moveOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = MoveCorridorCounted(query, navmesh, filter, corridor, wanted, moveOut); @@ -595,15 +670,19 @@ static MNAV_NOINLINE mnavResult MoveCorridorTargetWork(mnavQuery* query, const m { return mnav_errorInvalid; } + if (!InReach(navmesh, corridor)) + { + return mnav_errorLimit; + } mnavMove move; - mnavResult result = - mnavMoveAlongSurfaceWork(query, navmesh, filter, corridor->polygons[corridor->count - 1], - corridor->target, wanted, &move); + mnavResult result = mnavMoveAlongSurfaceWork(query, navmesh, filter, + corridor->polygons[corridor->polygonCount - 1], + corridor->target, wanted, &move); result = result == mnav_success ? MergeEnd(corridor, &move) : result; if (result == mnav_success) { corridor->target = move.point; - MNAV_ASSERT(SameId(corridor->polygons[corridor->count - 1], move.polygon)); + MNAV_ASSERT(SameId(corridor->polygons[corridor->polygonCount - 1], move.polygon)); } if (result == mnav_success && moveOut != nullptr) { @@ -618,14 +697,18 @@ static MNAV_NOINLINE mnavResult MoveCorridorTargetCounted(mnavQuery* query, mnavCorridor* corridor, mnavPos3 wanted, mnavMove* moveOut) { - return mnavHostCountInvalid( - mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - MoveCorridorTargetWork(query, navmesh, filter, corridor, wanted, moveOut)); + mnavMove moveMade = {0}; + mnavResult result = + mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + MoveCorridorTargetWork(query, navmesh, filter, corridor, wanted, + moveOut != nullptr ? &moveMade : nullptr)); + mnavGiveOutput(result, false, moveOut, &moveMade, sizeof(moveMade)); + return result; } -mnavResult mnavMoveCorridorTarget(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavCorridor* corridor, - mnavPos3 wanted, mnavMove* moveOut) +mnavResult mnavQuery_MoveCorridorTarget(mnavQuery* query, mnavCorridor* corridor, + const mnavNavmesh* navmesh, mnavPos3 wanted, + const mnavQueryFilter* filter, mnavMove* moveOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -647,6 +730,10 @@ static MNAV_NOINLINE mnavResult ShortcutCorridorWork(mnavQuery* query, const mna { return mnav_errorInvalid; } + if (!InReach(navmesh, corridor)) + { + return mnav_errorLimit; + } *shortenedOut = false; mnavRay ray; mnavResult result = mnavRaycastWork(query, navmesh, filter, corridor->polygons[0], @@ -658,7 +745,7 @@ static MNAV_NOINLINE mnavResult ShortcutCorridorWork(mnavQuery* query, const mna // The last corridor polygon the ray passed, and where it passed it. int32_t last = -1; int32_t at = -1; - for (int32_t i = corridor->count - 1; i >= 0 && last < 0; --i) + for (int32_t i = corridor->polygonCount - 1; i >= 0 && last < 0; --i) { at = IndexOf(ray.polygons, ray.polygonCount, corridor->polygons[i]); last = at >= 0 ? i : -1; @@ -669,11 +756,11 @@ static MNAV_NOINLINE mnavResult ShortcutCorridorWork(mnavQuery* query, const mna { return mnav_success; } - int32_t rest = corridor->count - last - 1; + int32_t rest = corridor->polygonCount - last - 1; memmove(corridor->polygons + at + 1, corridor->polygons + last + 1, (size_t)rest * sizeof(mnavPolygonId)); memcpy(corridor->polygons, ray.polygons, (size_t)(at + 1) * sizeof(mnavPolygonId)); - corridor->count = at + 1 + rest; + corridor->polygonCount = at + 1 + rest; *shortenedOut = true; return mnav_success; } @@ -684,14 +771,18 @@ static MNAV_NOINLINE mnavResult ShortcutCorridorCounted(mnavQuery* query, mnavCorridor* corridor, mnavPos3 toward, bool* shortenedOut) { - return mnavHostCountInvalid( + bool shortenedMade = false; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - ShortcutCorridorWork(query, navmesh, filter, corridor, toward, shortenedOut)); + ShortcutCorridorWork(query, navmesh, filter, corridor, toward, + shortenedOut != nullptr ? &shortenedMade : nullptr)); + mnavGiveOutput(result, false, shortenedOut, &shortenedMade, sizeof(shortenedMade)); + return result; } -mnavResult mnavShortcutCorridor(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavCorridor* corridor, - mnavPos3 toward, bool* shortenedOut) +mnavResult mnavQuery_ShortcutCorridor(mnavQuery* query, mnavCorridor* corridor, + const mnavNavmesh* navmesh, mnavPos3 toward, + const mnavQueryFilter* filter, bool* shortenedOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -725,10 +816,17 @@ static MNAV_NOINLINE mnavResult ReplanCorridorWork(mnavQuery* query, const mnavN { return mnav_errorBusy; } - if (query == nullptr || navmesh == nullptr || pathOut == nullptr || !WellFormed(corridor)) + // The box is checked though no end may need it, so that a bad one is + // refused whatever the state of the corridor's polygons. + if (query == nullptr || navmesh == nullptr || pathOut == nullptr || !WellFormed(corridor) || + !mnavGoodHalfExtents(halfExtents)) { return mnav_errorInvalid; } + if (!InReach(navmesh, corridor)) + { + return mnav_errorLimit; + } mnavPos3 start = corridor->position; mnavPos3 end = corridor->target; mnavNearest from; @@ -737,7 +835,7 @@ static MNAV_NOINLINE mnavResult ReplanCorridorWork(mnavQuery* query, const mnavN EndPolygon(navmesh, filter, corridor->polygons[0], &start, halfExtents, &from); if (result == mnav_success) { - result = EndPolygon(navmesh, filter, corridor->polygons[corridor->count - 1], &end, + result = EndPolygon(navmesh, filter, corridor->polygons[corridor->polygonCount - 1], &end, halfExtents, &to); } if (result != mnav_success) @@ -756,7 +854,10 @@ static MNAV_NOINLINE mnavResult ReplanCorridorWork(mnavQuery* query, const mnavN nullptr, 0, nullptr, + 0, + nullptr, 0}; + mnavKeepPath(query, pathOut); return mnav_success; } result = @@ -769,14 +870,18 @@ static MNAV_NOINLINE mnavResult ReplanCorridorCounted(mnavQuery* query, const mn mnavCorridor* corridor, mnavVec3 halfExtents, mnavPath* pathOut) { - return mnavHostCountInvalid( - mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - ReplanCorridorWork(query, navmesh, filter, corridor, halfExtents, pathOut)); + mnavPath pathMade = {0}; + mnavResult result = + mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + ReplanCorridorWork(query, navmesh, filter, corridor, halfExtents, + pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavReplanCorridor(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavCorridor* corridor, - mnavVec3 halfExtents, mnavPath* pathOut) +mnavResult mnavQuery_ReplanCorridor(mnavQuery* query, mnavCorridor* corridor, + const mnavNavmesh* navmesh, mnavVec3 halfExtents, + const mnavQueryFilter* filter, mnavPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = diff --git a/third_party/maul-nav/src/crowd.c b/third_party/maul-nav/src/crowd.c index ed955bb2e..7971ffa87 100644 --- a/third_party/maul-nav/src/crowd.c +++ b/third_party/maul-nav/src/crowd.c @@ -105,12 +105,12 @@ mnavCrowdKey mnavCrowdKeyOf(const mnavCrowd* crowd, mnavPos3 position, uint64_t CellOf(position.x, size), CellOf(position.y, size), z, id, index, position, 0}; } -static bool SameColumn(const mnavCrowdKey* a, const mnavCrowdKey* b) +MNAV_NODISCARD static bool SameColumn(const mnavCrowdKey* a, const mnavCrowdKey* b) { return a->x == b->x && a->y == b->y; } -static bool KeyBefore(const mnavCrowdKey* a, const mnavCrowdKey* b) +MNAV_NODISCARD static bool KeyBefore(const mnavCrowdKey* a, const mnavCrowdKey* b) { if (a->x != b->x) { @@ -208,7 +208,7 @@ static mnavCrowdRun RunIn(const mnavCrowd* crowd, int64_t x, int64_t y) return (mnavCrowdRun){0, 0}; } -static bool NeighborBefore(const mnavCrowdNeighbor* a, const mnavCrowdNeighbor* b) +MNAV_NODISCARD static bool NeighborBefore(const mnavCrowdNeighbor* a, const mnavCrowdNeighbor* b) { if (a->distance != b->distance) { diff --git a/third_party/maul-nav/src/debug.c b/third_party/maul-nav/src/debug.c index f76399529..4454db153 100644 --- a/third_party/maul-nav/src/debug.c +++ b/third_party/maul-nav/src/debug.c @@ -27,7 +27,7 @@ // A polygon's detail triangles, of a kind and value. static void Fill(mnavDebugBuffer* buffer, const mnavNavmesh* navmesh, int32_t slot, int32_t p, - mnavDebugKind kind, uint16_t value) + mnavDebugKind kind, uint32_t value) { const mnavTile* tile = navmesh->slots[slot].tile; const mnavDetailPart* part = &tile->detail.parts[p]; @@ -43,7 +43,7 @@ static void Fill(mnavDebugBuffer* buffer, const mnavNavmesh* navmesh, int32_t sl } // Whether tile links join edge j of polygon p. -static bool Linked(const mnavTile* tile, int32_t p, int32_t j) +MNAV_NODISCARD static bool Linked(const mnavTile* tile, int32_t p, int32_t j) { for (int32_t l = tile->firstLink[p]; l < tile->firstLink[p + 1]; ++l) { @@ -99,7 +99,7 @@ static void Bounds(mnavDebugBuffer* buffer, const mnavNavmesh* navmesh, int32_t } low = isfinite(low) ? low : 0.0; mnavFrame f = mnavFrameOf(navmesh, s->x, s->z); - double side = (double)navmesh->def.tileCells * f.cell; + double side = (double)navmesh->def.bake.tileCells * f.cell; mnavPos3 c[4] = {{f.x0, low, f.z0}, {f.x0 + side, low, f.z0}, {f.x0 + side, low, f.z0 + side}, @@ -110,18 +110,20 @@ static void Bounds(mnavDebugBuffer* buffer, const mnavNavmesh* navmesh, int32_t } } -static MNAV_NOINLINE mnavResult DebugNavmeshWork(const mnavNavmesh* navmesh, int32_t tileX0, - int32_t tileZ0, int32_t tileX1, int32_t tileZ1, +static MNAV_NOINLINE mnavResult DebugNavmeshWork(const mnavNavmesh* navmesh, + const mnavTileRange* tiles, mnavDebugBuffer* buffer) { - if (navmesh == nullptr || !mnavGoodBuffer(buffer) || tileX1 < tileX0 || tileZ1 < tileZ0) + mnavTileRange r = + tiles != nullptr ? *tiles : (mnavTileRange){INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}; + if (navmesh == nullptr || !mnavGoodBuffer(buffer) || r.x1 < r.x0 || r.z1 < r.z0) { return mnav_errorInvalid; } for (int32_t i = 0; i < navmesh->placeCount; ++i) { const mnavPlace* place = &navmesh->places[i]; - if (place->x < tileX0 || place->x > tileX1 || place->z < tileZ0 || place->z > tileZ1) + if (place->x < r.x0 || place->x > r.x1 || place->z < r.z0 || place->z > r.z1) { continue; } @@ -136,19 +138,18 @@ static MNAV_NOINLINE mnavResult DebugNavmeshWork(const mnavNavmesh* navmesh, int return mnavDrawResult(buffer); } -static MNAV_NOINLINE mnavResult DebugNavmeshCounted(const mnavNavmesh* navmesh, int32_t tileX0, - int32_t tileZ0, int32_t tileX1, int32_t tileZ1, +static MNAV_NOINLINE mnavResult DebugNavmeshCounted(const mnavNavmesh* navmesh, + const mnavTileRange* tiles, mnavDebugBuffer* buffer) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), - DebugNavmeshWork(navmesh, tileX0, tileZ0, tileX1, tileZ1, buffer)); + return mnavHostCountInvalid(MNAV_HOST(navmesh), DebugNavmeshWork(navmesh, tiles, buffer)); } -mnavResult mnavDebugNavmesh(const mnavNavmesh* navmesh, int32_t tileX0, int32_t tileZ0, - int32_t tileX1, int32_t tileZ1, mnavDebugBuffer* buffer) +mnavResult mnavNavmesh_Debug(const mnavNavmesh* navmesh, const mnavTileRange* tiles, + mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); - mnavResult result = DebugNavmeshCounted(navmesh, tileX0, tileZ0, tileX1, tileZ1, buffer); + mnavResult result = DebugNavmeshCounted(navmesh, tiles, buffer); mnavFloatEnvLeave(env); return result; } @@ -174,8 +175,8 @@ static MNAV_NOINLINE mnavResult DebugLinksWork(const mnavNavmesh* navmesh, mnavD double dz = b.z - a.z; double rise = sqrt(dx * dx + dy * dy + dz * dz) * 0.25; mnavDebugKind kind = link->def.twoWay ? mnav_debugLinkBothWays : mnav_debugLink; - uint16_t value = - (uint16_t)(link->def.kind + (state->attached && state->enabled ? 0 : LINK_OFF)); + uint32_t value = + (uint32_t)link->def.kind + (state->attached && state->enabled ? 0u : LINK_OFF); mnavPos3 last = a; for (int32_t k = 1; k <= ARC_LINES; ++k) { @@ -194,7 +195,7 @@ static MNAV_NOINLINE mnavResult DebugLinksCounted(const mnavNavmesh* navmesh, return mnavHostCountInvalid(MNAV_HOST(navmesh), DebugLinksWork(navmesh, buffer)); } -mnavResult mnavDebugLinks(const mnavNavmesh* navmesh, mnavDebugBuffer* buffer) +mnavResult mnavNavmesh_DebugLinks(const mnavNavmesh* navmesh, mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = DebugLinksCounted(navmesh, buffer); @@ -204,8 +205,9 @@ mnavResult mnavDebugLinks(const mnavNavmesh* navmesh, mnavDebugBuffer* buffer) static MNAV_NOINLINE mnavResult DebugPathWork(const mnavPath* path, mnavDebugBuffer* buffer) { - if (path == nullptr || !mnavGoodBuffer(buffer) || path->pointCount < 0 || - (path->pointCount > 0 && path->points == nullptr)) + // The points may be the caller's own: only the count given is read. + if (path == nullptr || !mnavGoodBuffer(buffer) || path->pointCount < 1 || + path->points == nullptr || !mnavGoodPoints(path->points, path->pointCount)) { return mnav_errorInvalid; } @@ -233,17 +235,21 @@ static MNAV_NOINLINE mnavResult DebugCorridorWork(const mnavNavmesh* navmesh, { return mnav_errorInvalid; } + // Every id is checked before the first fill, so a refused corridor + // leaves the buffer as it was. for (int32_t i = 0; i < count; ++i) { - mnavResult result = mnavCheckPolygon(navmesh, polygons[i]); - if (result == mnav_errorInvalid) + if (mnavCheckPolygon(navmesh, polygons[i]) == mnav_errorInvalid) { - return result; + return mnav_errorInvalid; } - if (result == mnav_success) + } + for (int32_t i = 0; i < count; ++i) + { + if (mnavCheckPolygon(navmesh, polygons[i]) == mnav_success) { Fill(buffer, navmesh, (int32_t)polygons[i].slot - 1, (int32_t)polygons[i].polygon, - mnav_debugCorridor, (uint16_t)(i & 0xFFFF)); + mnav_debugCorridor, (uint32_t)i); } } return mnavDrawResult(buffer); @@ -257,8 +263,8 @@ static MNAV_NOINLINE mnavResult DebugCorridorCounted(const mnavNavmesh* navmesh, DebugCorridorWork(navmesh, polygons, count, buffer)); } -mnavResult mnavDebugCorridor(const mnavNavmesh* navmesh, const mnavPolygonId* polygons, - int32_t count, mnavDebugBuffer* buffer) +mnavResult mnavNavmesh_DebugCorridor(const mnavNavmesh* navmesh, const mnavPolygonId* polygons, + int32_t count, mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = DebugCorridorCounted(navmesh, polygons, count, buffer); diff --git a/third_party/maul-nav/src/delaunay.c b/third_party/maul-nav/src/delaunay.c index 463d71ae4..4a2bda7d5 100644 --- a/third_party/maul-nav/src/delaunay.c +++ b/third_party/maul-nav/src/delaunay.c @@ -7,6 +7,8 @@ #include "invariant.h" +#include "maul-nav/base.h" + #include int64_t mnavDetailArea2(const mnavDetailVertex* a, const mnavDetailVertex* b, @@ -17,8 +19,8 @@ int64_t mnavDetailArea2(const mnavDetailVertex* a, const mnavDetailVertex* b, // Whether d lies strictly inside the circumcircle of (a, b, c), which is // wound negatively. Coordinates up to 2^14 keep every term within 2^60. -static bool InCircle(const mnavDetailVertex* a, const mnavDetailVertex* b, - const mnavDetailVertex* c, const mnavDetailVertex* d) +MNAV_NODISCARD static bool InCircle(const mnavDetailVertex* a, const mnavDetailVertex* b, + const mnavDetailVertex* c, const mnavDetailVertex* d) { int64_t adx = a->x - d->x; int64_t adz = a->z - d->z; diff --git a/third_party/maul-nav/src/detail.c b/third_party/maul-nav/src/detail.c index 8ebdbb2eb..e74d093b9 100644 --- a/third_party/maul-nav/src/detail.c +++ b/third_party/maul-nav/src/detail.c @@ -23,23 +23,23 @@ // Sixteenths of a cell per cell. #define SUBCELLS 16 // The most points on one edge: its two ends and the samples between. -#define EDGE_POINTS (MNAV_DETAIL_EDGE_SAMPLES + 2) -#define PART_TRIANGLES (2 * MNAV_DETAIL_VERTICES) +#define EDGE_POINTS (MNAV_MAX_DETAIL_EDGE_SAMPLES + 2) +#define PART_TRIANGLES (2 * MNAV_MAX_DETAIL_VERTICES) // One polygon's detail while it is built, and the triangulation's scratch. typedef struct Part { - mnavDetailVertex vertices[MNAV_DETAIL_VERTICES]; + mnavDetailVertex vertices[MNAV_MAX_DETAIL_VERTICES]; int32_t vertexCount; // The outline, as indices into vertices in polygon order. - uint8_t hull[MNAV_DETAIL_VERTICES]; + uint8_t hull[MNAV_MAX_DETAIL_VERTICES]; int32_t hullCount; mnavDetailTriangle triangles[PART_TRIANGLES]; int32_t triangleCount; - mnavContourVertex ring[MNAV_DETAIL_VERTICES]; - int32_t indices[MNAV_DETAIL_VERTICES]; - uint8_t ears[MNAV_DETAIL_VERTICES]; - int32_t corners[3 * MNAV_DETAIL_VERTICES]; + mnavContourVertex ring[MNAV_MAX_DETAIL_VERTICES]; + int32_t indices[MNAV_MAX_DETAIL_VERTICES]; + uint8_t ears[MNAV_MAX_DETAIL_VERTICES]; + int32_t corners[3 * MNAV_MAX_DETAIL_VERTICES]; // An edge's points and those kept. mnavDetailVertex points[EDGE_POINTS]; int32_t kept[EDGE_POINTS]; @@ -125,14 +125,34 @@ static int64_t Divisor(int64_t a, int64_t b) return a; } +// The most steps of an edge's whole sixteenths between two neighboring +// points when the edge's steps are cut in segments, each point at the +// step nearest its even place. +static int64_t WidestGap(int64_t steps, int64_t segments) +{ + int64_t widest = 0; + int64_t last = 0; + for (int64_t k = 1; k <= segments; ++k) + { + int64_t step = RoundDivide(steps * k, segments); + widest = step - last > widest ? step - last : widest; + last = step; + } + return widest; +} + // Places the points of the edge from a to b at most a sample distance -// apart, at most MNAV_DETAIL_EDGE_SAMPLES between the ends, with their +// apart, at most MNAV_MAX_DETAIL_EDGE_SAMPLES between the ends, with their // heights. Each point is the whole sixteenth on the edge nearest its even // place, so the outline runs exactly along the polygon's edges and stays -// a simple ring however sharp a corner is; an edge with fewer such -// sixteenths than segments takes each of them. -// Returns the number of segments. -static int32_t PlacePoints(Builder* builder, const mnavDetailVertex* a, const mnavDetailVertex* b) +// a simple ring however sharp a corner is; where snapping to them widens +// a gap past the distance, the edge takes another segment, and an edge +// with fewer such sixteenths than segments takes each of them. +// Returns the number of segments, and whether two neighboring points still +// lie farther apart than the sample distance, past the cap or for lack of +// whole sixteenths. +static int32_t PlacePoints(Builder* builder, const mnavDetailVertex* a, const mnavDetailVertex* b, + bool* coarse) { Part* part = builder->part; int64_t dx = b->x - a->x; @@ -148,6 +168,7 @@ static int32_t PlacePoints(Builder* builder, const mnavDetailVertex* a, const mn // still see a positive count there. steps = steps > 0 ? steps : 1; int64_t segments = 1; + *coarse = false; int64_t sample = builder->settings.sample; if (sample > 0) { @@ -157,8 +178,20 @@ static int32_t PlacePoints(Builder* builder, const mnavDetailVertex* a, const mn segments = (SquareRoot(length2) + sample - 1) / sample; segments += segments * sample * segments * sample < length2 ? 1 : 0; segments = segments < 1 ? 1 : segments; - segments = segments > EDGE_POINTS - 1 ? EDGE_POINTS - 1 : segments; - segments = segments > steps ? steps : segments; + int64_t most = EDGE_POINTS - 1 < steps ? EDGE_POINTS - 1 : steps; + segments = segments > most ? most : segments; + // A step's length squared: the edge's run and rise over its steps + // are whole. + int64_t unit2 = (dx / steps) * (dx / steps) + (dz / steps) * (dz / steps); + while (segments < most) + { + int64_t widest = WidestGap(steps, segments); + if (widest * widest * unit2 <= sample * sample) + { + break; + } + segments += 1; + } } for (int64_t k = 0; k <= segments; ++k) { @@ -169,6 +202,12 @@ static int32_t PlacePoints(Builder* builder, const mnavDetailVertex* a, const mn int32_t reference = a->y + (int32_t)RoundDivide(dy * k, segments); bool end = k == 0 || k == segments; p->y = end ? reference : Height(builder, p->x, p->z, reference); + if (k > 0 && sample > 0) + { + int64_t gx = p->x - part->points[k - 1].x; + int64_t gz = p->z - part->points[k - 1].z; + *coarse = *coarse || gx * gx + gz * gz > sample * sample; + } } return (int32_t)segments; } @@ -215,16 +254,19 @@ static int32_t Simplify(const Part* part, int32_t segments, int32_t error, int32 } // Whether a comes after b in lexical (x, z) order. -static bool After(const mnavDetailVertex* a, const mnavDetailVertex* b) +MNAV_NODISCARD static bool After(const mnavDetailVertex* a, const mnavDetailVertex* b) { return a->x != b->x ? a->x > b->x : a->z > b->z; } -// Adds the polygon's vertices and, edge by edge, the samples kept on it, +// Adds polygon p's vertices and, edge by edge, the samples kept on it, // building the outline in polygon order. Each edge is sampled from its -// lexically first end, so both polygons sharing it get the same points. -static void BuildOutline(Builder* builder, const mnavPolygon* polygon) +// lexically first end, so both polygons sharing it get the same points; +// an edge sampled more widely than the sample distance is counted by the +// lower of the two. +static void BuildOutline(Builder* builder, int32_t p) { + const mnavPolygon* polygon = &builder->mesh->polygons[p]; Part* part = builder->part; int32_t n = polygon->count; for (int32_t k = 0; k < n; ++k) @@ -240,7 +282,13 @@ static void BuildOutline(Builder* builder, const mnavPolygon* polygon) bool swapped = After(&part->vertices[j], &part->vertices[i]); const mnavDetailVertex* a = &part->vertices[swapped ? i : j]; const mnavDetailVertex* b = &part->vertices[swapped ? j : i]; - int32_t segments = PlacePoints(builder, a, b); + bool coarse = false; + int32_t segments = PlacePoints(builder, a, b, &coarse); + uint16_t across = polygon->neighbors[j]; + if (coarse && (across == MNAV_NO_INDEX || across > p)) + { + builder->detail->coarseEdges += 1; + } int32_t kept = Simplify(part, segments, builder->settings.error, part->kept); part->hull[part->hullCount++] = (uint8_t)j; for (int32_t k = 1; k < kept - 1; ++k) @@ -285,7 +333,8 @@ static void TriangulateOutline(Builder* builder) // Whether p lies inside the polygon's first n vertices at least half a // sample distance from every edge: strictly on each edge's inner side, // with 4 area^2 at least sample^2 length^2. -static bool WellInside(const Part* part, int32_t n, const mnavDetailVertex* p, int64_t sample) +MNAV_NODISCARD static bool WellInside(const Part* part, int32_t n, const mnavDetailVertex* p, + int64_t sample) { for (int32_t j = n - 1, i = 0; i < n; j = i++) { @@ -421,7 +470,7 @@ static void AddInterior(Builder* builder) } for (int32_t s = Worst(builder); s >= 0; s = Worst(builder)) { - if (part->vertexCount >= MNAV_DETAIL_VERTICES) + if (part->vertexCount >= MNAV_MAX_DETAIL_VERTICES) { builder->detail->cappedPolygons += 1; break; @@ -464,7 +513,7 @@ static mnavResult Store(Builder* builder, int32_t p) return result; } detail->parts[p] = (mnavDetailPart){detail->vertexCount, detail->triangleCount, - (uint8_t)part->vertexCount, (uint8_t)part->triangleCount}; + (uint8_t)part->vertexCount, (uint16_t)part->triangleCount}; memcpy(detail->vertices + detail->vertexCount, part->vertices, (size_t)part->vertexCount * sizeof(mnavDetailVertex)); memcpy(detail->triangles + detail->triangleCount, part->triangles, @@ -486,7 +535,7 @@ static mnavResult BuildPart(Builder* builder, int32_t p) { return result; } - BuildOutline(builder, &builder->mesh->polygons[p]); + BuildOutline(builder, p); TriangulateOutline(builder); result = Collect(builder, builder->mesh->polygons[p].count); if (result != mnav_success) @@ -499,7 +548,7 @@ static mnavResult BuildPart(Builder* builder, int32_t p) // One part per polygon, the parts end to end over the vertices and the // triangles, each with its polygon's vertices and at most -// MNAV_DETAIL_VERTICES, and every triangle's corners three distinct +// MNAV_MAX_DETAIL_VERTICES, and every triangle's corners three distinct // vertices of its part. static void CheckDetailMesh(const mnavPolyMesh* mesh, const mnavDetailMesh* detail) { @@ -511,7 +560,7 @@ static void CheckDetailMesh(const mnavPolyMesh* mesh, const mnavDetailMesh* deta const mnavDetailPart* part = &detail->parts[p]; MNAV_ASSERT(part->firstVertex == vertices && part->firstTriangle == triangles); MNAV_ASSERT(part->vertexCount >= mesh->polygons[p].count && - part->vertexCount <= MNAV_DETAIL_VERTICES); + part->vertexCount <= MNAV_MAX_DETAIL_VERTICES); for (int32_t t = 0; t < part->triangleCount; ++t) { const uint8_t* c = detail->triangles[part->firstTriangle + t].corners; diff --git a/third_party/maul-nav/src/detail.h b/third_party/maul-nav/src/detail.h index 94ba1b548..da1e787fb 100644 --- a/third_party/maul-nav/src/detail.h +++ b/third_party/maul-nav/src/detail.h @@ -18,12 +18,6 @@ #include -// The most detail vertices one polygon holds, and so at most 252 -// triangles. -#define MNAV_DETAIL_VERTICES 127 -// The most samples inside one polygon edge. -#define MNAV_DETAIL_EDGE_SAMPLES 20 - // A polygon's detail: its vertices (the polygon's own first) and // triangles. typedef struct mnavDetailPart @@ -31,7 +25,7 @@ typedef struct mnavDetailPart int32_t firstVertex; int32_t firstTriangle; uint8_t vertexCount; - uint8_t triangleCount; + uint16_t triangleCount; } mnavDetailPart; typedef struct mnavDetailMesh @@ -47,9 +41,13 @@ typedef struct mnavDetailMesh // Samples that found no height within the search radius and took the // polygon's own. int32_t fallbackHeights; - // Polygons that reached MNAV_DETAIL_VERTICES with samples still + // Polygons that reached MNAV_MAX_DETAIL_VERTICES with samples still // beyond the maximum error. int32_t cappedPolygons; + // Edges, each counted once, with two neighboring points farther apart + // than the sample distance, which only MNAV_MAX_DETAIL_EDGE_SAMPLES or + // too few whole sixteenths on the edge leave. + int32_t coarseEdges; } mnavDetailMesh; // What the detail mesh samples, from the bake's cells: the sample diff --git a/third_party/maul-nav/src/draw.c b/third_party/maul-nav/src/draw.c index 70e33a65e..1dc9f77e2 100644 --- a/third_party/maul-nav/src/draw.c +++ b/third_party/maul-nav/src/draw.c @@ -10,12 +10,13 @@ #include #include +#include #include // The most a list may hold: indices stay within 32 unsigned bits. #define MOST INT32_MAX -static bool GoodList(const void* items, int32_t capacity, int32_t count) +MNAV_NODISCARD static bool GoodList(const void* items, int32_t capacity, int32_t count) { return capacity >= 0 && count >= 0 && (capacity == 0 || items != nullptr); } @@ -29,7 +30,19 @@ bool mnavGoodBuffer(const mnavDebugBuffer* buffer) GoodList(buffer->lines, buffer->lineCapacity, buffer->lineCount); } -uint32_t mnavDrawVertex(mnavDebugBuffer* buffer, mnavPos3 p, mnavDebugKind kind, uint16_t value) +bool mnavGoodPoints(const mnavPos3* points, int32_t count) +{ + for (int32_t i = 0; i < count; ++i) + { + if (!isfinite(points[i].x) || !isfinite(points[i].y) || !isfinite(points[i].z)) + { + return false; + } + } + return true; +} + +uint32_t mnavDrawVertex(mnavDebugBuffer* buffer, mnavPos3 p, mnavDebugKind kind, uint32_t value) { int32_t at = buffer->vertexCount; if (at < buffer->vertexCapacity) @@ -42,34 +55,35 @@ uint32_t mnavDrawVertex(mnavDebugBuffer* buffer, mnavPos3 p, mnavDebugKind kind, return (uint32_t)at; } -// Appends an index to a list. -static void Index(uint32_t* list, int32_t capacity, int32_t* count, uint32_t index) +// Appends an item of n indices to a list counted in items. +static void Append(uint32_t* list, int32_t capacity, int32_t* count, const uint32_t* indices, + int32_t n) { if (*count < capacity) { - list[*count] = index; + for (int32_t k = 0; k < n; ++k) + { + list[(size_t)*count * (size_t)n + (size_t)k] = indices[k]; + } } *count = *count < MOST ? *count + 1 : *count; } void mnavDrawLine(mnavDebugBuffer* buffer, mnavPos3 a, mnavPos3 b, mnavDebugKind kind, - uint16_t value) + uint32_t value) { - uint32_t ia = mnavDrawVertex(buffer, a, kind, value); - uint32_t ib = mnavDrawVertex(buffer, b, kind, value); - Index(buffer->lines, buffer->lineCapacity, &buffer->lineCount, ia); - Index(buffer->lines, buffer->lineCapacity, &buffer->lineCount, ib); + uint32_t ends[2] = {mnavDrawVertex(buffer, a, kind, value), + mnavDrawVertex(buffer, b, kind, value)}; + Append(buffer->lines, buffer->lineCapacity, &buffer->lineCount, ends, 2); } void mnavDrawTriangle(mnavDebugBuffer* buffer, mnavPos3 a, mnavPos3 b, mnavPos3 c, - mnavDebugKind kind, uint16_t value) + mnavDebugKind kind, uint32_t value) { - uint32_t ia = mnavDrawVertex(buffer, a, kind, value); - uint32_t ib = mnavDrawVertex(buffer, b, kind, value); - uint32_t ic = mnavDrawVertex(buffer, c, kind, value); - Index(buffer->triangles, buffer->triangleCapacity, &buffer->triangleCount, ia); - Index(buffer->triangles, buffer->triangleCapacity, &buffer->triangleCount, ib); - Index(buffer->triangles, buffer->triangleCapacity, &buffer->triangleCount, ic); + uint32_t corners[3] = {mnavDrawVertex(buffer, a, kind, value), + mnavDrawVertex(buffer, b, kind, value), + mnavDrawVertex(buffer, c, kind, value)}; + Append(buffer->triangles, buffer->triangleCapacity, &buffer->triangleCount, corners, 3); } mnavResult mnavDrawResult(const mnavDebugBuffer* buffer) diff --git a/third_party/maul-nav/src/draw.h b/third_party/maul-nav/src/draw.h index ca66472f9..94520335f 100644 --- a/third_party/maul-nav/src/draw.h +++ b/third_party/maul-nav/src/draw.h @@ -15,16 +15,19 @@ // Whether a buffer is usable: counts within 0 and their capacities, // arrays present where a capacity asks for them, an origin finite. -bool mnavGoodBuffer(const mnavDebugBuffer* buffer); +MNAV_NODISCARD bool mnavGoodBuffer(const mnavDebugBuffer* buffer); + +// Whether every one of count points is finite. +MNAV_NODISCARD bool mnavGoodPoints(const mnavPos3* points, int32_t count); // Appends a vertex and returns its index. -uint32_t mnavDrawVertex(mnavDebugBuffer* buffer, mnavPos3 p, mnavDebugKind kind, uint16_t value); +uint32_t mnavDrawVertex(mnavDebugBuffer* buffer, mnavPos3 p, mnavDebugKind kind, uint32_t value); // Appends a line, and a triangle, with vertices of their own. void mnavDrawLine(mnavDebugBuffer* buffer, mnavPos3 a, mnavPos3 b, mnavDebugKind kind, - uint16_t value); + uint32_t value); void mnavDrawTriangle(mnavDebugBuffer* buffer, mnavPos3 a, mnavPos3 b, mnavPos3 c, - mnavDebugKind kind, uint16_t value); + mnavDebugKind kind, uint32_t value); // mnav_errorCapacity when any count went past its capacity. mnavResult mnavDrawResult(const mnavDebugBuffer* buffer); diff --git a/third_party/maul-nav/src/erode.c b/third_party/maul-nav/src/erode.c index a46b889c9..24aa08c9a 100644 --- a/third_party/maul-nav/src/erode.c +++ b/third_party/maul-nav/src/erode.c @@ -18,7 +18,8 @@ #define DIAGONAL 3 #define FAR 0xFFFF -static bool IsBoundary(const mnavCompactField* field, int32_t x, int32_t z, uint32_t i) +MNAV_NODISCARD static bool IsBoundary(const mnavCompactField* field, int32_t x, int32_t z, + uint32_t i) { if (field->areas[i] == mnav_areaNone) { diff --git a/third_party/maul-nav/src/filter.c b/third_party/maul-nav/src/filter.c index 7c4abed4d..c2e2c0a18 100644 --- a/third_party/maul-nav/src/filter.c +++ b/third_party/maul-nav/src/filter.c @@ -65,8 +65,9 @@ static int32_t Max(int32_t a, int32_t b) // One neighbor column, n, of a span with floor and ceiling: true when it // drops more than a step into space the agent fits through; else widens // low to high with its floors within a step that the agent fits on. -static bool Drops(const mnavSpan* spans, const uint32_t* columns, int32_t n, int32_t floor, - int32_t ceiling, int32_t height, int32_t step, int32_t* low, int32_t* high) +MNAV_NODISCARD static bool Drops(const mnavSpan* spans, const uint32_t* columns, int32_t n, + int32_t floor, int32_t ceiling, int32_t height, int32_t step, + int32_t* low, int32_t* high) { uint32_t first = columns[n]; uint32_t last = columns[n + 1]; @@ -105,8 +106,8 @@ static bool Drops(const mnavSpan* spans, const uint32_t* columns, int32_t n, int // and only when none does are the floors in reach compared, so the order // of the neighbors cannot change the answer. Column c is not on the // tile's edge. -static bool IsLedge(const mnavHeightfield* heightfield, int32_t c, uint32_t i, uint32_t end, - int32_t height, int32_t step) +MNAV_NODISCARD static bool IsLedge(const mnavHeightfield* heightfield, int32_t c, uint32_t i, + uint32_t end, int32_t height, int32_t step) { const mnavSpan* spans = heightfield->spans; const uint32_t* columns = heightfield->columns; @@ -138,7 +139,7 @@ static int32_t DropsAlone(const mnavSpan* n, int32_t floor, int32_t height, int3 // Whether every one of the columns c and its four neighbors holds one // span. -static bool AllAlone(const uint32_t* columns, int32_t c, int32_t width) +MNAV_NODISCARD static bool AllAlone(const uint32_t* columns, int32_t c, int32_t width) { return columns[c + 1] == columns[c] + 1 && columns[c] == columns[c - 1] + 1 && columns[c + 2] == columns[c + 1] + 1 && diff --git a/third_party/maul-nav/src/fingerprint.c b/third_party/maul-nav/src/fingerprint.c index 38342bdef..7d549cd45 100644 --- a/third_party/maul-nav/src/fingerprint.c +++ b/third_party/maul-nav/src/fingerprint.c @@ -19,7 +19,7 @@ uint64_t mnavHashWords(uint64_t hash, const uint32_t* words, int32_t count) { - return mnavHash64(hash, words, count * (int32_t)sizeof(uint32_t)); + return mnavHash64(hash, words, count * (int64_t)sizeof(uint32_t)); } // Adds a triangle that reaches the tile, as rasterization picks it, to diff --git a/third_party/maul-nav/src/flight.c b/third_party/maul-nav/src/flight.c index 37ef27214..f355aa269 100644 --- a/third_party/maul-nav/src/flight.c +++ b/third_party/maul-nav/src/flight.c @@ -51,15 +51,15 @@ static void SetRange(const Grid* g, uint64_t* column, int32_t from, int32_t to) } } -static bool Get(const uint64_t* column, int32_t y) +MNAV_NODISCARD static bool Get(const uint64_t* column, int32_t y) { return (column[y / 64] >> (y % 64) & 1u) != 0; } // Marks the heightfield's spans, offset columns in, and below the lowest // span of each column with ground when asked. -static void Mark(const mnavHeightfield* hf, int32_t offset, int32_t floorVoxel, bool groundBelow, - const Grid* g) +static void Mark(const mnavHeightfield* hf, int32_t offset, int32_t floorVoxel, + bool enableGroundFill, const Grid* g) { for (int32_t z = 0; z < g->width; ++z) { @@ -76,7 +76,7 @@ static void Mark(const mnavHeightfield* hf, int32_t offset, int32_t floorVoxel, int32_t top = (int32_t)hf->spans[s].top - MNAV_HEIGHT_OFFSET - floorVoxel; SetRange(g, column, bottom, top); } - if (groundBelow && first < last) + if (enableGroundFill && first < last) { SetRange(g, column, 0, (int32_t)hf->spans[first].bottom - MNAV_HEIGHT_OFFSET - floorVoxel); @@ -362,7 +362,7 @@ typedef struct Build int32_t tileX; int32_t tileZ; int32_t floorVoxel; - bool groundBelow; + bool enableGroundFill; } Build; // The tile's voxels with a border of the radius's columns, dilated: the @@ -382,7 +382,8 @@ static mnavResult Voxels(mnavMemory* memory, const Build* b, Grid* out, int32_t* if (r == mnav_success) { *spansOut = hf.spanCount; - Mark(&hf, b->cells->border - b->cells->agentRadius, b->floorVoxel, b->groundBelow, &raw); + Mark(&hf, b->cells->border - b->cells->agentRadius, b->floorVoxel, b->enableGroundFill, + &raw); } mnavReleaseHeightfield(memory, &hf); r = r == mnav_success ? MakeGrid(memory, out) : r; @@ -486,7 +487,7 @@ mnavResult mnavBuildFlightTile(mnavMemory* memory, const mnavFlightDef* def, { return mnav_errorInvalid; } - const Build b = {&voxels, &cells, input, tileX, tileZ, t->floorVoxel, def->groundBelow}; + const Build b = {&voxels, &cells, input, tileX, tileZ, t->floorVoxel, def->enableGroundFill}; int32_t layers = t->cubeCount * t->side; Grid g = {nullptr, t->side + 2 * cells.agentRadius, layers, (layers + 63) / 64}; mnavResult r = Voxels(memory, &b, &g, spansOut); @@ -503,6 +504,15 @@ mnavResult mnavBuildFlightTile(mnavMemory* memory, const mnavFlightDef* def, return r; } +bool mnavFlightTileInExtent(const mnavFlightDef* def, int32_t tileX, int32_t tileZ) +{ + mnavBakeDef voxels = mnavFlightBakeDef(def); + mnavBakeCells cells; + mnavTileFrame frame; + return mnavValidateBakeDef(&voxels, &cells).result == mnav_success && + mnavMakeTileFrame(&voxels, &cells, tileX, tileZ, &frame); +} + // The bits set in a mask. static uint32_t Ones(uint32_t mask) { diff --git a/third_party/maul-nav/src/flight.h b/third_party/maul-nav/src/flight.h index b7d008914..8307f6b1d 100644 --- a/third_party/maul-nav/src/flight.h +++ b/third_party/maul-nav/src/flight.h @@ -63,9 +63,13 @@ mnavResult mnavBuildFlightTile(mnavMemory* memory, const mnavFlightDef* def, int32_t tileX, int32_t tileZ, mnavFlightTile* tileOut, int32_t* spansOut); +// Whether the bake writes tile (tileX, tileZ) with a valid def: its +// frame within the extent bake input may reach, as the baker checks it. +MNAV_NODISCARD bool mnavFlightTileInExtent(const mnavFlightDef* def, int32_t tileX, int32_t tileZ); + // Whether voxel (x, y, z) of a tile is solid: x and z from 0 to its side, // y from 0 to its cubes times its side, up from its floor voxel. -bool mnavFlightSolid(const mnavFlightTile* tile, int32_t x, int32_t y, int32_t z); +MNAV_NODISCARD bool mnavFlightSolid(const mnavFlightTile* tile, int32_t x, int32_t y, int32_t z); void mnavReleaseFlightTile(mnavMemory* memory, mnavFlightTile* tile); diff --git a/third_party/maul-nav/src/flight_baker.c b/third_party/maul-nav/src/flight_baker.c index cddf13475..195f52f52 100644 --- a/third_party/maul-nav/src/flight_baker.c +++ b/third_party/maul-nav/src/flight_baker.c @@ -14,6 +14,8 @@ #include "flight_tile.h" #include "fpenv.h" #include "host_code.h" +#include "input.h" +#include "outputs.h" #include "raster.h" #include "tile_index.h" @@ -87,7 +89,7 @@ static void DropTile(mnavFlightBaker* baker) baker->tileSize = 0; } -uint64_t mnavGetFlightBakerMisuse(const mnavFlightBaker* baker) +uint64_t mnavFlightBaker_GetMisuse(const mnavFlightBaker* baker) { return baker != nullptr ? mnavHostMisuse(&baker->host) : 0; } @@ -128,7 +130,7 @@ static uint64_t HashSettings(const mnavFlightDef* def, int32_t tileX, int32_t ti mnavFloatBits(def->floor), mnavFloatBits(def->ceiling), mnavFloatBits(def->radius), - def->groundBelow ? 1u : 0u, + def->enableGroundFill ? 1u : 0u, (uint32_t)tileX, (uint32_t)tileZ, }; @@ -142,8 +144,7 @@ static mnavResult Bake(mnavFlightBaker* baker, const mnavBakeInput* input, int32 { return mnav_errorInvalid; } - mnavResult result = - mnavCheckSolidInput(&baker->voxels, input, false, &report->mesh, &report->input); + mnavResult result = mnavCheckSolidInput(&baker->voxels, input, false, &report->input); mnavTileFrame frame = {0}; if (result == mnav_success && !mnavMakeTileFrame(&baker->voxels, &baker->cells, tileX, tileZ, &frame)) @@ -158,15 +159,15 @@ static mnavResult Bake(mnavFlightBaker* baker, const mnavBakeInput* input, int32 all.index = nullptr; report->fingerprint = mnavFingerprintInput(&frame, baker->cells.cosMaxSlope, &all, tileX, tileZ, - HashSettings(&baker->def, tileX, tileZ), &report->triangles); + HashSettings(&baker->def, tileX, tileZ), &report->triangleCount); mnavFlightTile tile = {0}; result = mnavBuildFlightTile(&baker->memory, &baker->def, &baker->shape, input, tileX, tileZ, - &tile, &report->spans); + &tile, &report->spanCount); if (result == mnav_success) { - report->cubes = tile.cubeCount; - report->nodes = tile.nodeCount; - report->leaves = tile.leafCount; + report->cubeCount = tile.cubeCount; + report->nodeCount = tile.nodeCount; + report->leafCount = tile.leafCount; result = mnavEncodeFlightTile(&baker->memory, &baker->def, report->fingerprint, &tile, &baker->tile, &baker->tileSize); } @@ -183,8 +184,7 @@ static MNAV_NOINLINE mnavResult BakeFlightTileWork(mnavFlightBaker* baker, return mnav_errorBusy; } mnavFlightBakeReport report = {0}; - report.mesh = -1; - report.input = (mnavInputResult){mnav_success, mnav_elementNone, -1}; + report.input = mnavInputElementResult(mnav_success, mnav_elementNone, -1); mnavResult result = mnav_errorInvalid; if (baker != nullptr && input != nullptr) { @@ -208,8 +208,8 @@ static MNAV_NOINLINE mnavResult BakeFlightTileWork(mnavFlightBaker* baker, : result; } -mnavResult mnavBakeFlightTile(mnavFlightBaker* baker, const mnavBakeInput* input, int32_t tileX, - int32_t tileZ, mnavFlightBakeReport* reportOut) +mnavResult mnavFlightBaker_BakeTile(mnavFlightBaker* baker, const mnavBakeInput* input, + int32_t tileX, int32_t tileZ, mnavFlightBakeReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BakeFlightTileWork(baker, input, tileX, tileZ, reportOut); @@ -220,10 +220,14 @@ mnavResult mnavBakeFlightTile(mnavFlightBaker* baker, const mnavBakeInput* input static mnavResult CopyFlightTileWork(const mnavFlightBaker* baker, uint8_t* bufferOut, size_t capacity, size_t* sizeOut) { - if (baker == nullptr || (bufferOut == nullptr && capacity > 0) || baker->tile == nullptr) + if (baker == nullptr || (bufferOut == nullptr && capacity > 0)) { return mnav_errorInvalid; } + if (baker->tile == nullptr) + { + return mnav_errorState; + } if (sizeOut != nullptr) { *sizeOut = baker->tileSize; @@ -239,9 +243,13 @@ static mnavResult CopyFlightTileWork(const mnavFlightBaker* baker, uint8_t* buff return mnav_success; } -mnavResult mnavCopyFlightTile(const mnavFlightBaker* baker, uint8_t* bufferOut, size_t capacity, - size_t* sizeOut) +mnavResult mnavFlightBaker_CopyTile(const mnavFlightBaker* baker, uint8_t* bufferOut, + size_t capacity, size_t* sizeOut) { - return mnavHostCountInvalid(MNAV_HOST(baker), - CopyFlightTileWork(baker, bufferOut, capacity, sizeOut)); + mnavClearOutput(sizeOut, sizeof(*sizeOut)); + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(baker), CopyFlightTileWork(baker, bufferOut, capacity, sizeOut)); + // A buffer too small for the tile gets none of it. + mnavClearArray(result, false, bufferOut, mnavByteCapacity(capacity), 1); + return mnavClearUnlessCapacity(result, sizeOut, sizeof(*sizeOut)); } diff --git a/third_party/maul-nav/src/flight_debug.c b/third_party/maul-nav/src/flight_debug.c index 7e831aafd..9d5f36d3c 100644 --- a/third_party/maul-nav/src/flight_debug.c +++ b/third_party/maul-nav/src/flight_debug.c @@ -18,7 +18,7 @@ #include #include -static bool HasNaN(mnavPos3 p) +MNAV_NODISCARD static bool HasNaN(mnavPos3 p) { return isnan(p.x) || isnan(p.y) || isnan(p.z); } @@ -36,7 +36,7 @@ static void DrawBlock(mnavDebugBuffer* buffer, const mnavFlightFrame* frame, corner[c] = mnavFlightToWorld(frame, v); } // Corners differing in one bit share an edge. - uint16_t value = b->size < 65535 ? (uint16_t)b->size : (uint16_t)65535; + uint32_t value = (uint32_t)b->size; for (int32_t c = 0; c < 8; ++c) { for (int32_t bit = 1; bit < 8; bit *= 2) @@ -112,8 +112,8 @@ static MNAV_NOINLINE mnavResult DebugFlightCounted(const mnavFlightVolume* volum return mnavHostCountInvalid(MNAV_HOST(volume), DebugFlightWork(volume, low, high, buffer)); } -mnavResult mnavDebugFlight(const mnavFlightVolume* volume, mnavPos3 low, mnavPos3 high, - mnavDebugBuffer* buffer) +mnavResult mnavFlightVolume_Debug(const mnavFlightVolume* volume, mnavPos3 low, mnavPos3 high, + mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = DebugFlightCounted(volume, low, high, buffer); @@ -124,8 +124,9 @@ mnavResult mnavDebugFlight(const mnavFlightVolume* volume, mnavPos3 low, mnavPos static MNAV_NOINLINE mnavResult DebugFlightPathWork(const mnavFlightPath* path, mnavDebugBuffer* buffer) { - if (path == nullptr || !mnavGoodBuffer(buffer) || path->pointCount < 0 || - (path->pointCount > 0 && path->points == nullptr)) + // The points may be the caller's own: only the count given is read. + if (path == nullptr || !mnavGoodBuffer(buffer) || path->pointCount < 1 || + path->points == nullptr || !mnavGoodPoints(path->points, path->pointCount)) { return mnav_errorInvalid; } diff --git a/third_party/maul-nav/src/flight_def.c b/third_party/maul-nav/src/flight_def.c index d9b2597e9..1e2eecc95 100644 --- a/third_party/maul-nav/src/flight_def.c +++ b/third_party/maul-nav/src/flight_def.c @@ -37,7 +37,6 @@ static mnavFlightDefResult CheckLimits(const mnavFlightLimits* limits) {limits->tileSpans, MNAV_MAX_TILE_SPANS, mnav_flightSettingTileSpans}, {limits->tileNodes, MNAV_MAX_FLIGHT_TILE_NODES, mnav_flightSettingTileNodes}, {limits->tileLeaves, MNAV_MAX_FLIGHT_TILE_LEAVES, mnav_flightSettingTileLeaves}, - {limits->tiles, MNAV_MAX_TILES, mnav_flightSettingTiles}, }; for (size_t i = 0; i < sizeof(counts) / sizeof(counts[0]); ++i) { diff --git a/third_party/maul-nav/src/flight_frame.c b/third_party/maul-nav/src/flight_frame.c index 2c846e52b..0fef97d66 100644 --- a/third_party/maul-nav/src/flight_frame.c +++ b/third_party/maul-nav/src/flight_frame.c @@ -15,9 +15,9 @@ mnavFlightFrame mnavMakeFlightFrame(const mnavFlightVolume* volume) { - return (mnavFlightFrame){volume->def.origin, (double)volume->def.voxelSize, + return (mnavFlightFrame){volume->def.flight.origin, (double)volume->def.flight.voxelSize, volume->shape.floorVoxel, - volume->shape.cubeCount * volume->def.tileVoxels}; + volume->shape.cubeCount * volume->def.flight.tileVoxels}; } mnavResult mnavFlightToVoxels(const mnavFlightFrame* frame, mnavPos3 point, double out[3], diff --git a/third_party/maul-nav/src/flight_probe.c b/third_party/maul-nav/src/flight_probe.c index a39f88426..e4a83d3fb 100644 --- a/third_party/maul-nav/src/flight_probe.c +++ b/third_party/maul-nav/src/flight_probe.c @@ -8,6 +8,7 @@ #include "flight_space.h" #include "flight_volume.h" #include "fpenv.h" +#include "outputs.h" #include "maul-nav/base.h" #include "maul-nav/flight.h" @@ -56,11 +57,14 @@ static MNAV_NOINLINE mnavResult FlightRaycastWork(const mnavFlightVolume* volume static MNAV_NOINLINE mnavResult FlightRaycastCounted(const mnavFlightVolume* volume, mnavPos3 from, mnavPos3 to, mnavFlightHit* hitOut) { - return mnavHostCountInvalid(MNAV_HOST(volume), FlightRaycastWork(volume, from, to, hitOut)); + mnavClearOutput(hitOut, sizeof(*hitOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(volume), FlightRaycastWork(volume, from, to, hitOut)); + return mnavClearOnFailure(result, hitOut, sizeof(*hitOut)); } -mnavResult mnavFlightRaycast(const mnavFlightVolume* volume, mnavPos3 from, mnavPos3 to, - mnavFlightHit* hitOut) +mnavResult mnavFlightVolume_Raycast(const mnavFlightVolume* volume, mnavPos3 from, mnavPos3 to, + mnavFlightHit* hitOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FlightRaycastCounted(volume, from, to, hitOut); @@ -124,12 +128,16 @@ static MNAV_NOINLINE mnavResult CheckFlightPathCounted(const mnavFlightVolume* v const mnavPos3* points, int32_t pointCount, int32_t* stepOut, mnavFlightHit* hitOut) { - return mnavHostCountInvalid(MNAV_HOST(volume), - CheckFlightPathWork(volume, points, pointCount, stepOut, hitOut)); + mnavClearOutput(stepOut, sizeof(*stepOut)); + mnavClearOutput(hitOut, sizeof(*hitOut)); + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(volume), CheckFlightPathWork(volume, points, pointCount, stepOut, hitOut)); + return mnavClearOnFailure(mnavClearOnFailure(result, stepOut, sizeof(*stepOut)), hitOut, + sizeof(*hitOut)); } -mnavResult mnavCheckFlightPath(const mnavFlightVolume* volume, const mnavPos3* points, - int32_t pointCount, int32_t* stepOut, mnavFlightHit* hitOut) +mnavResult mnavFlightVolume_CheckPath(const mnavFlightVolume* volume, const mnavPos3* points, + int32_t pointCount, int32_t* stepOut, mnavFlightHit* hitOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = CheckFlightPathCounted(volume, points, pointCount, stepOut, hitOut); @@ -288,7 +296,6 @@ static MNAV_NOINLINE mnavResult FindNearestFlightPointWork(const mnavFlightVolum return mnav_errorInvalid; } *foundOut = false; - *nearestOut = point; mnavFlightFrame frame = mnavMakeFlightFrame(volume); Nearest n = {{0.0, 0.0, 0.0}, 0.0, {0.0, 0.0, 0.0}, false}; int32_t at[3]; @@ -305,6 +312,7 @@ static MNAV_NOINLINE mnavResult FindNearestFlightPointWork(const mnavFlightVolum if (mnavFlightHolder(&cursor, at[0], at[1], at[2], nullptr) == MNAV_SPACE_OPEN) { *foundOut = true; + *nearestOut = point; return mnav_success; } n.best = (double)radius / frame.voxel; @@ -324,12 +332,16 @@ static MNAV_NOINLINE mnavResult FindNearestFlightPointCounted(const mnavFlightVo mnavPos3 point, float radius, mnavPos3* nearestOut, bool* foundOut) { - return mnavHostCountInvalid( + mnavClearOutput(nearestOut, sizeof(*nearestOut)); + mnavClearOutput(foundOut, sizeof(*foundOut)); + mnavResult result = mnavHostCountInvalid( MNAV_HOST(volume), FindNearestFlightPointWork(volume, point, radius, nearestOut, foundOut)); + return mnavClearOnFailure(mnavClearOnFailure(result, nearestOut, sizeof(*nearestOut)), foundOut, + sizeof(*foundOut)); } -mnavResult mnavFindNearestFlightPoint(const mnavFlightVolume* volume, mnavPos3 point, float radius, - mnavPos3* nearestOut, bool* foundOut) +mnavResult mnavFlightVolume_FindNearestPoint(const mnavFlightVolume* volume, mnavPos3 point, + float radius, mnavPos3* nearestOut, bool* foundOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindNearestFlightPointCounted(volume, point, radius, nearestOut, foundOut); diff --git a/third_party/maul-nav/src/flight_search.c b/third_party/maul-nav/src/flight_search.c index 3f15901db..6ea48a7f8 100644 --- a/third_party/maul-nav/src/flight_search.c +++ b/third_party/maul-nav/src/flight_search.c @@ -10,6 +10,7 @@ #include "flight_volume.h" #include "fpenv.h" #include "invariant.h" +#include "outputs.h" #include "query.h" #include "maul-nav/base.h" @@ -52,12 +53,12 @@ static double Distance(const double a[3], const double b[3]) return sqrt(dx * dx + dy * dy + dz * dz); } -static bool SameBlock(const int32_t a[4], const int32_t b[4]) +MNAV_NODISCARD static bool SameBlock(const int32_t a[4], const int32_t b[4]) { return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; } -static bool Sooner(const Node* nodes, int32_t a, int32_t b) +MNAV_NODISCARD static bool Sooner(const Node* nodes, int32_t a, int32_t b) { double ta = nodes[a].cost + nodes[a].remaining; double tb = nodes[b].cost + nodes[b].remaining; @@ -137,7 +138,7 @@ static uint32_t Slot(const Search* s, const int32_t block[4]) } } -static bool InSight(Search* s, const double a[3], const double b[3]) +MNAV_NODISCARD static bool InSight(Search* s, const double a[3], const double b[3]) { return mnavFlightWalk(&s->cursor, a, b, nullptr) == MNAV_SPACE_OPEN; } @@ -343,13 +344,13 @@ static void Settle(Search* s, int32_t node) Around(s, node, Repair); } -static bool Nearer(const Node* nodes, int32_t a, int32_t b) +MNAV_NODISCARD static bool Nearer(const Node* nodes, int32_t a, int32_t b) { return b == MNAV_NO_NODE || nodes[a].remaining < nodes[b].remaining; } // Closes up to budget nodes; returns whether the search has ended. -static bool Run(Search* s, int32_t budget) +MNAV_NODISCARD static bool Run(Search* s, int32_t budget) { mnavQuery* q = s->query; for (int32_t closed = 0; closed < budget && q->heapCount > 0; ++closed) @@ -397,14 +398,15 @@ static mnavFlightPath PathTo(const Search* s, const mnavFlightFrame* frame, int3 points[i] = points[count - 1 - i]; points[count - 1 - i] = swap; } - return (mnavFlightPath){end, s->nodes[last].cost * frame->voxel, points, count}; + return (mnavFlightPath){end, s->nodes[last].cost * frame->voxel, + mnavKeepPoints(s->query, points, count), count}; } // The path when the search cannot begin: the start alone. static mnavFlightPath StartOnly(mnavQuery* query, mnavPos3 start, mnavPathEnd end) { query->points[0] = start; - return (mnavFlightPath){end, 0.0, query->points, 1}; + return (mnavFlightPath){end, 0.0, mnavKeepPoints(query, query->points, 1), 1}; } static mnavPathEnd Refused(int32_t held) @@ -456,7 +458,7 @@ static void SpreadTo(void* context, mnavFlightBlock block) // POCKET_BLOCKS (and the node budget) without the start's block or a face // with no tile loaded. No path reaches the end from outside it. Uses the // search's nodes and table, which Begin clears. -static bool Pocket(Search* s, const int32_t start[4]) +MNAV_NODISCARD static bool Pocket(Search* s, const int32_t start[4]) { mnavQuery* q = s->query; mnavClearTable(q); @@ -634,8 +636,8 @@ static MNAV_NOINLINE mnavResult BeginFlightPathCounted(mnavQuery* query, BeginFlightPathWork(query, volume, start, end)); } -mnavResult mnavBeginFlightPath(mnavQuery* query, const mnavFlightVolume* volume, mnavPos3 start, - mnavPos3 end) +mnavResult mnavQuery_BeginFlightPath(mnavQuery* query, const mnavFlightVolume* volume, + mnavPos3 start, mnavPos3 end) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BeginFlightPathCounted(query, volume, start, end); @@ -672,12 +674,16 @@ static MNAV_NOINLINE mnavResult ContinueFlightPathCounted(mnavQuery* query, const mnavFlightVolume* volume, int32_t nodes, bool* endedOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(volume)), - ContinueFlightPathWork(query, volume, nodes, endedOut)); + bool endedMade = false; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(volume)), + ContinueFlightPathWork(query, volume, nodes, endedOut != nullptr ? &endedMade : nullptr)); + mnavGiveOutput(result, false, endedOut, &endedMade, sizeof(endedMade)); + return result; } -mnavResult mnavContinueFlightPath(mnavQuery* query, const mnavFlightVolume* volume, int32_t nodes, - bool* endedOut) +mnavResult mnavQuery_ContinueFlightPath(mnavQuery* query, const mnavFlightVolume* volume, + int32_t nodes, bool* endedOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = ContinueFlightPathCounted(query, volume, nodes, endedOut); @@ -709,7 +715,8 @@ static MNAV_NOINLINE mnavResult FinishFlightPathWork(mnavQuery* query, { query->points[0] = f->start; query->points[1] = f->endPoint; - *pathOut = (mnavFlightPath){mnav_pathFound, f->straightLength, query->points, 2}; + *pathOut = (mnavFlightPath){mnav_pathFound, f->straightLength, + mnavKeepPoints(query, query->points, 2), 2}; return mnav_success; } Search s = Load(query); @@ -729,12 +736,16 @@ static MNAV_NOINLINE mnavResult FinishFlightPathCounted(mnavQuery* query, const mnavFlightVolume* volume, mnavFlightPath* pathOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(volume)), - FinishFlightPathWork(query, volume, pathOut)); + mnavFlightPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(volume)), + FinishFlightPathWork(query, volume, pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFinishFlightPath(mnavQuery* query, const mnavFlightVolume* volume, - mnavFlightPath* pathOut) +mnavResult mnavQuery_FinishFlightPath(mnavQuery* query, const mnavFlightVolume* volume, + mnavFlightPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FinishFlightPathCounted(query, volume, pathOut); @@ -768,12 +779,16 @@ static MNAV_NOINLINE mnavResult FindFlightPathCounted(mnavQuery* query, mnavPos3 start, mnavPos3 end, mnavFlightPath* pathOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(volume)), - FindFlightPathWork(query, volume, start, end, pathOut)); + mnavFlightPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(volume)), + FindFlightPathWork(query, volume, start, end, pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFindFlightPath(mnavQuery* query, const mnavFlightVolume* volume, mnavPos3 start, - mnavPos3 end, mnavFlightPath* pathOut) +mnavResult mnavQuery_FindFlightPath(mnavQuery* query, const mnavFlightVolume* volume, + mnavPos3 start, mnavPos3 end, mnavFlightPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindFlightPathCounted(query, volume, start, end, pathOut); diff --git a/third_party/maul-nav/src/flight_space.c b/third_party/maul-nav/src/flight_space.c index 2a7f7070a..f34de29bc 100644 --- a/third_party/maul-nav/src/flight_space.c +++ b/third_party/maul-nav/src/flight_space.c @@ -37,7 +37,7 @@ static uint32_t Ones(uint32_t mask) mnavFlightCursor mnavMakeFlightCursor(const mnavFlightVolume* volume) { - int32_t side = volume->def.tileVoxels; + int32_t side = volume->def.flight.tileVoxels; int32_t shift = 0; while (1 << shift < side) { @@ -68,7 +68,7 @@ static uint32_t Child(const mnavFlightNode* node, int32_t o) return node->firstChild + Ones(node->mask & ((1u << o) - 1u)); } -static bool Inside(const mnavFlightBlock* b, const int32_t at[3]) +MNAV_NODISCARD static bool Inside(const mnavFlightBlock* b, const int32_t at[3]) { return at[0] >= b->x && at[1] >= b->y && at[2] >= b->z && at[0] < b->x + b->size && at[1] < b->y + b->size && at[2] < b->z + b->size; @@ -127,7 +127,7 @@ static int32_t Hold(const mnavFlightTile* t, int32_t x, int32_t y, int32_t z, mn } // Whether a voxel of the cursor's leaf is solid. -static bool LeafSolid(const mnavFlightCursor* c, const int32_t at[3]) +MNAV_NODISCARD static bool LeafSolid(const mnavFlightCursor* c, const int32_t at[3]) { int32_t bit = (at[0] - c->leaf.x) + 4 * (at[1] - c->leaf.y) + 16 * (at[2] - c->leaf.z); return (c->leafBits >> bit & 1u) != 0; @@ -185,7 +185,7 @@ typedef struct Region void* context; } Region; -static bool Meets(const Region* r, int32_t x, int32_t y, int32_t z, int32_t size) +MNAV_NODISCARD static bool Meets(const Region* r, int32_t x, int32_t y, int32_t z, int32_t size) { return x < r->hi[0] && y < r->hi[1] && z < r->hi[2] && x + size > r->lo[0] && y + size > r->lo[1] && z + size > r->lo[2]; @@ -286,7 +286,7 @@ static int32_t Holding(const Region* r, int32_t x, int32_t y, int32_t z, int32_t // does instead. Above it a search meets one child a node, so starting it // there visits the same blocks in the same order. Returns whether a node // is left to search, in f. -static bool Descend(const mnavFlightTile* t, int32_t c, const Region* r, Frame* f) +MNAV_NODISCARD static bool Descend(const mnavFlightTile* t, int32_t c, const Region* r, Frame* f) { int32_t side = t->side; if (t->roots[c] != MNAV_FLIGHT_MIXED) @@ -507,8 +507,8 @@ static int32_t Advance(mnavFlightCursor* cursor, Walk* w, uint32_t tied, const i // Whether every voxel a step from at across the axes in tied touches, // each reached by a part of the steps, is an open voxel of the cursor's // leaf: at and to are in it, and so is every voxel between them. -static bool LeafClear(const mnavFlightCursor* c, const int32_t at[3], const int32_t step[3], - uint32_t tied, const int32_t to[3]) +MNAV_NODISCARD static bool LeafClear(const mnavFlightCursor* c, const int32_t at[3], + const int32_t step[3], uint32_t tied, const int32_t to[3]) { if (!Inside(&c->leaf, at) || !Inside(&c->leaf, to)) { @@ -623,8 +623,8 @@ static void Skip(Walk* w, const int32_t end[3]) // block. Returns false where the walk ends open, at the end's voxel or // past the segment's end, and true when its next step needs Advance and // Look. -static bool Within(const mnavFlightCursor* cursor, Walk* w, const int32_t end[3], uint32_t* tied, - int32_t to[3]) +MNAV_NODISCARD static bool Within(const mnavFlightCursor* cursor, Walk* w, const int32_t end[3], + uint32_t* tied, int32_t to[3]) { if (w->open.size >= 4) { diff --git a/third_party/maul-nav/src/flight_tile.c b/third_party/maul-nav/src/flight_tile.c index e93476b09..7f70456fa 100644 --- a/third_party/maul-nav/src/flight_tile.c +++ b/third_party/maul-nav/src/flight_tile.c @@ -65,7 +65,7 @@ static void WriteField(mnavByteWriter* w, int32_t field, const mnavFlightDef* de mnavPutU32(w, (uint32_t)tile->tileX); mnavPutU32(w, (uint32_t)tile->tileZ); mnavPutU16(w, (uint64_t)tile->side); - mnavPutU16(w, def->groundBelow ? FLAG_GROUND_BELOW : 0u); + mnavPutU16(w, def->enableGroundFill ? FLAG_GROUND_BELOW : 0u); mnavPutU32(w, mnavFloatBits(def->voxelSize)); mnavPutU32(w, mnavFloatBits(def->radius)); mnavPutU32(w, mnavFloatBits(def->floor)); @@ -130,7 +130,7 @@ mnavResult mnavEncodeFlightTile(mnavMemory* memory, const mnavFlightDef* def, ui } } MNAV_ASSERT(w.at == out + total); - uint64_t hash = mnavHash64(MNAV_HASH_INIT, out + MNAV_FLIGHT_HEADER_BYTES, (int32_t)payload); + uint64_t hash = mnavHash64(MNAV_HASH_INIT, out + MNAV_FLIGHT_HEADER_BYTES, (int64_t)payload); w.at = out; for (int32_t k = 0; k < 4; ++k) { @@ -163,10 +163,11 @@ static mnavTileResult Accept(void) } // Whether FLHT's settings are the def's and its shape the shape. -static bool SameSettings(mnavByteReader* r, const mnavFlightDef* def, const mnavFlightShape* shape) +MNAV_NODISCARD static bool SameSettings(mnavByteReader* r, const mnavFlightDef* def, + const mnavFlightShape* shape) { bool same = mnavGetU16(r) == (uint64_t)def->tileVoxels; - same &= mnavGetU16(r) == (def->groundBelow ? FLAG_GROUND_BELOW : 0u); + same &= mnavGetU16(r) == (def->enableGroundFill ? FLAG_GROUND_BELOW : 0u); same &= mnavGetU32(r) == mnavFloatBits(def->voxelSize); same &= mnavGetU32(r) == mnavFloatBits(def->radius); same &= mnavGetU32(r) == mnavFloatBits(def->floor); @@ -199,7 +200,7 @@ static mnavTileResult ReadHeader(const uint8_t* bytes, size_t size, const mnavFl size_t payload = (size_t)mnavGetU32(&r); uint64_t count = mnavGetU32(&r); if (payload != size - MNAV_FLIGHT_HEADER_BYTES || payload > INT32_MAX || - mnavHash64(MNAV_HASH_INIT, bytes + MNAV_FLIGHT_HEADER_BYTES, (int32_t)payload) != hash) + mnavHash64(MNAV_HASH_INIT, bytes + MNAV_FLIGHT_HEADER_BYTES, (int64_t)payload) != hash) { return Refuse(mnav_errorFormat, mnav_tilePayload, -1); } @@ -219,7 +220,9 @@ static mnavTileResult ReadHeader(const uint8_t* bytes, size_t size, const mnavFl r = (mnavByteReader){flight->bytes, flight->size, false}; tile->tileX = (int32_t)(uint32_t)mnavGetU32(&r); tile->tileZ = (int32_t)(uint32_t)mnavGetU32(&r); - if (!SameSettings(&r, def, shape)) + // A place the bake never writes is refused, which also keeps each + // tile's voxels within 32 bits. + if (!SameSettings(&r, def, shape) || !mnavFlightTileInExtent(def, tile->tileX, tile->tileZ)) { return Refuse(mnav_errorFormat, mnav_tileHeader, -1); } @@ -264,7 +267,7 @@ static int32_t Ones(uint32_t bits) // Whether a node's mask is a mixed block's: no child both mixed and // solid, and neither every child empty nor every child solid. -static bool MixedMask(uint32_t mask) +MNAV_NODISCARD static bool MixedMask(uint32_t mask) { uint32_t mixed = mask & 0xFFu; uint32_t solid = mask >> 8; diff --git a/third_party/maul-nav/src/flight_volume.c b/third_party/maul-nav/src/flight_volume.c index d30616936..9cd4611bf 100644 --- a/third_party/maul-nav/src/flight_volume.c +++ b/third_party/maul-nav/src/flight_volume.c @@ -11,6 +11,7 @@ #include "flight_def.h" #include "flight_tile.h" #include "fpenv.h" +#include "outputs.h" #include "maul-nav/base.h" #include "maul-nav/flight.h" @@ -21,16 +22,61 @@ #include #include -static MNAV_NOINLINE mnavFlightDefResult CreateFlightVolumeWork(const mnavFlightDef* def, - mnavFlightVolume** volumeOut) +static mnavFlightVolumeDefResult Refuse(mnavResult result, mnavFlightVolumeSetting setting) +{ + return (mnavFlightVolumeDefResult){result, setting, mnav_flightSettingNone}; +} + +// Checks a flight volume def and gives its flight def's shape. +static mnavFlightVolumeDefResult CheckDef(const mnavFlightVolumeDef* def, mnavFlightShape* shape) +{ + if (def == nullptr) + { + return Refuse(mnav_errorInvalid, mnav_flightVolumeSettingNone); + } + if (def->cookie != MNAV_FLIGHT_VOLUME_DEF_COOKIE) + { + return Refuse(mnav_errorInvalid, mnav_flightVolumeSettingCookie); + } + if (def->version != MNAV_ABI_VERSION) + { + return Refuse(mnav_errorVersion, mnav_flightVolumeSettingNone); + } + if ((def->allocator.alloc == nullptr) != (def->allocator.free == nullptr)) + { + return Refuse(mnav_errorInvalid, mnav_flightVolumeSettingAllocator); + } + mnavFlightDefResult flight = mnavCheckFlightDef(&def->flight, shape); + if (flight.result != mnav_success) + { + return (mnavFlightVolumeDefResult){flight.result, mnav_flightVolumeSettingFlight, + flight.setting}; + } + if (def->limits.tiles < 1) + { + return Refuse(mnav_errorInvalid, mnav_flightVolumeSettingTiles); + } + if (def->limits.tiles > MNAV_MAX_TILES) + { + return Refuse(mnav_errorLimit, mnav_flightVolumeSettingTiles); + } + if (def->limits.memoryBytes == 0) + { + return Refuse(mnav_errorInvalid, mnav_flightVolumeSettingMemoryBytes); + } + return Refuse(mnav_success, mnav_flightVolumeSettingNone); +} + +static MNAV_NOINLINE mnavFlightVolumeDefResult +CreateFlightVolumeWork(const mnavFlightVolumeDef* def, mnavFlightVolume** volumeOut) { if (volumeOut == nullptr) { - return (mnavFlightDefResult){mnav_errorInvalid, mnav_flightSettingNone}; + return Refuse(mnav_errorInvalid, mnav_flightVolumeSettingNone); } *volumeOut = nullptr; mnavFlightShape shape = {0}; - mnavFlightDefResult checked = mnavCheckFlightDef(def, &shape); + mnavFlightVolumeDefResult checked = CheckDef(def, &shape); if (checked.result != mnav_success) { return checked; @@ -41,18 +87,19 @@ static MNAV_NOINLINE mnavFlightDefResult CreateFlightVolumeWork(const mnavFlight alignof(mnavFlightVolume), (void**)&volume); if (result != mnav_success) { - return (mnavFlightDefResult){result, mnav_flightSettingNone}; + return Refuse(result, mnav_flightVolumeSettingNone); } *volume = (mnavFlightVolume){.def = *def, .shape = shape, .memory = memory}; volume->memory.host = &volume->host; *volumeOut = volume; - return (mnavFlightDefResult){mnav_success, mnav_flightSettingNone}; + return Refuse(mnav_success, mnav_flightVolumeSettingNone); } -mnavFlightDefResult mnavCreateFlightVolume(const mnavFlightDef* def, mnavFlightVolume** volumeOut) +mnavFlightVolumeDefResult mnavCreateFlightVolume(const mnavFlightVolumeDef* def, + mnavFlightVolume** volumeOut) { uint64_t env = mnavFloatEnvEnter(); - mnavFlightDefResult result = CreateFlightVolumeWork(def, volumeOut); + mnavFlightVolumeDefResult result = CreateFlightVolumeWork(def, volumeOut); mnavFloatEnvLeave(env); return result; } @@ -65,7 +112,7 @@ static void DropEntries(mnavMemory* memory, mnavFlightEntry* entries, int32_t co } } -uint64_t mnavGetFlightVolumeMisuse(const mnavFlightVolume* volume) +uint64_t mnavFlightVolume_GetMisuse(const mnavFlightVolume* volume) { return volume != nullptr ? mnavHostMisuse(&volume->host) : 0; } @@ -76,9 +123,17 @@ void mnavDestroyFlightVolume(mnavFlightVolume* volume) { return; } - DropEntries(&volume->memory, volume->tiles, volume->tileCount); - DropEntries(&volume->memory, volume->staged, volume->stagedCount); - mnavRelease(&volume->memory, volume->tiles, (size_t)volume->tileCount, sizeof(mnavFlightEntry), + // The lists are emptied before anything is released, so a free hook + // that reads the volume back finds no tile rather than released + // memory. + mnavFlightEntry* tiles = volume->tiles; + int32_t tileCount = volume->tileCount; + int32_t stagedCount = volume->stagedCount; + volume->tileCount = 0; + volume->stagedCount = 0; + DropEntries(&volume->memory, tiles, tileCount); + DropEntries(&volume->memory, volume->staged, stagedCount); + mnavRelease(&volume->memory, tiles, (size_t)tileCount, sizeof(mnavFlightEntry), alignof(mnavFlightEntry)); mnavRelease(&volume->memory, volume->staged, (size_t)volume->stagedCapacity, sizeof(mnavFlightEntry), alignof(mnavFlightEntry)); @@ -158,8 +213,9 @@ static MNAV_NOINLINE mnavTileResult StageFlightTileWork(mnavFlightVolume* volume return (mnavTileResult){mnav_errorInvalid, mnav_tileHeader, -1}; } mnavFlightEntry change = {0}; - mnavTileResult loaded = mnavDecodeFlightTile(&volume->memory, &volume->def, &volume->shape, - bytes, size, &change.tile, &change.fingerprint); + mnavTileResult loaded = + mnavDecodeFlightTile(&volume->memory, &volume->def.flight, &volume->shape, bytes, size, + &change.tile, &change.fingerprint); if (loaded.result != mnav_success) { return loaded; @@ -178,7 +234,8 @@ static MNAV_NOINLINE mnavTileResult StageFlightTileCounted(mnavFlightVolume* vol return result; } -mnavTileResult mnavStageFlightTile(mnavFlightVolume* volume, const uint8_t* bytes, size_t size) +mnavTileResult mnavFlightVolume_StageTile(mnavFlightVolume* volume, const uint8_t* bytes, + size_t size) { uint64_t env = mnavFloatEnvEnter(); mnavTileResult result = StageFlightTileCounted(volume, bytes, size); @@ -199,7 +256,7 @@ static mnavResult StageFlightTileRemovalWork(mnavFlightVolume* volume, int32_t t return Stage(volume, (mnavFlightEntry){.x = tileX, .z = tileZ, .removal = true}); } -mnavResult mnavStageFlightTileRemoval(mnavFlightVolume* volume, int32_t tileX, int32_t tileZ) +mnavResult mnavFlightVolume_StageTileRemoval(mnavFlightVolume* volume, int32_t tileX, int32_t tileZ) { return mnavHostCountInvalid(MNAV_HOST(volume), StageFlightTileRemovalWork(volume, tileX, tileZ)); @@ -227,7 +284,7 @@ static int64_t CountAfter(const mnavFlightVolume* volume) } // Merges the sorted staged changes into the committed tiles, into next, -// releasing the tiles replaced or removed. +// marking the tiles replaced or removed. static void Merge(mnavFlightVolume* volume, mnavFlightEntry* next) { int32_t n = 0; @@ -243,8 +300,7 @@ static void Merge(mnavFlightVolume* volume, mnavFlightEntry* next) if (t < volume->tileCount && volume->tiles[t].x == change->x && volume->tiles[t].z == change->z) { - mnavReleaseFlightTile(&volume->memory, &volume->tiles[t].tile); - ++t; + volume->tiles[t++].removal = true; } if (!change->removal) { @@ -294,14 +350,28 @@ static MNAV_NOINLINE mnavResult CommitFlightWork(mnavFlightVolume* volume) { // Nothing is left: every tile is removed, and the staged changes // are removals, which hold no tile. - DropEntries(&volume->memory, volume->tiles, volume->tileCount); + for (int32_t t = 0; t < volume->tileCount; ++t) + { + volume->tiles[t].removal = true; + } } - mnavRelease(&volume->memory, volume->tiles, (size_t)volume->tileCount, sizeof(mnavFlightEntry), - alignof(mnavFlightEntry)); + // The tiles replaced or removed are released only with the new list in + // place, so a free hook that reads the volume back finds the new tiles. + mnavFlightEntry* old = volume->tiles; + int32_t oldCount = volume->tileCount; volume->tiles = next; volume->tileCount = (int32_t)count; volume->stagedCount = 0; volume->commits += 1; + for (int32_t t = 0; t < oldCount; ++t) + { + if (old[t].removal) + { + mnavReleaseFlightTile(&volume->memory, &old[t].tile); + } + } + mnavRelease(&volume->memory, old, (size_t)oldCount, sizeof(mnavFlightEntry), + alignof(mnavFlightEntry)); return mnav_success; } @@ -310,7 +380,7 @@ static MNAV_NOINLINE mnavResult CommitFlightCounted(mnavFlightVolume* volume) return mnavHostCountInvalid(MNAV_HOST(volume), CommitFlightWork(volume)); } -mnavResult mnavCommitFlight(mnavFlightVolume* volume) +mnavResult mnavFlightVolume_Commit(mnavFlightVolume* volume) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = CommitFlightCounted(volume); @@ -330,16 +400,18 @@ static mnavResult GetFlightTileWork(const mnavFlightVolume* volume, int32_t tile return e != nullptr ? mnav_success : mnav_errorNotLoaded; } -mnavResult mnavGetFlightTile(const mnavFlightVolume* volume, int32_t tileX, int32_t tileZ, - uint64_t* fingerprintOut) +mnavResult mnavFlightVolume_GetTile(const mnavFlightVolume* volume, int32_t tileX, int32_t tileZ, + uint64_t* fingerprintOut) { - return mnavHostCountInvalid(MNAV_HOST(volume), - GetFlightTileWork(volume, tileX, tileZ, fingerprintOut)); + mnavClearOutput(fingerprintOut, sizeof(*fingerprintOut)); + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(volume), GetFlightTileWork(volume, tileX, tileZ, fingerprintOut)); + return mnavClearOnFailure(result, fingerprintOut, sizeof(*fingerprintOut)); } // The voxel holding a distance from the origin; false past // MNAV_MAX_FLIGHT_VOXELS. -static bool VoxelOf(double meters, double voxel, int64_t* voxelOut) +MNAV_NODISCARD static bool VoxelOf(double meters, double voxel, int64_t* voxelOut) { double q = floor(meters / voxel); if (!(fabs(q) <= (double)MNAV_MAX_FLIGHT_VOXELS)) @@ -365,7 +437,7 @@ static MNAV_NOINLINE mnavResult IsFlightOpenWork(const mnavFlightVolume* volume, return mnav_errorInvalid; } *openOut = false; - const mnavFlightDef* def = &volume->def; + const mnavFlightDef* def = &volume->def.flight; double voxel = (double)def->voxelSize; int64_t v[3] = {0, 0, 0}; if (isnan(point.x) || isnan(point.y) || isnan(point.z)) @@ -400,10 +472,13 @@ static MNAV_NOINLINE mnavResult IsFlightOpenWork(const mnavFlightVolume* volume, static MNAV_NOINLINE mnavResult IsFlightOpenCounted(const mnavFlightVolume* volume, mnavPos3 point, bool* openOut) { - return mnavHostCountInvalid(MNAV_HOST(volume), IsFlightOpenWork(volume, point, openOut)); + mnavClearOutput(openOut, sizeof(*openOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(volume), IsFlightOpenWork(volume, point, openOut)); + return mnavClearOnFailure(result, openOut, sizeof(*openOut)); } -mnavResult mnavIsFlightOpen(const mnavFlightVolume* volume, mnavPos3 point, bool* openOut) +mnavResult mnavFlightVolume_IsOpen(const mnavFlightVolume* volume, mnavPos3 point, bool* openOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = IsFlightOpenCounted(volume, point, openOut); diff --git a/third_party/maul-nav/src/flight_volume.h b/third_party/maul-nav/src/flight_volume.h index 2a975e0c8..233400484 100644 --- a/third_party/maul-nav/src/flight_volume.h +++ b/third_party/maul-nav/src/flight_volume.h @@ -16,7 +16,8 @@ #include // A committed tile, or a staged change at a place: a tile to install, or -// a removal. +// a removal. A commit marks a committed tile it replaces or removes as a +// removal, and releases it once the new tiles are in place. typedef struct mnavFlightEntry { uint64_t fingerprint; @@ -28,7 +29,7 @@ typedef struct mnavFlightEntry struct mnavFlightVolume { - mnavFlightDef def; + mnavFlightVolumeDef def; mnavFlightShape shape; mnavMemory memory; // The committed tiles, sorted by x, then z. diff --git a/third_party/maul-nav/src/flow.c b/third_party/maul-nav/src/flow.c index caaa272ff..5ce3cc27d 100644 --- a/third_party/maul-nav/src/flow.c +++ b/third_party/maul-nav/src/flow.c @@ -14,6 +14,7 @@ #include "flow.h" #include "fpenv.h" #include "invariant.h" +#include "outputs.h" #include "query_filter.h" #include "maul-nav/bake.h" @@ -29,15 +30,15 @@ // The diagonal's length in cells, rounded once to binary64. #define DIAGONAL 1.4142135623730951 -static MNAV_NOINLINE mnavResult CreateFlowFieldWork(const mnavFlowFieldDef* def, - mnavFlowField** fieldOut) +static MNAV_NOINLINE mnavResult CreateFlowFieldWork(const mnavGridFlowDef* def, + mnavGridFlow** fieldOut) { if (fieldOut == nullptr) { return mnav_errorInvalid; } *fieldOut = nullptr; - if (def == nullptr || def->cookie != MNAV_FLOW_FIELD_DEF_COOKIE) + if (def == nullptr || def->cookie != MNAV_GRID_FLOW_DEF_COOKIE) { return mnav_errorInvalid; } @@ -53,19 +54,19 @@ static MNAV_NOINLINE mnavResult CreateFlowFieldWork(const mnavFlowFieldDef* def, { return mnav_errorInvalid; } - if (def->cells > MNAV_MAX_FLOW_CELLS) + if (def->cells > MNAV_MAX_GRID_FLOW_CELLS) { return mnav_errorLimit; } mnavMemory memory = mnavMakeMemory(def->allocator, UINT64_MAX); - mnavFlowField* f = nullptr; + mnavGridFlow* f = nullptr; mnavResult result = - mnavAllocate(&memory, 1, sizeof(mnavFlowField), alignof(mnavFlowField), (void**)&f); + mnavAllocate(&memory, 1, sizeof(mnavGridFlow), alignof(mnavGridFlow), (void**)&f); if (result != mnav_success) { return result; } - *f = (mnavFlowField){0}; + *f = (mnavGridFlow){0}; f->memory = memory; f->memory.host = &f->host; f->def = *def; @@ -107,14 +108,14 @@ static MNAV_NOINLINE mnavResult CreateFlowFieldWork(const mnavFlowFieldDef* def, } if (result != mnav_success) { - mnavDestroyFlowField(f); + mnavDestroyGridFlow(f); return result; } *fieldOut = f; return mnav_success; } -mnavResult mnavCreateFlowField(const mnavFlowFieldDef* def, mnavFlowField** fieldOut) +mnavResult mnavCreateGridFlow(const mnavGridFlowDef* def, mnavGridFlow** fieldOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = CreateFlowFieldWork(def, fieldOut); @@ -122,12 +123,12 @@ mnavResult mnavCreateFlowField(const mnavFlowFieldDef* def, mnavFlowField** fiel return result; } -uint64_t mnavGetFlowFieldMisuse(const mnavFlowField* field) +uint64_t mnavGridFlow_GetMisuse(const mnavGridFlow* field) { return field != nullptr ? mnavHostMisuse(&field->host) : 0; } -void mnavDestroyFlowField(mnavFlowField* field) +void mnavDestroyGridFlow(mnavGridFlow* field) { if (field == nullptr || mnavHostCalledBack(&field->host)) { @@ -147,12 +148,12 @@ void mnavDestroyFlowField(mnavFlowField* field) // does not touch it. mnavHostEnter(&field->host); memory.host = nullptr; - mnavRelease(&memory, field, 1, sizeof(mnavFlowField), alignof(mnavFlowField)); + mnavRelease(&memory, field, 1, sizeof(mnavGridFlow), alignof(mnavGridFlow)); } bool mnavFlowOpen(const mnavFlowSearch* s, int32_t x, int32_t y) { - const mnavFlowRegion* r = &s->field->region; + const mnavFlowWindow* r = &s->field->region; if (x < r->x || y < r->y || x >= r->x + r->width || y >= r->y + r->height) { return false; @@ -163,25 +164,25 @@ bool mnavFlowOpen(const mnavFlowSearch* s, int32_t x, int32_t y) double mnavFlowAreaCost(const mnavFlowSearch* s, int32_t cell) { - const mnavFlowField* f = s->field; + const mnavGridFlow* f = s->field; int32_t x = f->region.x + cell % f->region.width; int32_t y = f->region.y + cell / f->region.width; return (double)f->filter.costs[s->areas[(size_t)y * (size_t)f->gridWidth + (size_t)x]]; } // The heap, by cost, then by cell index. -static bool Sooner(const mnavFlowField* f, int32_t a, int32_t b) +MNAV_NODISCARD static bool Sooner(const mnavGridFlow* f, int32_t a, int32_t b) { return f->costs[a] != f->costs[b] ? f->costs[a] < f->costs[b] : a < b; } -static void Place(mnavFlowField* f, int32_t at, int32_t cell) +static void Place(mnavGridFlow* f, int32_t at, int32_t cell) { f->heap[at] = cell; f->places[cell] = at; } -static void SiftUp(mnavFlowField* f, int32_t at) +static void SiftUp(mnavGridFlow* f, int32_t at) { int32_t cell = f->heap[at]; while (at > 0) @@ -197,7 +198,7 @@ static void SiftUp(mnavFlowField* f, int32_t at) Place(f, at, cell); } -static int32_t Pop(mnavFlowField* f) +static int32_t Pop(mnavGridFlow* f) { MNAV_ASSERT(f->heapCount > 0 && f->places[f->heap[0]] == 0); int32_t top = f->heap[0]; @@ -229,7 +230,7 @@ static int32_t Pop(mnavFlowField* f) return top; } -void mnavFlowLower(mnavFlowField* f, int32_t cell, double cost, int32_t next) +void mnavFlowLower(mnavGridFlow* f, int32_t cell, double cost, int32_t next) { // A goal leads to itself; any other cell's way costs no less than its // next cell's, as area costs are at least 0. @@ -262,7 +263,7 @@ static const mnavCell s_steps[8] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, bool mnavFlowNeighbor(const mnavFlowSearch* s, int32_t cell, int32_t d, int32_t* other, double* length) { - const mnavFlowField* f = s->field; + const mnavGridFlow* f = s->field; int32_t width = f->region.width; int32_t x = f->region.x + cell % width; int32_t y = f->region.y + cell / width; @@ -290,7 +291,7 @@ double mnavFlowStepCost(const mnavFlowSearch* s, int32_t from, int32_t to, doubl // with the cell's own area cost read once. static void Expand(const mnavFlowSearch* s, int32_t cell) { - mnavFlowField* f = s->field; + mnavGridFlow* f = s->field; int32_t width = f->region.width; int32_t x = f->region.x + cell % width; int32_t y = f->region.y + cell / width; @@ -320,13 +321,13 @@ static void Expand(const mnavFlowSearch* s, int32_t cell) } } -static bool GoodGrid(const mnavGrid* grid) +MNAV_NODISCARD static bool GoodGrid(const mnavGrid* grid) { return grid->areas != nullptr && grid->width >= 1 && grid->height >= 1 && isfinite(grid->cellSize) && grid->cellSize > 0.0f; } -static bool GoodGoals(const mnavGrid* grid, const mnavCell* goals, int32_t count) +MNAV_NODISCARD static bool GoodGoals(const mnavGrid* grid, const mnavCell* goals, int32_t count) { for (int32_t i = 0; i < count; ++i) { @@ -339,13 +340,13 @@ static bool GoodGoals(const mnavGrid* grid, const mnavCell* goals, int32_t count return true; } -static bool GoodRegion(const mnavGrid* grid, const mnavFlowRegion* r) +MNAV_NODISCARD static bool GoodRegion(const mnavGrid* grid, const mnavCellRange* r) { - return r->x >= 0 && r->y >= 0 && r->width >= 1 && r->height >= 1 && - r->x <= grid->width - r->width && r->y <= grid->height - r->height; + return r->x0 >= 0 && r->y0 >= 0 && r->x0 <= r->x1 && r->y0 <= r->y1 && r->x1 < grid->width && + r->y1 < grid->height; } -static mnavResult Check(mnavFlowField* field, const mnavGrid* grid, const mnavFlowRegion* region, +static mnavResult Check(mnavGridFlow* field, const mnavGrid* grid, const mnavCellRange* region, const mnavCell* goals, int32_t goalCount) { if (field == nullptr || grid == nullptr || goalCount < 0 || @@ -358,14 +359,15 @@ static mnavResult Check(mnavFlowField* field, const mnavGrid* grid, const mnavFl { return mnav_errorLimit; } - int64_t cells = region != nullptr ? (int64_t)region->width * (int64_t)region->height + int64_t cells = region != nullptr ? ((int64_t)region->x1 - region->x0 + 1) * + ((int64_t)region->y1 - region->y0 + 1) : (int64_t)grid->width * (int64_t)grid->height; return cells > (int64_t)field->def.cells ? mnav_errorCapacity : mnav_success; } -static MNAV_NOINLINE mnavResult BeginFlowFieldWork(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult BeginFlowFieldWork(mnavGridFlow* field, const mnavGrid* grid, const mnavQueryFilter* filter, - const mnavFlowRegion* region, + const mnavCellRange* region, const mnavCell* goals, int32_t goalCount) { if (field != nullptr && mnavHostCalledBack(&field->host)) @@ -386,7 +388,10 @@ static MNAV_NOINLINE mnavResult BeginFlowFieldWork(mnavFlowField* field, const m { return result; } - field->region = region != nullptr ? *region : (mnavFlowRegion){0, 0, grid->width, grid->height}; + field->region = region != nullptr + ? (mnavFlowWindow){region->x0, region->y0, region->x1 - region->x0 + 1, + region->y1 - region->y0 + 1} + : (mnavFlowWindow){0, 0, grid->width, grid->height}; field->gridWidth = grid->width; field->gridHeight = grid->height; field->cellSize = grid->cellSize; @@ -425,18 +430,18 @@ static MNAV_NOINLINE mnavResult BeginFlowFieldWork(mnavFlowField* field, const m return mnav_success; } -static MNAV_NOINLINE mnavResult BeginFlowFieldCounted(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult BeginFlowFieldCounted(mnavGridFlow* field, const mnavGrid* grid, const mnavQueryFilter* filter, - const mnavFlowRegion* region, + const mnavCellRange* region, const mnavCell* goals, int32_t goalCount) { return mnavHostCountInvalid(MNAV_HOST(field), BeginFlowFieldWork(field, grid, filter, region, goals, goalCount)); } -mnavResult mnavBeginFlowField(mnavFlowField* field, const mnavGrid* grid, - const mnavQueryFilter* filter, const mnavFlowRegion* region, - const mnavCell* goals, int32_t goalCount) +mnavResult mnavGridFlow_Begin(mnavGridFlow* field, const mnavGrid* grid, + const mnavCellRange* region, const mnavCell* goals, int32_t goalCount, + const mnavQueryFilter* filter) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BeginFlowFieldCounted(field, grid, filter, region, goals, goalCount); @@ -444,13 +449,13 @@ mnavResult mnavBeginFlowField(mnavFlowField* field, const mnavGrid* grid, return result; } -bool mnavFlowSameGrid(const mnavFlowField* f, const mnavGrid* grid) +bool mnavFlowSameGrid(const mnavGridFlow* f, const mnavGrid* grid) { return grid->areas != nullptr && grid->width == f->gridWidth && grid->height == f->gridHeight && grid->cellSize == f->cellSize; } -static MNAV_NOINLINE mnavResult ContinueFlowFieldWork(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult ContinueFlowFieldWork(mnavGridFlow* field, const mnavGrid* grid, int32_t cells, bool* endedOut) { if (field != nullptr && mnavHostCalledBack(&field->host)) @@ -488,14 +493,18 @@ static MNAV_NOINLINE mnavResult ContinueFlowFieldWork(mnavFlowField* field, cons return mnav_success; } -static MNAV_NOINLINE mnavResult ContinueFlowFieldCounted(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult ContinueFlowFieldCounted(mnavGridFlow* field, const mnavGrid* grid, int32_t cells, bool* endedOut) { - return mnavHostCountInvalid(MNAV_HOST(field), - ContinueFlowFieldWork(field, grid, cells, endedOut)); + bool endedMade = false; + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(field), + ContinueFlowFieldWork(field, grid, cells, endedOut != nullptr ? &endedMade : nullptr)); + mnavGiveOutput(result, false, endedOut, &endedMade, sizeof(endedMade)); + return result; } -mnavResult mnavContinueFlowField(mnavFlowField* field, const mnavGrid* grid, int32_t cells, +mnavResult mnavGridFlow_Continue(mnavGridFlow* field, const mnavGrid* grid, int32_t cells, bool* endedOut) { uint64_t env = mnavFloatEnvEnter(); @@ -504,7 +513,7 @@ mnavResult mnavContinueFlowField(mnavFlowField* field, const mnavGrid* grid, int return result; } -static MNAV_NOINLINE mnavResult BuildFlowFieldWork(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult BuildFlowFieldWork(mnavGridFlow* field, const mnavGrid* grid, const mnavQueryFilter* filter, const mnavCell* goals, int32_t goalCount) { @@ -512,7 +521,7 @@ static MNAV_NOINLINE mnavResult BuildFlowFieldWork(mnavFlowField* field, const m return result == mnav_success ? ContinueFlowFieldWork(field, grid, INT32_MAX, nullptr) : result; } -static MNAV_NOINLINE mnavResult BuildFlowFieldCounted(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult BuildFlowFieldCounted(mnavGridFlow* field, const mnavGrid* grid, const mnavQueryFilter* filter, const mnavCell* goals, int32_t goalCount) { @@ -520,9 +529,8 @@ static MNAV_NOINLINE mnavResult BuildFlowFieldCounted(mnavFlowField* field, cons BuildFlowFieldWork(field, grid, filter, goals, goalCount)); } -mnavResult mnavBuildFlowField(mnavFlowField* field, const mnavGrid* grid, - const mnavQueryFilter* filter, const mnavCell* goals, - int32_t goalCount) +mnavResult mnavGridFlow_Build(mnavGridFlow* field, const mnavGrid* grid, const mnavCell* goals, + int32_t goalCount, const mnavQueryFilter* filter) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BuildFlowFieldCounted(field, grid, filter, goals, goalCount); @@ -530,8 +538,8 @@ mnavResult mnavBuildFlowField(mnavFlowField* field, const mnavGrid* grid, return result; } -static MNAV_NOINLINE mnavResult FlowAtWork(const mnavFlowField* field, mnavCell cell, - mnavFlow* flowOut) +static MNAV_NOINLINE mnavResult FlowAtWork(const mnavGridFlow* field, mnavCell cell, + mnavCellFlow* flowOut) { if (field == nullptr || flowOut == nullptr) { @@ -541,7 +549,7 @@ static MNAV_NOINLINE mnavResult FlowAtWork(const mnavFlowField* field, mnavCell { return mnav_errorState; } - const mnavFlowRegion* r = &field->region; + const mnavFlowWindow* r = &field->region; if (cell.x < r->x || cell.y < r->y || cell.x >= r->x + r->width || cell.y >= r->y + r->height) { return mnav_errorOutside; @@ -552,17 +560,19 @@ static MNAV_NOINLINE mnavResult FlowAtWork(const mnavFlowField* field, mnavCell } int32_t at = (cell.y - r->y) * r->width + (cell.x - r->x); int32_t next = field->next[at]; - *flowOut = (mnavFlow){field->costs[at], {r->x + next % r->width, r->y + next / r->width}}; + *flowOut = (mnavCellFlow){field->costs[at], {r->x + next % r->width, r->y + next / r->width}}; return mnav_success; } -static MNAV_NOINLINE mnavResult FlowAtCounted(const mnavFlowField* field, mnavCell cell, - mnavFlow* flowOut) +static MNAV_NOINLINE mnavResult FlowAtCounted(const mnavGridFlow* field, mnavCell cell, + mnavCellFlow* flowOut) { - return mnavHostCountInvalid(MNAV_HOST(field), FlowAtWork(field, cell, flowOut)); + mnavClearOutput(flowOut, sizeof(*flowOut)); + mnavResult result = mnavHostCountInvalid(MNAV_HOST(field), FlowAtWork(field, cell, flowOut)); + return mnavClearOnFailure(result, flowOut, sizeof(*flowOut)); } -mnavResult mnavFlowAt(const mnavFlowField* field, mnavCell cell, mnavFlow* flowOut) +mnavResult mnavGridFlow_GetFlow(const mnavGridFlow* field, mnavCell cell, mnavCellFlow* flowOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FlowAtCounted(field, cell, flowOut); @@ -570,7 +580,7 @@ mnavResult mnavFlowAt(const mnavFlowField* field, mnavCell cell, mnavFlow* flowO return result; } -static MNAV_NOINLINE mnavResult DebugFlowFieldWork(const mnavFlowField* field, double height, +static MNAV_NOINLINE mnavResult DebugFlowFieldWork(const mnavGridFlow* field, double height, mnavDebugBuffer* buffer) { if (field == nullptr || !mnavGoodBuffer(buffer) || !isfinite(height)) @@ -581,7 +591,7 @@ static MNAV_NOINLINE mnavResult DebugFlowFieldWork(const mnavFlowField* field, d { return mnav_errorState; } - const mnavFlowRegion* r = &field->region; + const mnavFlowWindow* r = &field->region; int32_t cells = field->state == mnav_flowEnded ? r->width * r->height : 0; double cellSize = (double)field->cellSize; for (int32_t c = 0; c < cells; ++c) @@ -616,13 +626,13 @@ static MNAV_NOINLINE mnavResult DebugFlowFieldWork(const mnavFlowField* field, d return mnavDrawResult(buffer); } -static MNAV_NOINLINE mnavResult DebugFlowFieldCounted(const mnavFlowField* field, double height, +static MNAV_NOINLINE mnavResult DebugFlowFieldCounted(const mnavGridFlow* field, double height, mnavDebugBuffer* buffer) { return mnavHostCountInvalid(MNAV_HOST(field), DebugFlowFieldWork(field, height, buffer)); } -mnavResult mnavDebugFlowField(const mnavFlowField* field, double height, mnavDebugBuffer* buffer) +mnavResult mnavGridFlow_Debug(const mnavGridFlow* field, double height, mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = DebugFlowFieldCounted(field, height, buffer); diff --git a/third_party/maul-nav/src/flow.h b/third_party/maul-nav/src/flow.h index a0a0757d5..72dd8191c 100644 --- a/third_party/maul-nav/src/flow.h +++ b/third_party/maul-nav/src/flow.h @@ -16,6 +16,16 @@ #include #include +// The cells a field covers: columns x to x + width - 1 and rows y to +// y + height - 1 of its grid, from the mnavCellRange its work began with. +typedef struct mnavFlowWindow +{ + int32_t x; + int32_t y; + int32_t width; + int32_t height; +} mnavFlowWindow; + // A cell's place in the heap when it is not in it: never reached, or done. #define MNAV_FLOW_NOT_OPEN (-1) #define MNAV_FLOW_DONE (-2) @@ -36,11 +46,11 @@ enum mnav_flowNewGoal = 4 }; -struct mnavFlowField +struct mnavGridFlow { mnavMemory memory; mnavHostCode host; - mnavFlowFieldDef def; + mnavGridFlowDef def; // Per cell of the region, row by row: the cost to the goals, the next // cell's index, the place in the heap and the repair's marks. double* costs; @@ -59,7 +69,7 @@ struct mnavFlowField int32_t fixed; bool repairing; // The work's region, its grid's size and cell size, and the filter. - mnavFlowRegion region; + mnavFlowWindow region; int32_t gridWidth; int32_t gridHeight; float cellSize; @@ -70,30 +80,30 @@ struct mnavFlowField // A step of work: the field and the grid's areas. typedef struct mnavFlowSearch { - mnavFlowField* field; + mnavGridFlow* field; const mnavAreaType* areas; } mnavFlowSearch; // Whether grid cell (x, y) lies in the region and may be walked. -bool mnavFlowOpen(const mnavFlowSearch* s, int32_t x, int32_t y); +MNAV_NODISCARD bool mnavFlowOpen(const mnavFlowSearch* s, int32_t x, int32_t y); // The area cost of a cell of the region. double mnavFlowAreaCost(const mnavFlowSearch* s, int32_t cell); // Whether a cell may step in direction d, 0 to 7, to the 8 neighbours; // the neighbour and the step's length. -bool mnavFlowNeighbor(const mnavFlowSearch* s, int32_t cell, int32_t d, int32_t* other, - double* length); +MNAV_NODISCARD bool mnavFlowNeighbor(const mnavFlowSearch* s, int32_t cell, int32_t d, + int32_t* other, double* length); // A step's cost from one cell to a neighbour, as the search adds it. double mnavFlowStepCost(const mnavFlowSearch* s, int32_t from, int32_t to, double length); // Gives a cell a lower cost by way of the next cell given, opening it // again when it was done; a repair lists it as touched. -void mnavFlowLower(mnavFlowField* f, int32_t cell, double cost, int32_t next); +void mnavFlowLower(mnavGridFlow* f, int32_t cell, double cost, int32_t next); // Whether the grid is the one the work began on. -bool mnavFlowSameGrid(const mnavFlowField* f, const mnavGrid* grid); +MNAV_NODISCARD bool mnavFlowSameGrid(const mnavGridFlow* f, const mnavGrid* grid); // Chooses the next cell of up to budget touched cells and their // neighbours; returns how many it did, and ends the repair after the diff --git a/third_party/maul-nav/src/flow_repair.c b/third_party/maul-nav/src/flow_repair.c index 965bb4f15..dca16d1ad 100644 --- a/third_party/maul-nav/src/flow_repair.c +++ b/third_party/maul-nav/src/flow_repair.c @@ -25,7 +25,7 @@ #include // Marks a cell raised and lists it, once. -static void Raise(mnavFlowField* f, int32_t cell) +static void Raise(mnavGridFlow* f, int32_t cell) { if ((f->marks[cell] & mnav_flowRaised) == 0) { @@ -37,9 +37,9 @@ static void Raise(mnavFlowField* f, int32_t cell) } // The cell of the region at grid place (x, y), or -1 outside it. -static int32_t CellAt(const mnavFlowField* f, int32_t x, int32_t y) +static int32_t CellAt(const mnavGridFlow* f, int32_t x, int32_t y) { - const mnavFlowRegion* r = &f->region; + const mnavFlowWindow* r = &f->region; if (x < r->x || y < r->y || x >= r->x + r->width || y >= r->y + r->height) { return -1; @@ -49,7 +49,7 @@ static int32_t CellAt(const mnavFlowField* f, int32_t x, int32_t y) // Raises a changed cell and the cells beside it whose diagonal step to // their next cell passes its corner. -static void RaiseChanged(mnavFlowField* f, int32_t x, int32_t y) +static void RaiseChanged(mnavGridFlow* f, int32_t x, int32_t y) { int32_t cell = CellAt(f, x, y); if (cell < 0) @@ -80,7 +80,7 @@ static void RaiseChanged(mnavFlowField* f, int32_t x, int32_t y) // Raises the subtrees of the raised cells listed: every cell whose next // cell is raised. -static void RaiseSubtrees(mnavFlowField* f) +static void RaiseSubtrees(mnavGridFlow* f) { int32_t width = f->region.width; for (int32_t i = 0; i < f->touchedCount; ++i) @@ -106,7 +106,7 @@ static void RaiseSubtrees(mnavFlowField* f) // that of a way the grid has, so any offer is one. static void Offer(const mnavFlowSearch* s, int32_t cell) { - mnavFlowField* f = s->field; + mnavGridFlow* f = s->field; for (int32_t d = 0; d < 8; ++d) { int32_t n = 0; @@ -124,9 +124,9 @@ static void Offer(const mnavFlowSearch* s, int32_t cell) } // Whether grid cell (x, y) is open: in the region and walkable. -static bool OpenCell(const mnavFlowSearch* s, int32_t cell) +MNAV_NODISCARD static bool OpenCell(const mnavFlowSearch* s, int32_t cell) { - const mnavFlowField* f = s->field; + const mnavGridFlow* f = s->field; return mnavFlowOpen(s, f->region.x + cell % f->region.width, f->region.y + cell / f->region.width); } @@ -148,7 +148,21 @@ static void OfferAround(const mnavFlowSearch* s, int32_t x, int32_t y) } } -static mnavResult Check(const mnavFlowField* field, const mnavGrid* grid, const mnavCell* goals, +// Whether every cell of a list lies in the grid. +MNAV_NODISCARD static bool InGrid(const mnavGrid* grid, const mnavCell* cells, int32_t count) +{ + for (int32_t i = 0; i < count; ++i) + { + mnavCell c = cells[i]; + if (c.x < 0 || c.y < 0 || c.x >= grid->width || c.y >= grid->height) + { + return false; + } + } + return true; +} + +static mnavResult Check(const mnavGridFlow* field, const mnavGrid* grid, const mnavCell* goals, int32_t goalCount, const mnavCell* changed, int32_t changedCount) { if (field == nullptr || grid == nullptr || goalCount < 0 || @@ -165,13 +179,10 @@ static mnavResult Check(const mnavFlowField* field, const mnavGrid* grid, const { return mnav_errorInvalid; } - for (int32_t i = 0; i < goalCount + changedCount; ++i) + // Each list by its own count: the two may sum past 32 bits. + if (!InGrid(grid, goals, goalCount) || !InGrid(grid, changed, changedCount)) { - mnavCell c = i < goalCount ? goals[i] : changed[i - goalCount]; - if (c.x < 0 || c.y < 0 || c.x >= grid->width || c.y >= grid->height) - { - return mnav_errorInvalid; - } + return mnav_errorInvalid; } return field->state != mnav_flowEnded ? mnav_errorState : mnav_success; } @@ -180,7 +191,7 @@ static mnavResult Check(const mnavFlowField* field, const mnavGrid* grid, const // new list. static void SwapGoals(const mnavFlowSearch* s, const mnavCell* goals, int32_t goalCount) { - mnavFlowField* f = s->field; + mnavGridFlow* f = s->field; for (int32_t i = 0; i < goalCount; ++i) { int32_t cell = CellAt(f, goals[i].x, goals[i].y); @@ -208,7 +219,7 @@ static void SwapGoals(const mnavFlowSearch* s, const mnavCell* goals, int32_t go } } -static MNAV_NOINLINE mnavResult UpdateFlowFieldWork(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult UpdateFlowFieldWork(mnavGridFlow* field, const mnavGrid* grid, const mnavCell* goals, int32_t goalCount, const mnavCell* changed, int32_t changedCount) { @@ -265,7 +276,7 @@ static MNAV_NOINLINE mnavResult UpdateFlowFieldWork(mnavFlowField* field, const return mnav_success; } -static MNAV_NOINLINE mnavResult UpdateFlowFieldCounted(mnavFlowField* field, const mnavGrid* grid, +static MNAV_NOINLINE mnavResult UpdateFlowFieldCounted(mnavGridFlow* field, const mnavGrid* grid, const mnavCell* goals, int32_t goalCount, const mnavCell* changed, int32_t changedCount) @@ -274,7 +285,7 @@ static MNAV_NOINLINE mnavResult UpdateFlowFieldCounted(mnavFlowField* field, con changed, changedCount)); } -mnavResult mnavUpdateFlowField(mnavFlowField* field, const mnavGrid* grid, const mnavCell* goals, +mnavResult mnavGridFlow_Update(mnavGridFlow* field, const mnavGrid* grid, const mnavCell* goals, int32_t goalCount, const mnavCell* changed, int32_t changedCount) { uint64_t env = mnavFloatEnvEnter(); @@ -289,7 +300,7 @@ mnavResult mnavUpdateFlowField(mnavFlowField* field, const mnavGrid* grid, const // the lowest by cost and then by index. static int32_t NextOf(const mnavFlowSearch* s, int32_t cell) { - const mnavFlowField* f = s->field; + const mnavGridFlow* f = s->field; double cost = f->costs[cell]; if (cost == 0.0 || !isfinite(cost)) { @@ -316,7 +327,7 @@ static int32_t NextOf(const mnavFlowSearch* s, int32_t cell) int32_t mnavFlowFix(const mnavFlowSearch* s, int32_t budget) { - mnavFlowField* f = s->field; + mnavGridFlow* f = s->field; int32_t width = f->region.width; int32_t done = 0; for (; done < budget && f->fixed < f->touchedCount; ++done) diff --git a/third_party/maul-nav/src/funnel.c b/third_party/maul-nav/src/funnel.c index 269374449..dcbfba6d0 100644 --- a/third_party/maul-nav/src/funnel.c +++ b/third_party/maul-nav/src/funnel.c @@ -92,7 +92,7 @@ static double Area2(mnavPos3 a, mnavPos3 b, mnavPos3 c) return (b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x); } -static bool Same(mnavPos3 a, mnavPos3 b) +MNAV_NODISCARD static bool Same(mnavPos3 a, mnavPos3 b) { return a.x == b.x && a.y == b.y && a.z == b.z; } @@ -119,7 +119,7 @@ typedef struct Funnel } Funnel; // Whether a point lies on a portal, within a millimeter on the ground. -static bool OnPortal(mnavPos3 p, const mnavPortal* portal) +MNAV_NODISCARD static bool OnPortal(mnavPos3 p, const mnavPortal* portal) { double dx = portal->right.x - portal->left.x; double dz = portal->right.z - portal->left.z; diff --git a/third_party/maul-nav/src/grid.c b/third_party/maul-nav/src/grid.c index c06240169..4fc395509 100644 --- a/third_party/maul-nav/src/grid.c +++ b/third_party/maul-nav/src/grid.c @@ -9,6 +9,7 @@ #include "fpenv.h" #include "invariant.h" +#include "outputs.h" #include "query.h" #include "query_filter.h" @@ -54,7 +55,7 @@ typedef struct Grid int32_t best; } Grid; -static bool Open(const Grid* g, int32_t x, int32_t y) +MNAV_NODISCARD static bool Open(const Grid* g, int32_t x, int32_t y) { if (x < 0 || y < 0 || x >= g->grid->width || y >= g->grid->height) { @@ -80,7 +81,7 @@ static double Octile(const Grid* g, int32_t x0, int32_t y0, int32_t x1, int32_t } // The open list, a binary heap by total cost, then by node index. -static bool Sooner(const GridNode* nodes, int32_t a, int32_t b) +MNAV_NODISCARD static bool Sooner(const GridNode* nodes, int32_t a, int32_t b) { double ta = nodes[a].cost + nodes[a].remaining; double tb = nodes[b].cost + nodes[b].remaining; @@ -215,7 +216,7 @@ static const Step s_steps[8] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}}; // Whether a step from (x, y) by (dx, dy) is allowed. -static bool CanStep(const Grid* g, int32_t x, int32_t y, int32_t dx, int32_t dy) +MNAV_NODISCARD static bool CanStep(const Grid* g, int32_t x, int32_t y, int32_t dx, int32_t dy) { if (!Open(g, x + dx, y + dy)) { @@ -245,7 +246,7 @@ static void ExpandAStar(Grid* g, int32_t node) // Whether cell (x, y), reached moving straight by (dx, dy), has a forced // neighbour: a side cell open with the one behind it blocked. -static bool Forced(const Grid* g, int32_t x, int32_t y, int32_t dx, int32_t dy) +MNAV_NODISCARD static bool Forced(const Grid* g, int32_t x, int32_t y, int32_t dx, int32_t dy) { if (dx != 0) { @@ -256,14 +257,15 @@ static bool Forced(const Grid* g, int32_t x, int32_t y, int32_t dx, int32_t dy) (Open(g, x + 1, y) && !Open(g, x + 1, y - dy)); } -static bool IsEnd(const Grid* g, int32_t x, int32_t y) +MNAV_NODISCARD static bool IsEnd(const Grid* g, int32_t x, int32_t y) { return x == g->end.x && y == g->end.y; } // Whether a straight jump from (x, y), not counting it, finds a jump // point. -static bool StraightFinds(const Grid* g, int32_t x, int32_t y, int32_t dx, int32_t dy) +MNAV_NODISCARD static bool StraightFinds(const Grid* g, int32_t x, int32_t y, int32_t dx, + int32_t dy) { for (;;) { @@ -382,7 +384,7 @@ static void ExpandJump(Grid* g, int32_t node) // Whether node a lies nearer the end than node b: by the heuristic, then // the cost, then the index. -static bool Nearer(const GridNode* nodes, int32_t a, int32_t b) +MNAV_NODISCARD static bool Nearer(const GridNode* nodes, int32_t a, int32_t b) { if (nodes[a].remaining != nodes[b].remaining) { @@ -443,7 +445,7 @@ static mnavGridPath PathTo(const Grid* g, int32_t last, mnavPathEnd end) return (mnavGridPath){end, g->nodes[last].cost, g->nodes[last].length, cells, kept}; } -static bool GoodGrid(const mnavGrid* grid, mnavCell a, mnavCell b) +MNAV_NODISCARD static bool GoodGrid(const mnavGrid* grid, mnavCell a, mnavCell b) { return grid->areas != nullptr && grid->width >= 1 && grid->height >= 1 && isfinite(grid->cellSize) && grid->cellSize > 0.0f && a.x >= 0 && a.y >= 0 && @@ -536,12 +538,17 @@ static MNAV_NOINLINE mnavResult FindGridPathCounted(mnavQuery* query, const mnav const mnavQueryFilter* filter, mnavCell start, mnavCell end, mnavGridPath* pathOut) { - return mnavHostCountInvalid(MNAV_HOST(query), - FindGridPathWork(query, grid, filter, start, end, pathOut)); + mnavGridPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(query), FindGridPathWork(query, grid, filter, start, end, + pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFindGridPath(mnavQuery* query, const mnavGrid* grid, const mnavQueryFilter* filter, - mnavCell start, mnavCell end, mnavGridPath* pathOut) +mnavResult mnavQuery_FindGridPath(mnavQuery* query, const mnavGrid* grid, mnavCell start, + mnavCell end, const mnavQueryFilter* filter, + mnavGridPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindGridPathCounted(query, grid, filter, start, end, pathOut); diff --git a/third_party/maul-nav/src/hash.c b/third_party/maul-nav/src/hash.c index f63201fd3..ac6742864 100644 --- a/third_party/maul-nav/src/hash.c +++ b/third_party/maul-nav/src/hash.c @@ -8,7 +8,7 @@ #include -uint64_t mnavHash64(uint64_t seed, const void* data, int32_t byteCount) +uint64_t mnavHash64(uint64_t seed, const void* data, int64_t byteCount) { // Eight bytes a round: each word, read in the host's byte order like // every value the hash covers, is xored into the state, the state is @@ -18,7 +18,7 @@ uint64_t mnavHash64(uint64_t seed, const void* data, int32_t byteCount) // are frozen: the hash feeds the determinism gates. const uint8_t* bytes = (const uint8_t*)data; uint64_t hash = seed; - int32_t i = 0; + int64_t i = 0; for (; i + 8 <= byteCount; i += 8) { uint64_t word; diff --git a/third_party/maul-nav/src/height_patch.c b/third_party/maul-nav/src/height_patch.c index 0de28d3d6..2170067a1 100644 --- a/third_party/maul-nav/src/height_patch.c +++ b/third_party/maul-nav/src/height_patch.c @@ -50,7 +50,7 @@ static void Take(Flood* flood, int32_t fx, int32_t fz, uint32_t i, int32_t index // Takes each cell's span of the region; returns whether any cell had one. // Only spans at the region's edge are queued, as only they can reach // cells the region does not cover. -static bool TakeRegion(Flood* flood, const uint32_t* ids, uint32_t region) +MNAV_NODISCARD static bool TakeRegion(Flood* flood, const uint32_t* ids, uint32_t region) { const mnavCompactField* field = flood->field; const mnavHeightPatch* patch = flood->patch; diff --git a/third_party/maul-nav/src/height_patch.h b/third_party/maul-nav/src/height_patch.h index 02d163759..be54f15cd 100644 --- a/third_party/maul-nav/src/height_patch.h +++ b/third_party/maul-nav/src/height_patch.h @@ -49,8 +49,8 @@ mnavResult mnavFillHeightPatch(mnavMemory* memory, const mnavCompactField* field // when unset, the set height nearest reference in the first ring of cells // round it, out to radius, that has one, the first in ring order on ties. // Returns false when no cell within radius is set. -bool mnavPatchHeight(const mnavHeightPatch* patch, int32_t x, int32_t z, int32_t reference, - int32_t radius, uint16_t* height); +MNAV_NODISCARD bool mnavPatchHeight(const mnavHeightPatch* patch, int32_t x, int32_t z, + int32_t reference, int32_t radius, uint16_t* height); void mnavReleaseHeightPatch(mnavMemory* memory, mnavHeightPatch* patch); diff --git a/third_party/maul-nav/src/heightfield.c b/third_party/maul-nav/src/heightfield.c index 6c233ff36..fd257969d 100644 --- a/third_party/maul-nav/src/heightfield.c +++ b/third_party/maul-nav/src/heightfield.c @@ -149,7 +149,7 @@ static mnavResult Collect(mnavMemory* memory, const mnavBakeDef* def, const mnav return mnav_success; } -static bool Before(const mnavFragment* a, const mnavFragment* b) +MNAV_NODISCARD static bool Before(const mnavFragment* a, const mnavFragment* b) { if (a->bottom != b->bottom) { diff --git a/third_party/maul-nav/src/hierarchy.c b/third_party/maul-nav/src/hierarchy.c index 568d9e2ee..d89eaa0ec 100644 --- a/third_party/maul-nav/src/hierarchy.c +++ b/third_party/maul-nav/src/hierarchy.c @@ -14,6 +14,7 @@ #include "invariant.h" #include "navmesh.h" #include "offmesh.h" +#include "outputs.h" #include "query.h" #include "query_filter.h" #include "sort.h" @@ -105,7 +106,6 @@ static mnavResult Allocate(mnavHierarchy* h) mnavResult r = mnav_success; Take(h, &r, n.tiles, sizeof(int32_t), alignof(int32_t), (void**)&h->clusterOf); Take(h, &r, n.tiles, sizeof(int32_t), alignof(int32_t), (void**)&h->firstOfSlot); - Take(h, &r, n.tiles, sizeof(uint8_t), alignof(uint8_t), (void**)&h->inside); Take(h, &r, n.tiles, sizeof(uint64_t), alignof(uint64_t), (void**)&h->keys); Take(h, &r, n.tiles, sizeof(uint64_t), alignof(uint64_t), (void**)&h->scratch); Take(h, &r, n.tiles + 1, sizeof(int32_t), alignof(int32_t), (void**)&h->firstSlot); @@ -120,11 +120,7 @@ static mnavResult Allocate(mnavHierarchy* h) Take(h, &r, n.tiles, sizeof(uint64_t), alignof(uint64_t), (void**)&h->areaHashes); Take(h, &r, n.tiles, sizeof(uint8_t), alignof(uint8_t), (void**)&h->changed); Take(h, &r, n.tiles, sizeof(uint8_t), alignof(uint8_t), (void**)&h->dirty); - Take(h, &r, n.transitions + 1, sizeof(double), alignof(double), (void**)&h->costs); - Take(h, &r, n.transitions, sizeof(double), alignof(double), (void**)&h->joins); - Take(h, &r, n.transitions + 1, sizeof(int32_t), alignof(int32_t), (void**)&h->parents); - Take(h, &r, n.transitions + 1, sizeof(int32_t), alignof(int32_t), (void**)&h->places); - Take(h, &r, n.transitions + 1, sizeof(int32_t), alignof(int32_t), (void**)&h->heap); + Take(h, &r, n.tiles + 1, sizeof(int32_t), alignof(int32_t), (void**)&h->cursors); return r; } @@ -171,7 +167,6 @@ static MNAV_NOINLINE mnavResult CreateHierarchyWork(const mnavHierarchyDef* def, mnavDestroyHierarchy(h); return result; } - memset(h->inside, 0, (size_t)def->limits.tiles); *hierarchyOut = h; return mnav_success; } @@ -184,7 +179,7 @@ mnavResult mnavCreateHierarchy(const mnavHierarchyDef* def, mnavHierarchy** hier return result; } -uint64_t mnavGetHierarchyMisuse(const mnavHierarchy* hierarchy) +uint64_t mnavHierarchy_GetMisuse(const mnavHierarchy* hierarchy) { return hierarchy != nullptr ? mnavHostMisuse(&hierarchy->host) : 0; } @@ -199,7 +194,6 @@ void mnavDestroyHierarchy(mnavHierarchy* hierarchy) Sizes n = SizesOf(&h->def); Give(h, h->clusterOf, n.tiles, sizeof(int32_t), alignof(int32_t)); Give(h, h->firstOfSlot, n.tiles, sizeof(int32_t), alignof(int32_t)); - Give(h, h->inside, n.tiles, sizeof(uint8_t), alignof(uint8_t)); Give(h, h->keys, n.tiles, sizeof(uint64_t), alignof(uint64_t)); Give(h, h->scratch, n.tiles, sizeof(uint64_t), alignof(uint64_t)); Give(h, h->firstSlot, n.tiles + 1, sizeof(int32_t), alignof(int32_t)); @@ -213,11 +207,7 @@ void mnavDestroyHierarchy(mnavHierarchy* hierarchy) Give(h, h->areaHashes, n.tiles, sizeof(uint64_t), alignof(uint64_t)); Give(h, h->changed, n.tiles, sizeof(uint8_t), alignof(uint8_t)); Give(h, h->dirty, n.tiles, sizeof(uint8_t), alignof(uint8_t)); - Give(h, h->costs, n.transitions + 1, sizeof(double), alignof(double)); - Give(h, h->joins, n.transitions, sizeof(double), alignof(double)); - Give(h, h->parents, n.transitions + 1, sizeof(int32_t), alignof(int32_t)); - Give(h, h->places, n.transitions + 1, sizeof(int32_t), alignof(int32_t)); - Give(h, h->heap, n.transitions + 1, sizeof(int32_t), alignof(int32_t)); + Give(h, h->cursors, n.tiles + 1, sizeof(int32_t), alignof(int32_t)); // The mark lives in the block being freed: it is set for good, so that // a call back in from this release is refused too, and the release // does not touch it. @@ -347,8 +337,8 @@ static mnavTransition Cross(const mnavHierarchy* h, const mnavNavmesh* navmesh, } // Whether tile link l of a tile leaves the cluster across a side. -static bool Leaves(const mnavHierarchy* h, const mnavTile* tile, int32_t slot, int32_t l, - int32_t side) +MNAV_NODISCARD static bool Leaves(const mnavHierarchy* h, const mnavTile* tile, int32_t slot, + int32_t l, int32_t side) { const mnavLink* link = &tile->links[l]; int32_t target = (int32_t)link->target.slot - 1; @@ -493,9 +483,8 @@ static mnavResult FindTransitions(mnavHierarchy* h, const mnavNavmesh* navmesh, { h->firstLeaving[c + 1] += h->firstLeaving[c]; } - // Counting sort by the cluster left, keeping transition order; the - // end costs' slots serve as cursors. - int32_t* cursors = h->parents; + // Counting sort by the cluster left, keeping transition order. + int32_t* cursors = h->cursors; memcpy(cursors, h->firstLeaving, ((size_t)h->clusterCount + 1) * sizeof(int32_t)); for (int32_t u = 0; u < h->transitionCount; ++u) { @@ -596,7 +585,7 @@ static uint64_t AreaHash(const mnavTile* tile) uint64_t hash = MNAV_HASH_INIT; for (int32_t p = 0; p < tile->mesh.polygonCount; ++p) { - hash = mnavHash64(hash, &tile->mesh.polygons[p].area, (int32_t)sizeof(mnavAreaType)); + hash = mnavHash64(hash, &tile->mesh.polygons[p].area, (int64_t)sizeof(mnavAreaType)); } return hash; } @@ -606,22 +595,22 @@ static uint64_t AreaHash(const mnavTile* tile) static uint64_t LinkHash(const mnavNavmesh* navmesh) { uint64_t hash = mnavHash64(MNAV_HASH_INIT, navmesh->attachments, - (int32_t)((size_t)navmesh->attachmentCount * sizeof(uint64_t))); + (int64_t)((size_t)navmesh->attachmentCount * sizeof(uint64_t))); for (int32_t i = 0; i < navmesh->attachmentCount; ++i) { const mnavOffLink* link = &navmesh->links[mnavAttachmentOf(navmesh->attachments[i]).link]; - hash = mnavHash64(hash, &link->def.kind, (int32_t)sizeof(link->def.kind)); - hash = mnavHash64(hash, &link->def.cost, (int32_t)sizeof(link->def.cost)); - hash = mnavHash64(hash, &link->state.start, (int32_t)sizeof(mnavPos3)); - hash = mnavHash64(hash, &link->state.end, (int32_t)sizeof(mnavPos3)); - hash = mnavHash64(hash, &link->state.startPolygon, (int32_t)sizeof(mnavPolygonId)); - hash = mnavHash64(hash, &link->state.endPolygon, (int32_t)sizeof(mnavPolygonId)); + hash = mnavHash64(hash, &link->def.kind, (int64_t)sizeof(link->def.kind)); + hash = mnavHash64(hash, &link->def.cost, (int64_t)sizeof(link->def.cost)); + hash = mnavHash64(hash, &link->state.start, (int64_t)sizeof(mnavPos3)); + hash = mnavHash64(hash, &link->state.end, (int64_t)sizeof(mnavPos3)); + hash = mnavHash64(hash, &link->state.startPolygon, (int64_t)sizeof(mnavPolygonId)); + hash = mnavHash64(hash, &link->state.endPolygon, (int64_t)sizeof(mnavPolygonId)); } return hash; } // Whether the hierarchy's filter includes the polygon transition u enters. -static bool Included(const mnavHierarchy* h, const mnavNavmesh* navmesh, int32_t u) +MNAV_NODISCARD static bool Included(const mnavHierarchy* h, const mnavNavmesh* navmesh, int32_t u) { const mnavTransition* t = &h->transitions[u]; const mnavTile* tile = navmesh->slots[t->slot].tile; @@ -648,7 +637,7 @@ static void Stamp(mnavHierarchy* h, const mnavNavmesh* navmesh) // Whether the tiles and links are those the graph was built from, so // that only areas may have changed. -static bool SameShape(const mnavHierarchy* h, const mnavNavmesh* navmesh) +MNAV_NODISCARD static bool SameShape(const mnavHierarchy* h, const mnavNavmesh* navmesh) { if (navmesh->slotCount > h->def.limits.tiles || h->linkHash != LinkHash(navmesh)) { @@ -676,7 +665,7 @@ static bool SameShape(const mnavHierarchy* h, const mnavNavmesh* navmesh) // leaves end there, opening the polygon entered but never walking it: its // area costs them nothing, so that cluster is marked only when the // filter has come to include or exclude it. -static bool MarkDirty(mnavHierarchy* h, const mnavNavmesh* navmesh) +MNAV_NODISCARD static bool MarkDirty(mnavHierarchy* h, const mnavNavmesh* navmesh) { memset(h->dirty, 0, (size_t)h->clusterCount); memset(h->changed, 0, (size_t)navmesh->slotCount); @@ -909,14 +898,18 @@ static MNAV_NOINLINE mnavResult BuildHierarchyCounted(mnavHierarchy* hierarchy, const mnavQueryFilter* filter, mnavHierarchyReport* reportOut) { - return mnavHostCountInvalid( + mnavHierarchyReport reportMade = {0}; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(hierarchy), mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh))), - BuildHierarchyWork(hierarchy, query, navmesh, filter, reportOut)); + BuildHierarchyWork(hierarchy, query, navmesh, filter, + reportOut != nullptr ? &reportMade : nullptr)); + mnavGiveOutput(result, false, reportOut, &reportMade, sizeof(reportMade)); + return result; } -mnavResult mnavBuildHierarchy(mnavHierarchy* hierarchy, mnavQuery* query, - const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - mnavHierarchyReport* reportOut) +mnavResult mnavHierarchy_Build(mnavHierarchy* hierarchy, mnavQuery* query, + const mnavNavmesh* navmesh, const mnavQueryFilter* filter, + mnavHierarchyReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BuildHierarchyCounted(hierarchy, query, navmesh, filter, reportOut); @@ -964,13 +957,17 @@ static MNAV_NOINLINE mnavResult UpdateHierarchyCounted(mnavHierarchy* hierarchy, const mnavNavmesh* navmesh, mnavHierarchyReport* reportOut) { - return mnavHostCountInvalid( + mnavHierarchyReport reportMade = {0}; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(hierarchy), mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh))), - UpdateHierarchyWork(hierarchy, query, navmesh, reportOut)); + UpdateHierarchyWork(hierarchy, query, navmesh, + reportOut != nullptr ? &reportMade : nullptr)); + mnavGiveOutput(result, false, reportOut, &reportMade, sizeof(reportMade)); + return result; } -mnavResult mnavUpdateHierarchy(mnavHierarchy* hierarchy, mnavQuery* query, - const mnavNavmesh* navmesh, mnavHierarchyReport* reportOut) +mnavResult mnavHierarchy_Update(mnavHierarchy* hierarchy, mnavQuery* query, + const mnavNavmesh* navmesh, mnavHierarchyReport* reportOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = UpdateHierarchyCounted(hierarchy, query, navmesh, reportOut); diff --git a/third_party/maul-nav/src/hierarchy.h b/third_party/maul-nav/src/hierarchy.h index aa3c0eb15..117c3998c 100644 --- a/third_party/maul-nav/src/hierarchy.h +++ b/third_party/maul-nav/src/hierarchy.h @@ -67,11 +67,9 @@ struct mnavHierarchy const mnavNavmesh* reading; mnavQueryFilter filter; double scale; - // Per slot: its cluster, or -1, its place's first transition, and - // whether a confined search may expand it. + // Per slot: its cluster, or -1, and its place's first transition. int32_t* clusterOf; int32_t* firstOfSlot; - uint8_t* inside; // Clusters: their sort keys, their slots and the transitions leaving // them, each as ranges of the arrays after. uint64_t* keys; @@ -96,23 +94,15 @@ struct mnavHierarchy uint8_t* changed; uint8_t* dirty; int32_t searches; - // The abstract search: per transition, and one more for the end. - double* costs; - double* joins; - int32_t* parents; - int32_t* places; - int32_t* heap; - int32_t heapCount; + // A build's cursors, one per cluster and one more. + int32_t* cursors; }; -// Sets the confinement flag of every slot of a cluster. -void mnavMarkCluster(mnavHierarchy* h, int32_t cluster, uint8_t value); - // Runs Dijkstra's search from a point in a polygon within a cluster, // opening the nodes just beyond it, backward when asked (off-mesh links // followed from where they land); mnav_errorCapacity when it runs out of // nodes. -mnavResult mnavSearchCluster(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, +mnavResult mnavSearchCluster(const mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, bool backward, int32_t cluster, mnavPolygonId polygon, mnavPos3 point); // The cost the last search found to cross transition v's portal, or diff --git a/third_party/maul-nav/src/hierarchy_path.c b/third_party/maul-nav/src/hierarchy_path.c index 5f4220af5..2fbe81c96 100644 --- a/third_party/maul-nav/src/hierarchy_path.c +++ b/third_party/maul-nav/src/hierarchy_path.c @@ -9,6 +9,7 @@ #include "fpenv.h" #include "hierarchy.h" #include "navmesh.h" +#include "outputs.h" #include "query.h" #include "maul-nav/base.h" @@ -20,15 +21,7 @@ #include #include -void mnavMarkCluster(mnavHierarchy* h, int32_t cluster, uint8_t value) -{ - for (int32_t i = h->firstSlot[cluster]; i < h->firstSlot[cluster + 1]; ++i) - { - h->inside[h->slots[i]] = value; - } -} - -mnavResult mnavSearchCluster(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, +mnavResult mnavSearchCluster(const mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, bool backward, int32_t cluster, mnavPolygonId polygon, mnavPos3 point) { mnavResult result = @@ -37,8 +30,7 @@ mnavResult mnavSearchCluster(mnavHierarchy* h, mnavQuery* query, const mnavNavme { return result; } - mnavMarkCluster(h, cluster, 1); - mnavConfineSearch(query, h->inside, true, true); + mnavConfineSearch(query, h->clusterOf, cluster, true, true); if (backward) { mnavTurnSearchBackward(query); @@ -48,7 +40,6 @@ mnavResult mnavSearchCluster(mnavHierarchy* h, mnavQuery* query, const mnavNavme { result = mnavContinuePathWork(query, navmesh, INT32_MAX, &ended); } - mnavMarkCluster(h, cluster, 0); bool out = query->search.outOfNodes; query->search.active = false; query->flight.active = false; @@ -63,130 +54,132 @@ double mnavCostTo(const mnavQuery* query, const mnavTransition* v) // The abstract search's open list, by cost and estimate, then by index; // index transitionCount is the end. -static double Total(const mnavHierarchy* h, int32_t u, mnavPos3 end) +static double Total(const mnavHierarchy* h, const mnavGraphSearch* g, int32_t u, mnavPos3 end) { if (u == h->transitionCount) { - return h->costs[u]; + return g->costs[u]; } mnavPos3 a = h->transitions[u].at; double dx = a.x - end.x; double dy = a.y - end.y; double dz = a.z - end.z; - return h->costs[u] + sqrt(dx * dx + dy * dy + dz * dz) * h->scale; + return g->costs[u] + sqrt(dx * dx + dy * dy + dz * dz) * h->scale; } -static bool Sooner(const mnavHierarchy* h, int32_t a, int32_t b, mnavPos3 end) +MNAV_NODISCARD static bool Sooner(const mnavHierarchy* h, const mnavGraphSearch* g, int32_t a, + int32_t b, mnavPos3 end) { - double ta = Total(h, a, end); - double tb = Total(h, b, end); + double ta = Total(h, g, a, end); + double tb = Total(h, g, b, end); return ta != tb ? ta < tb : a < b; } -static void Place(mnavHierarchy* h, int32_t at, int32_t u) +static void Place(mnavGraphSearch* g, int32_t at, int32_t u) { - h->heap[at] = u; - h->places[u] = at; + g->heap[at] = u; + g->places[u] = at; } -static void SiftUp(mnavHierarchy* h, int32_t at, mnavPos3 end) +static void SiftUp(const mnavHierarchy* h, mnavGraphSearch* g, int32_t at, mnavPos3 end) { - int32_t u = h->heap[at]; + int32_t u = g->heap[at]; while (at > 0) { int32_t up = (at - 1) / 2; - if (!Sooner(h, u, h->heap[up], end)) + if (!Sooner(h, g, u, g->heap[up], end)) { break; } - Place(h, at, h->heap[up]); + Place(g, at, g->heap[up]); at = up; } - Place(h, at, u); + Place(g, at, u); } -static int32_t Pop(mnavHierarchy* h, mnavPos3 end) +static int32_t Pop(const mnavHierarchy* h, mnavGraphSearch* g, mnavPos3 end) { - int32_t top = h->heap[0]; - int32_t last = h->heap[--h->heapCount]; + int32_t top = g->heap[0]; + int32_t last = g->heap[--g->heapCount]; int32_t at = 0; - while (h->heapCount > 0) + while (g->heapCount > 0) { int32_t child = 2 * at + 1; - if (child >= h->heapCount) + if (child >= g->heapCount) { break; } - if (child + 1 < h->heapCount && Sooner(h, h->heap[child + 1], h->heap[child], end)) + if (child + 1 < g->heapCount && Sooner(h, g, g->heap[child + 1], g->heap[child], end)) { child += 1; } - if (!Sooner(h, h->heap[child], last, end)) + if (!Sooner(h, g, g->heap[child], last, end)) { break; } - Place(h, at, h->heap[child]); + Place(g, at, g->heap[child]); at = child; } - if (h->heapCount > 0) + if (g->heapCount > 0) { - Place(h, at, last); + Place(g, at, last); } - h->places[top] = MNAV_CLOSED; + g->places[top] = MNAV_CLOSED; return top; } // Lowers u's cost to cost by way of parent, opening it if new. -static void Relax(mnavHierarchy* h, int32_t u, double cost, int32_t parent, mnavPos3 end) +static void Relax(const mnavHierarchy* h, mnavGraphSearch* g, int32_t u, double cost, + int32_t parent, mnavPos3 end) { - if (h->places[u] == MNAV_CLOSED || cost >= h->costs[u]) + if (g->places[u] == MNAV_CLOSED || cost >= g->costs[u]) { return; } - h->costs[u] = cost; - h->parents[u] = parent; - if (h->places[u] == MNAV_NO_NODE) + g->costs[u] = cost; + g->parents[u] = parent; + if (g->places[u] == MNAV_NO_NODE) { - Place(h, h->heapCount++, u); + Place(g, g->heapCount++, u); } - SiftUp(h, h->places[u], end); + SiftUp(h, g, g->places[u], end); } // A* over the transitions, opened with the start's costs, to the end, // reached from each transition with its join; whether it got there. -static bool SearchGraph(mnavHierarchy* h, mnavPos3 end) +MNAV_NODISCARD static bool SearchGraph(const mnavHierarchy* h, mnavGraphSearch* g, mnavPos3 end) { int32_t goal = h->transitionCount; - while (h->heapCount > 0) + while (g->heapCount > 0) { - int32_t u = Pop(h, end); + int32_t u = Pop(h, g, end); if (u == goal) { return true; } for (int32_t e = h->firstEdge[u]; e < h->firstEdge[u + 1]; ++e) { - Relax(h, h->edges[e].to, h->costs[u] + h->edges[e].cost, u, end); + Relax(h, g, h->edges[e].to, g->costs[u] + h->edges[e].cost, u, end); } // An infinite join leaves the end as it is. - Relax(h, goal, h->costs[u] + h->joins[u], u, end); + Relax(h, g, goal, g->costs[u] + g->joins[u], u, end); } return false; } -static void ResetGraph(mnavHierarchy* h) +static void ResetGraph(const mnavHierarchy* h, mnavGraphSearch* g) { for (int32_t u = 0; u < h->transitionCount; ++u) { - h->joins[u] = (double)INFINITY; + g->joins[u] = (double)INFINITY; } for (int32_t u = 0; u <= h->transitionCount; ++u) { - h->costs[u] = (double)INFINITY; - h->parents[u] = -1; - h->places[u] = MNAV_NO_NODE; + g->costs[u] = (double)INFINITY; + g->parents[u] = -1; + g->places[u] = MNAV_NO_NODE; } - h->heapCount = 0; + g->heapCount = 0; } // The cost the last search found to a point in a polygon: through the @@ -215,15 +208,16 @@ static double CostToPoint(const mnavQuery* query, const mnavNavmesh* navmesh, in // Joins the end to the off-mesh links landing in its cluster: the walk // from each landing, found by the search from the end. -static void JoinLinks(mnavHierarchy* h, const mnavQuery* query, const mnavNavmesh* navmesh, +static void JoinLinks(const mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, int32_t cluster) { + mnavGraphSearch* g = &query->graph; for (int32_t u = 0; u < h->transitionCount; ++u) { const mnavTransition* t = &h->transitions[u]; if (t->side == 0 && t->cluster == cluster) { - h->joins[u] = CostToPoint(query, navmesh, t->slot, t->polygon, t->at, &h->filter); + g->joins[u] = CostToPoint(query, navmesh, t->slot, t->polygon, t->at, &h->filter); } } } @@ -234,13 +228,14 @@ static void JoinLinks(mnavHierarchy* h, const mnavQuery* query, const mnavNavmes // follows off-mesh links from where they land, so a one-way link inside // the cluster joins the transitions it serves and no other. A search out // of nodes joins what it reached. -static mnavResult Join(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, +static mnavResult Join(const mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end) { + mnavGraphSearch* g = &query->graph; int32_t first = h->clusterOf[startPolygon.slot - 1]; int32_t last = h->clusterOf[endPolygon.slot - 1]; - ResetGraph(h); + ResetGraph(h, g); mnavResult result = mnavSearchCluster(h, query, navmesh, true, last, endPolygon, end); if (result != mnav_success && result != mnav_errorCapacity) { @@ -251,7 +246,7 @@ static mnavResult Join(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* na const mnavTransition* v = &h->transitions[h->leaving[i]]; if (v->reverse >= 0) { - h->joins[v->reverse] = mnavCostTo(query, v); + g->joins[v->reverse] = mnavCostTo(query, v); } } JoinLinks(h, query, navmesh, last); @@ -266,7 +261,7 @@ static mnavResult Join(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* na double cost = mnavCostTo(query, &h->transitions[v]); if (isfinite(cost)) { - Relax(h, v, cost, -1, end); + Relax(h, g, v, cost, -1, end); } } return mnav_success; @@ -288,15 +283,13 @@ static mnavResult Run(mnavQuery* query, const mnavNavmesh* navmesh, bool* foundO // One step of the abstract path: from the node the search stands on, the // way within cluster from across any link of the transition's run, aimed // at the transition. -static mnavResult Step(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, int32_t from, - const mnavTransition* t, bool* foundOut) +static mnavResult Step(const mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, + int32_t from, const mnavTransition* t, bool* foundOut) { - mnavMarkCluster(h, from, 1); - mnavConfineSearch(query, h->inside, true, false); + mnavConfineSearch(query, h->clusterOf, from, true, false); const int32_t goal[4] = {t->slot, t->tag, t->runLow, t->runHigh}; mnavAimSearch(query, t->at, -1, -1, goal); mnavResult result = Run(query, navmesh, foundOut); - mnavMarkCluster(h, from, 0); if (result == mnav_success && *foundOut) { mnavRestartSearch(query, query->search.found); @@ -308,15 +301,16 @@ static mnavResult Step(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* na // one cluster and started over from where the last one ended, so that // the nodes held are the way so far and one cluster's; then the last // cluster to the end. Whether every step found its way. -static mnavResult Refine(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, +static mnavResult Refine(const mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, bool* foundOut) { + mnavGraphSearch* g = &query->graph; // The transitions in order, into the abstract search's heap. int32_t count = 0; - for (int32_t u = h->parents[h->transitionCount]; u >= 0; u = h->parents[u]) + for (int32_t u = g->parents[h->transitionCount]; u >= 0; u = g->parents[u]) { - h->heap[count++] = u; + g->heap[count++] = u; } mnavResult result = mnavBeginPathWork(query, navmesh, &h->filter, startPolygon, start, endPolygon, end); @@ -324,7 +318,7 @@ static mnavResult Refine(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* *foundOut = true; for (int32_t i = count - 1; i >= 0 && result == mnav_success && *foundOut; --i) { - const mnavTransition* t = &h->transitions[h->heap[i]]; + const mnavTransition* t = &h->transitions[g->heap[i]]; result = Step(h, query, navmesh, cluster, t, foundOut); cluster = t->cluster; } @@ -332,16 +326,13 @@ static mnavResult Refine(mnavHierarchy* h, mnavQuery* query, const mnavNavmesh* { return result; } - mnavMarkCluster(h, cluster, 1); - mnavConfineSearch(query, h->inside, false, false); + mnavConfineSearch(query, h->clusterOf, cluster, false, false); const int32_t none[4] = {-1, -1, -1, -1}; mnavAimSearch(query, end, (int32_t)endPolygon.slot - 1, (int32_t)endPolygon.polygon, none); - result = Run(query, navmesh, foundOut); - mnavMarkCluster(h, cluster, 0); - return result; + return Run(query, navmesh, foundOut); } -static bool FinitePoint(mnavPos3 p) +MNAV_NODISCARD static bool FinitePoint(mnavPos3 p) { return isfinite(p.x) && isfinite(p.y) && isfinite(p.z); } @@ -355,6 +346,10 @@ static mnavResult CheckQuery(const mnavQuery* query, const mnavHierarchy* h, { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, start) || !mnavInQueryReach(navmesh, end)) + { + return mnav_errorLimit; + } if (h->navmesh == nullptr) { return mnav_errorState; @@ -373,16 +368,18 @@ static mnavResult CheckQuery(const mnavQuery* query, const mnavHierarchy* h, // Whether two points lie within a cluster's side of each other on the // ground. -static bool Near(const mnavHierarchy* h, const mnavNavmesh* navmesh, mnavPos3 a, mnavPos3 b) +MNAV_NODISCARD static bool Near(const mnavHierarchy* h, const mnavNavmesh* navmesh, mnavPos3 a, + mnavPos3 b) { - double side = (double)h->def.clusterTiles * (double)navmesh->def.cellSize * - (double)navmesh->def.tileCells; + double side = (double)h->def.clusterTiles * (double)navmesh->def.bake.cellSize * + (double)navmesh->def.bake.tileCells; double dx = a.x - b.x; double dz = a.z - b.z; return dx * dx + dz * dz <= side * side; } -static MNAV_NOINLINE mnavResult FindHierarchicalPathWork(mnavQuery* query, mnavHierarchy* hierarchy, +static MNAV_NOINLINE mnavResult FindHierarchicalPathWork(mnavQuery* query, + const mnavHierarchy* hierarchy, const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, @@ -392,7 +389,7 @@ static MNAV_NOINLINE mnavResult FindHierarchicalPathWork(mnavQuery* query, mnavH { return mnav_errorBusy; } - mnavHierarchy* h = hierarchy; + const mnavHierarchy* h = hierarchy; if (pathOut == nullptr) { return mnav_errorInvalid; @@ -402,6 +399,10 @@ static MNAV_NOINLINE mnavResult FindHierarchicalPathWork(mnavQuery* query, mnavH { return result; } + if (h->transitionCount > query->limits.transitions) + { + return mnav_errorCapacity; + } int32_t first = h->clusterOf[startPolygon.slot - 1]; if (first == h->clusterOf[endPolygon.slot - 1]) { @@ -424,7 +425,7 @@ static MNAV_NOINLINE mnavResult FindHierarchicalPathWork(mnavQuery* query, mnavH { return result; } - if (!SearchGraph(h, end)) + if (!SearchGraph(h, &query->graph, end)) { return mnavFindPathWork(query, navmesh, &h->filter, startPolygon, start, endPolygon, end, pathOut); @@ -444,20 +445,23 @@ static MNAV_NOINLINE mnavResult FindHierarchicalPathWork(mnavQuery* query, mnavH } static MNAV_NOINLINE mnavResult -FindHierarchicalPathCounted(mnavQuery* query, mnavHierarchy* hierarchy, const mnavNavmesh* navmesh, - mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, - mnavPos3 end, mnavPath* pathOut) +FindHierarchicalPathCounted(mnavQuery* query, const mnavHierarchy* hierarchy, + const mnavNavmesh* navmesh, mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, mnavPath* pathOut) { - return mnavHostCountInvalid( + mnavPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(query), mnavHostFirst(MNAV_HOST(hierarchy), MNAV_HOST(navmesh))), FindHierarchicalPathWork(query, hierarchy, navmesh, startPolygon, start, endPolygon, end, - pathOut)); + pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFindHierarchicalPath(mnavQuery* query, mnavHierarchy* hierarchy, - const mnavNavmesh* navmesh, mnavPolygonId startPolygon, - mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, - mnavPath* pathOut) +mnavResult mnavQuery_FindHierarchicalPath(mnavQuery* query, const mnavHierarchy* hierarchy, + const mnavNavmesh* navmesh, mnavPolygonId startPolygon, + mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, + mnavPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindHierarchicalPathCounted(query, hierarchy, navmesh, startPolygon, start, diff --git a/third_party/maul-nav/src/holes.c b/third_party/maul-nav/src/holes.c index 6d9977f76..afabb3a02 100644 --- a/third_party/maul-nav/src/holes.c +++ b/third_party/maul-nav/src/holes.c @@ -40,8 +40,9 @@ typedef struct Merger // Whether segment d0 d1 crosses an edge of a contour, skipping the edges // at vertex skip (or none for -1) and edges sharing an end with it. -static bool CrossesContour(const mnavContourVertex* d0, const mnavContourVertex* d1, int32_t skip, - const mnavContourVertex* vertices, int32_t count) +MNAV_NODISCARD static bool CrossesContour(const mnavContourVertex* d0, const mnavContourVertex* d1, + int32_t skip, const mnavContourVertex* vertices, + int32_t count) { for (int32_t k = 0; k < count; ++k) { @@ -59,8 +60,8 @@ static bool CrossesContour(const mnavContourVertex* d0, const mnavContourVertex* } // Whether point p lies in the cone at vertex i of a contour. -static bool InCone(const mnavContourVertex* vertices, int32_t count, int32_t i, - const mnavContourVertex* p) +MNAV_NODISCARD static bool InCone(const mnavContourVertex* vertices, int32_t count, int32_t i, + const mnavContourVertex* p) { return mnavInCone(&vertices[(i + count - 1) % count], &vertices[i], &vertices[(i + 1) % count], p, false); @@ -71,7 +72,7 @@ static const mnavContourVertex* VerticesOf(const mnavContourSet* set, int32_t co return set->vertices + set->contours[contour].first; } -static bool HoleBefore(const Hole* a, const Hole* b) +MNAV_NODISCARD static bool HoleBefore(const Hole* a, const Hole* b) { if (a->minX != b->minX) { @@ -84,7 +85,7 @@ static bool HoleBefore(const Hole* a, const Hole* b) return a->contour < b->contour; } -static bool CandidateBefore(const Candidate* a, const Candidate* b) +MNAV_NODISCARD static bool CandidateBefore(const Candidate* a, const Candidate* b) { return a->distance != b->distance ? a->distance < b->distance : a->vertex < b->vertex; } diff --git a/third_party/maul-nav/src/host_code.h b/third_party/maul-nav/src/host_code.h index 28acb94b4..99e7d79d6 100644 --- a/third_party/maul-nav/src/host_code.h +++ b/third_party/maul-nav/src/host_code.h @@ -67,33 +67,34 @@ static inline void mnavHostLeaveReader(mnavHostCode* host) atomic_fetch_sub_explicit(&host->reading, 1, memory_order_acq_rel); } -static inline bool mnavHostCountMisuse(mnavHostCode* host) +static inline void mnavHostCountMisuse(mnavHostCode* host) { atomic_fetch_add_explicit(&host->misuse, 1, memory_order_relaxed); - return true; } // Whether a call that changes the object comes while host code of either // kind runs on it: then one misuse is counted and the call refuses. -static inline bool mnavHostCalledBack(mnavHostCode* host) +MNAV_NODISCARD static inline bool mnavHostCalledBack(mnavHostCode* host) { if (atomic_load_explicit(&host->writing, memory_order_acquire) == 0 && atomic_load_explicit(&host->reading, memory_order_acquire) == 0) { return false; } - return mnavHostCountMisuse(host); + mnavHostCountMisuse(host); + return true; } // The same for a call that changes nothing a reader reads: refused only // while the object's allocator runs. -static inline bool mnavHostCalledBackWriting(mnavHostCode* host) +MNAV_NODISCARD static inline bool mnavHostCalledBackWriting(mnavHostCode* host) { if (atomic_load_explicit(&host->writing, memory_order_acquire) == 0) { return false; } - return mnavHostCountMisuse(host); + mnavHostCountMisuse(host); + return true; } static inline uint64_t mnavHostMisuse(const mnavHostCode* host) diff --git a/third_party/maul-nav/src/input.c b/third_party/maul-nav/src/input.c index 4737ca5c1..72f143c2a 100644 --- a/third_party/maul-nav/src/input.c +++ b/third_party/maul-nav/src/input.c @@ -5,9 +5,6 @@ #include "input.h" -#include "bake_def.h" -#include "fpenv.h" - #include "maul-nav/bake.h" #include @@ -15,7 +12,26 @@ static mnavInputResult Refuse(mnavResult result, mnavInputElement element, int32_t index) { - return (mnavInputResult){result, element, index}; + return mnavInputElementResult(result, element, index); +} + +mnavInputResult mnavInputElementResult(mnavResult result, mnavInputElement element, int32_t index) +{ + return (mnavInputResult){.result = result, + .kind = mnav_inputNone, + .index = -1, + .element = element, + .elementIndex = index}; +} + +mnavInputResult mnavInputNamed(mnavInputResult check, mnavInputKind kind, int32_t index) +{ + if (check.result != mnav_success) + { + check.kind = kind; + check.index = index; + } + return check; } static mnavInputResult CheckVertex(const mnavBakeDef* def, const mnavTriangleMesh* mesh, int32_t i) @@ -122,29 +138,6 @@ mnavInputResult mnavCheckTriangleMesh(const mnavBakeDef* def, const mnavTriangle return CheckTriangles(mesh); } -static MNAV_NOINLINE mnavInputResult ValidateTriangleMeshWork(const mnavBakeDef* def, - const mnavTriangleMesh* mesh) -{ - if (def == nullptr || mesh == nullptr) - { - return Refuse(mnav_errorInvalid, mnav_elementNone, -1); - } - mnavResult checked = mnavCheckBakeDef(def, nullptr).result; - if (checked != mnav_success) - { - return Refuse(checked, mnav_elementNone, -1); - } - return mnavCheckTriangleMesh(def, mesh); -} - -mnavInputResult mnavValidateTriangleMesh(const mnavBakeDef* def, const mnavTriangleMesh* mesh) -{ - uint64_t env = mnavFloatEnvEnter(); - mnavInputResult result = ValidateTriangleMeshWork(def, mesh); - mnavFloatEnvLeave(env); - return result; -} - mnavInputResult mnavCheckOutline(const mnavBakeDef* def, const mnavOutline* outline) { if (outline->pointCount < 3 || outline->points == nullptr || outline->area >= MNAV_AREA_TYPES) @@ -170,26 +163,3 @@ mnavInputResult mnavCheckOutline(const mnavBakeDef* def, const mnavOutline* outl } return Refuse(mnav_success, mnav_elementNone, -1); } - -static MNAV_NOINLINE mnavInputResult ValidateOutlineWork(const mnavBakeDef* def, - const mnavOutline* outline) -{ - if (def == nullptr || outline == nullptr) - { - return Refuse(mnav_errorInvalid, mnav_elementNone, -1); - } - mnavResult checked = mnavCheckBakeDef(def, nullptr).result; - if (checked != mnav_success) - { - return Refuse(checked, mnav_elementNone, -1); - } - return mnavCheckOutline(def, outline); -} - -mnavInputResult mnavValidateOutline(const mnavBakeDef* def, const mnavOutline* outline) -{ - uint64_t env = mnavFloatEnvEnter(); - mnavInputResult result = ValidateOutlineWork(def, outline); - mnavFloatEnvLeave(env); - return result; -} diff --git a/third_party/maul-nav/src/input.h b/third_party/maul-nav/src/input.h index 5a21207c1..b9b065019 100644 --- a/third_party/maul-nav/src/input.h +++ b/third_party/maul-nav/src/input.h @@ -8,6 +8,14 @@ #include "maul-nav/bake.h" +// An element check's outcome, naming no input: the status and the +// element refused, or mnav_elementNone and -1. +mnavInputResult mnavInputElementResult(mnavResult result, mnavInputElement element, int32_t index); + +// A check's outcome with the input it refused named, by its kind and its +// index in that kind's array; a success passes through unnamed. +mnavInputResult mnavInputNamed(mnavInputResult check, mnavInputKind kind, int32_t index); + // Checks a mesh against a def that is already valid. mnavInputResult mnavCheckTriangleMesh(const mnavBakeDef* def, const mnavTriangleMesh* mesh); diff --git a/third_party/maul-nav/src/linkgen.c b/third_party/maul-nav/src/linkgen.c index ab27fe8ac..e44b44693 100644 --- a/third_party/maul-nav/src/linkgen.c +++ b/third_party/maul-nav/src/linkgen.c @@ -12,6 +12,7 @@ #include "fpenv.h" #include "navmesh.h" #include "nearest.h" +#include "outputs.h" #include "query.h" #include "query_filter.h" @@ -25,7 +26,7 @@ #include #include -static bool Within(float v, float low, float high) +MNAV_NODISCARD static bool Within(float v, float low, float high) { return isfinite(v) && v >= low && v <= high; } @@ -51,7 +52,7 @@ static mnavResult CheckDef(const mnavLinkGenDef* d) past = past || values[k] > most[k]; } if (d->spacing == 0.0f || (d->detour != 0.0f && !Within(d->detour, 1.0f, 1.0e6f)) || - d->dropKind >= MNAV_LINK_KINDS || d->jumpKind >= MNAV_LINK_KINDS) + (uint32_t)d->dropKind >= MNAV_LINK_KINDS || (uint32_t)d->jumpKind >= MNAV_LINK_KINDS) { return mnav_errorInvalid; } @@ -95,12 +96,17 @@ static double Distance(mnavPos3 a, mnavPos3 b) return sqrt(dx * dx + dy * dy + dz * dz); } +MNAV_NODISCARD static bool SameId(mnavPolygonId a, mnavPolygonId b) +{ + return a.slot == b.slot && a.generation == b.generation && a.polygon == b.polygon; +} + // The surface nearest a height at a ground point, among the polygons in a -// box there: the highest from low to high when highest is set, else the -// one nearest the height; whether one was found. A box meeting more -// polygons than the landings stops generation. -static bool Landing(Gen* g, double x, double z, double low, double high, double height, - bool highest, Spot* out) +// box there but the takeoff's: the highest from low to high when highest +// is set, else the one nearest the height; whether one was found. A box +// meeting more polygons than the landings stops generation. +MNAV_NODISCARD static bool Landing(Gen* g, mnavPolygonId takeoff, double x, double z, double low, + double high, double height, bool highest, Spot* out) { mnavPolygonId ids[MNAV_MAX_LINK_LANDINGS]; mnavFound found; @@ -114,10 +120,13 @@ static bool Landing(Gen* g, double x, double z, double low, double high, double return false; } bool any = false; - for (int32_t i = 0; i < found.count; ++i) + for (int32_t i = 0; i < found.polygonCount; ++i) { double h = 0.0; - if (mnavGetHeightWork(g->navmesh, ids[i], x, z, &h) != mnav_success || h < low || h > high) + // With no radius, the first jump box lies on the edge itself, which + // only the takeoff's polygon holds: a landing there is no jump. + if (SameId(ids[i], takeoff) || + mnavGetHeightWork(g->navmesh, ids[i], x, z, &h) != mnav_success || h < low || h > high) { continue; } @@ -134,7 +143,7 @@ static bool Landing(Gen* g, double x, double z, double low, double high, double // Whether the navmesh walks from one spot to the other within the def's // detour times the straight distance: the straight path's length, since // the search's own runs through portal midpoints and may be far longer. -static bool Walks(const Gen* g, Spot from, Spot to) +MNAV_NODISCARD static bool Walks(const Gen* g, Spot from, Spot to) { if (g->def->detour == 0.0f) { @@ -155,8 +164,9 @@ static bool Walks(const Gen* g, Spot from, Spot to) return length <= (double)g->def->detour * Distance(from.point, to.point); } -// Whether both ends of a link lie near an earlier link's. -static bool Near(const Gen* g, mnavPos3 start, mnavPos3 end) +// Whether both ends of a link lie near an earlier link's in the buffer; +// links past it are not held anywhere to compare with. +MNAV_NODISCARD static bool Near(const Gen* g, mnavPos3 start, mnavPos3 end) { double d = (double)g->def->filterDistance; int32_t held = g->count < g->capacity ? g->count : g->capacity; @@ -175,17 +185,17 @@ static bool Near(const Gen* g, mnavPos3 start, mnavPos3 end) // into either that would change what generation reads is refused; the // navmesh is only read, and its mark is the one field this writes // through it. -static bool Clear(const Gen* g, mnavPos3 from, mnavPos3 to) +MNAV_NODISCARD static bool Clear(const Gen* g, mnavPos3 from, mnavPos3 to) { const mnavLinkGenDef* d = g->def; - if (d->clear == nullptr) + if (d->clearance == nullptr) { return true; } mnavHostCode* navmesh = (mnavHostCode*)&g->navmesh->host; mnavHostEnterReader(navmesh); mnavHostEnterReader(&g->query->host); - bool clear = d->clear(d->context, from, to); + bool clear = d->clearance(d->context, from, to); mnavHostLeaveReader(&g->query->host); mnavHostLeaveReader(navmesh); return clear; @@ -200,7 +210,9 @@ static void Keep(Gen* g, Spot from, Spot to, bool drop) } if (g->count == g->total) { - g->result = mnav_errorLimit; + // Past the buffer the count is room, not links kept: a buffer of + // the total either holds them all or reaches the limit itself. + g->result = g->count > g->capacity ? mnav_errorCapacity : mnav_errorLimit; return; } bool twoWay = drop && from.point.y - to.point.y <= (double)d->climbMax; @@ -227,7 +239,7 @@ static void Sample(Gen* g, Spot from, mnavPos3 edge, double nx, double nz) { double out = 2.0 * g->radius + 4.0 * g->cell; Spot to = {{0, 0, 0}, {0.0, -(double)INFINITY, 0.0}}; - if (Landing(g, edge.x + nx * out, edge.z + nz * out, top - (double)d->dropMax, + if (Landing(g, from.polygon, edge.x + nx * out, edge.z + nz * out, top - (double)d->dropMax, top - g->step, top, true, &to)) { Keep(g, from, to, true); @@ -238,8 +250,8 @@ static void Sample(Gen* g, Spot from, mnavPos3 edge, double nx, double nz) out += g->cell) { Spot to = {{0, 0, 0}, {0.0, (double)INFINITY, 0.0}}; - if (Landing(g, edge.x + nx * out, edge.z + nz * out, top - g->step, top + g->step, top, - false, &to)) + if (Landing(g, from.polygon, edge.x + nx * out, edge.z + nz * out, top - g->step, + top + g->step, top, false, &to)) { Keep(g, from, to, false); break; @@ -348,21 +360,21 @@ static mnavResult Generate(mnavQuery* query, const mnavNavmesh* navmesh, return result; } // Samples closer than a cell tell nothing a cell's resolution can. - if (def->spacing < navmesh->def.cellSize) + if (def->spacing < navmesh->def.bake.cellSize) { return mnav_errorInvalid; } // The filter is copied: the searches begun here replace the context's. mnavQueryFilter own = *usable; - const mnavAgentProfile* agent = &navmesh->def.agent; + const mnavAgentProfile* agent = &navmesh->def.bake.agent; Gen g = {query, navmesh, &own, def, (double)agent->radius, (double)agent->stepHeight, - (double)navmesh->def.cellSize, - (double)navmesh->def.cellHeight, + (double)navmesh->def.bake.cellSize, + (double)navmesh->def.bake.cellHeight, linksOut, capacity, 0, @@ -387,40 +399,48 @@ static mnavResult Generate(mnavQuery* query, const mnavNavmesh* navmesh, static MNAV_NOINLINE mnavResult GenerateLinksWork(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavLinkGenDef* def, int32_t tileX0, - int32_t tileZ0, int32_t tileX1, int32_t tileZ1, - mnavLinkDef* linksOut, int32_t capacity, - int32_t* countOut) + const mnavLinkGenDef* def, + const mnavTileRange* tiles, mnavLinkDef* linksOut, + int32_t capacity, int32_t* countOut) { if (query != nullptr && mnavHostCalledBack(&query->host)) { return mnav_errorBusy; } - return Generate(query, navmesh, filter, def, (mnavTileRange){tileX0, tileZ0, tileX1, tileZ1}, - linksOut, capacity, countOut, + mnavTileRange range = + tiles != nullptr ? *tiles : (mnavTileRange){INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}; + return Generate(query, navmesh, filter, def, range, linksOut, capacity, countOut, (mnavLinkGenLimits){MNAV_MAX_LINKS, MNAV_MAX_LINK_LANDINGS}); } static MNAV_NOINLINE mnavResult GenerateLinksCounted(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavLinkGenDef* def, int32_t tileX0, - int32_t tileZ0, int32_t tileX1, int32_t tileZ1, + const mnavLinkGenDef* def, + const mnavTileRange* tiles, mnavLinkDef* linksOut, int32_t capacity, int32_t* countOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - GenerateLinksWork(query, navmesh, filter, def, tileX0, tileZ0, - tileX1, tileZ1, linksOut, capacity, countOut)); + int32_t countMade = 0; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + GenerateLinksWork(query, navmesh, filter, def, tiles, linksOut, capacity, + countOut != nullptr ? &countMade : nullptr)); + // Generation that stopped at a bound keeps and counts the links found, + // as a buffer too small does. + bool defined = result == mnav_errorCapacity || result == mnav_errorLimit; + mnavClearArray(result, defined, linksOut, capacity, sizeof(*linksOut)); + mnavGiveOutput(result, defined, countOut, &countMade, sizeof(countMade)); + return result; } -mnavResult mnavGenerateLinks(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, const mnavLinkGenDef* def, - int32_t tileX0, int32_t tileZ0, int32_t tileX1, int32_t tileZ1, - mnavLinkDef* linksOut, int32_t capacity, int32_t* countOut) +mnavResult mnavQuery_GenerateLinks(mnavQuery* query, const mnavNavmesh* navmesh, + const mnavLinkGenDef* def, const mnavTileRange* tiles, + const mnavQueryFilter* filter, mnavLinkDef* linksOut, + int32_t capacity, int32_t* countOut) { uint64_t env = mnavFloatEnvEnter(); - mnavResult result = GenerateLinksCounted(query, navmesh, filter, def, tileX0, tileZ0, tileX1, - tileZ1, linksOut, capacity, countOut); + mnavResult result = + GenerateLinksCounted(query, navmesh, filter, def, tiles, linksOut, capacity, countOut); mnavFloatEnvLeave(env); return result; } diff --git a/third_party/maul-nav/src/linkgen.h b/third_party/maul-nav/src/linkgen.h index 8f1cadc18..320d1245d 100644 --- a/third_party/maul-nav/src/linkgen.h +++ b/third_party/maul-nav/src/linkgen.h @@ -13,18 +13,9 @@ #include -// A range of tile places, both ends included. -typedef struct mnavTileRange -{ - int32_t x0; - int32_t z0; - int32_t x1; - int32_t z1; -} mnavTileRange; - // Where generation stops with mnav_errorLimit: past links found, or at a // landing box meeting more than landings polygons, at most -// MNAV_MAX_LINK_LANDINGS. mnavGenerateLinks gives MNAV_MAX_LINKS and +// MNAV_MAX_LINK_LANDINGS. mnavQuery_GenerateLinks gives MNAV_MAX_LINKS and // MNAV_MAX_LINK_LANDINGS. typedef struct mnavLinkGenLimits { @@ -32,7 +23,7 @@ typedef struct mnavLinkGenLimits int32_t landings; } mnavLinkGenLimits; -// mnavGenerateLinks over a range, within the limits given. +// mnavQuery_GenerateLinks over a range, within the limits given. mnavResult mnavGenerateLinksWithin(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, const mnavLinkGenDef* def, mnavTileRange range, mnavLinkDef* linksOut, int32_t capacity, diff --git a/third_party/maul-nav/src/modifiers.c b/third_party/maul-nav/src/modifiers.c index 21e5e60be..8018959f3 100644 --- a/third_party/maul-nav/src/modifiers.c +++ b/third_party/maul-nav/src/modifiers.c @@ -9,6 +9,7 @@ #include "allocator.h" #include "fpenv.h" #include "navmesh.h" +#include "outputs.h" #include "maul-nav/bake.h" #include "maul-nav/base.h" @@ -17,7 +18,7 @@ #include #include -mnavTier mnavGetTier(const mnavNavmesh* navmesh) +mnavTier mnavNavmesh_GetTier(const mnavNavmesh* navmesh) { return navmesh != nullptr ? navmesh->def.tier : mnav_tierStatic; } @@ -58,7 +59,7 @@ static MNAV_NOINLINE mnavResult StageAreaCounted(mnavNavmesh* navmesh, mnavPolyg return mnavHostCountInvalid(MNAV_HOST(navmesh), StageAreaWork(navmesh, polygon, area)); } -mnavResult mnavStageArea(mnavNavmesh* navmesh, mnavPolygonId polygon, mnavAreaType area) +mnavResult mnavPolygon_StageArea(mnavNavmesh* navmesh, mnavPolygonId polygon, mnavAreaType area) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = StageAreaCounted(navmesh, polygon, area); @@ -81,9 +82,13 @@ static mnavResult GetAreaWork(const mnavNavmesh* navmesh, mnavPolygonId polygon, return result; } -mnavResult mnavGetArea(const mnavNavmesh* navmesh, mnavPolygonId polygon, mnavAreaType* areaOut) +mnavResult mnavPolygon_GetArea(const mnavNavmesh* navmesh, mnavPolygonId polygon, + mnavAreaType* areaOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), GetAreaWork(navmesh, polygon, areaOut)); + mnavClearOutput(areaOut, sizeof(*areaOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(navmesh), GetAreaWork(navmesh, polygon, areaOut)); + return mnavClearOnFailure(result, areaOut, sizeof(*areaOut)); } void mnavApplyAreas(mnavNavmesh* navmesh) diff --git a/third_party/maul-nav/src/navflow.c b/third_party/maul-nav/src/navflow.c index 5fc66979c..d5496445a 100644 --- a/third_party/maul-nav/src/navflow.c +++ b/third_party/maul-nav/src/navflow.c @@ -17,6 +17,7 @@ #include "invariant.h" #include "navmesh.h" #include "offmesh.h" +#include "outputs.h" #include "query_filter.h" #include "sort.h" @@ -68,7 +69,7 @@ struct mnavNavFlow int32_t polygonCount; mnavQueryFilter filter; // The tiles searched, and whether the work has ended. - mnavNavFlowRegion region; + mnavTileRange region; bool ended; }; @@ -80,8 +81,8 @@ static mnavResult CheckLimits(const mnavNavFlowLimits* l) { return mnav_errorInvalid; } - return l->polygons > MNAV_MAX_NAVFLOW_POLYGONS || l->tiles > MNAV_MAX_NAVFLOW_TILES || - l->links > MNAV_MAX_NAVFLOW_LINKS + return l->polygons > MNAV_MAX_NAV_FLOW_POLYGONS || l->tiles > MNAV_MAX_NAV_FLOW_TILES || + l->links > MNAV_MAX_NAV_FLOW_LINKS ? mnav_errorLimit : mnav_success; } @@ -165,7 +166,7 @@ mnavResult mnavCreateNavFlow(const mnavNavFlowDef* def, mnavNavFlow** fieldOut) return result; } -uint64_t mnavGetNavFlowMisuse(const mnavNavFlow* field) +uint64_t mnavNavFlow_GetMisuse(const mnavNavFlow* field) { return field != nullptr ? mnavHostMisuse(&field->host) : 0; } @@ -194,7 +195,7 @@ void mnavDestroyNavFlow(mnavNavFlow* field) } // The heap, by cost, then by number. -static bool Sooner(const mnavNavFlow* f, int32_t a, int32_t b) +MNAV_NODISCARD static bool Sooner(const mnavNavFlow* f, int32_t a, int32_t b) { return f->ways[a].cost != f->ways[b].cost ? f->ways[a].cost < f->ways[b].cost : a < b; } @@ -325,15 +326,15 @@ static void Polygon(const mnavNavFlow* f, int32_t number, int32_t* slot, int32_t } // Whether a slot's tile lies in the region. -static bool InRegion(const mnavNavFlow* f, const mnavNavmesh* navmesh, int32_t slot) +MNAV_NODISCARD static bool InRegion(const mnavNavFlow* f, const mnavNavmesh* navmesh, int32_t slot) { const mnavSlot* s = &navmesh->slots[slot]; - const mnavNavFlowRegion* r = &f->region; + const mnavTileRange* r = &f->region; return s->tile != nullptr && s->x >= r->x0 && s->x <= r->x1 && s->z >= r->z0 && s->z <= r->z1; } // Whether a polygon may be walked: in the region, of an area included. -static bool Usable(const mnavNavFlow* f, int32_t slot, int32_t polygon) +MNAV_NODISCARD static bool Usable(const mnavNavFlow* f, int32_t slot, int32_t polygon) { return InRegion(f, f->navmesh, slot) && mnavIncludes(&f->filter, f->navmesh->slots[slot].tile->mesh.polygons[polygon].area); @@ -490,6 +491,10 @@ static mnavResult Seed(mnavNavFlow* f, const mnavNavFlowGoal* goals, int32_t goa { return mnav_errorInvalid; } + if (!mnavInQueryReach(f->navmesh, p)) + { + return mnav_errorLimit; + } mnavResult result = mnavCheckPolygon(f->navmesh, goals[i].polygon); if (result != mnav_success) { @@ -509,7 +514,7 @@ static mnavResult Seed(mnavNavFlow* f, const mnavNavFlowGoal* goals, int32_t goa static MNAV_NOINLINE mnavResult BeginNavFlowWork(mnavNavFlow* field, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavNavFlowRegion* region, + const mnavTileRange* region, const mnavNavFlowGoal* goals, int32_t goalCount) { if (field != nullptr && mnavHostCalledBack(&field->host)) @@ -527,9 +532,8 @@ static MNAV_NOINLINE mnavResult BeginNavFlowWork(mnavNavFlow* field, const mnavN { return mnav_errorInvalid; } - field->region = region != nullptr - ? *region - : (mnavNavFlowRegion){INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}; + field->region = + region != nullptr ? *region : (mnavTileRange){INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX}; mnavResult result = mnavCheckFilter(filter, &usable); result = result == mnav_success ? Index(field, navmesh) : result; if (result != mnav_success) @@ -553,16 +557,16 @@ static MNAV_NOINLINE mnavResult BeginNavFlowWork(mnavNavFlow* field, const mnavN static MNAV_NOINLINE mnavResult BeginNavFlowCounted(mnavNavFlow* field, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - const mnavNavFlowRegion* region, + const mnavTileRange* region, const mnavNavFlowGoal* goals, int32_t goalCount) { return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(field), MNAV_HOST(navmesh)), BeginNavFlowWork(field, navmesh, filter, region, goals, goalCount)); } -mnavResult mnavBeginNavFlow(mnavNavFlow* field, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, const mnavNavFlowRegion* region, - const mnavNavFlowGoal* goals, int32_t goalCount) +mnavResult mnavNavFlow_Begin(mnavNavFlow* field, const mnavNavmesh* navmesh, + const mnavTileRange* region, const mnavNavFlowGoal* goals, + int32_t goalCount, const mnavQueryFilter* filter) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BeginNavFlowCounted(field, navmesh, filter, region, goals, goalCount); @@ -624,12 +628,16 @@ static MNAV_NOINLINE mnavResult ContinueNavFlowCounted(mnavNavFlow* field, const mnavNavmesh* navmesh, int32_t polygons, bool* endedOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(field), MNAV_HOST(navmesh)), - ContinueNavFlowWork(field, navmesh, polygons, endedOut)); + bool endedMade = false; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(field), MNAV_HOST(navmesh)), + ContinueNavFlowWork(field, navmesh, polygons, endedOut != nullptr ? &endedMade : nullptr)); + mnavGiveOutput(result, false, endedOut, &endedMade, sizeof(endedMade)); + return result; } -mnavResult mnavContinueNavFlow(mnavNavFlow* field, const mnavNavmesh* navmesh, int32_t polygons, - bool* endedOut) +mnavResult mnavNavFlow_Continue(mnavNavFlow* field, const mnavNavmesh* navmesh, int32_t polygons, + bool* endedOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = ContinueNavFlowCounted(field, navmesh, polygons, endedOut); @@ -654,9 +662,9 @@ static MNAV_NOINLINE mnavResult BuildNavFlowCounted(mnavNavFlow* field, const mn BuildNavFlowWork(field, navmesh, filter, goals, goalCount)); } -mnavResult mnavBuildNavFlow(mnavNavFlow* field, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, const mnavNavFlowGoal* goals, - int32_t goalCount) +mnavResult mnavNavFlow_Build(mnavNavFlow* field, const mnavNavmesh* navmesh, + const mnavNavFlowGoal* goals, int32_t goalCount, + const mnavQueryFilter* filter) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = BuildNavFlowCounted(field, navmesh, filter, goals, goalCount); @@ -711,12 +719,14 @@ static MNAV_NOINLINE mnavResult NavFlowAtCounted(const mnavNavFlow* field, const mnavNavmesh* navmesh, mnavPolygonId polygon, mnavPolygonFlow* flowOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(field), MNAV_HOST(navmesh)), - NavFlowAtWork(field, navmesh, polygon, flowOut)); + mnavClearOutput(flowOut, sizeof(*flowOut)); + mnavResult result = mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(field), MNAV_HOST(navmesh)), + NavFlowAtWork(field, navmesh, polygon, flowOut)); + return mnavClearOnFailure(result, flowOut, sizeof(*flowOut)); } -mnavResult mnavNavFlowAt(const mnavNavFlow* field, const mnavNavmesh* navmesh, - mnavPolygonId polygon, mnavPolygonFlow* flowOut) +mnavResult mnavNavFlow_GetFlow(const mnavNavFlow* field, const mnavNavmesh* navmesh, + mnavPolygonId polygon, mnavPolygonFlow* flowOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = NavFlowAtCounted(field, navmesh, polygon, flowOut); @@ -812,8 +822,8 @@ static MNAV_NOINLINE mnavResult DebugNavFlowCounted(const mnavNavFlow* field, DebugNavFlowWork(field, navmesh, buffer)); } -mnavResult mnavDebugNavFlow(const mnavNavFlow* field, const mnavNavmesh* navmesh, - mnavDebugBuffer* buffer) +mnavResult mnavNavFlow_Debug(const mnavNavFlow* field, const mnavNavmesh* navmesh, + mnavDebugBuffer* buffer) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = DebugNavFlowCounted(field, navmesh, buffer); diff --git a/third_party/maul-nav/src/navmesh.c b/third_party/maul-nav/src/navmesh.c index c1241ce73..23a05e13f 100644 --- a/third_party/maul-nav/src/navmesh.c +++ b/third_party/maul-nav/src/navmesh.c @@ -10,27 +10,95 @@ #include "detail.h" #include "fpenv.h" #include "invariant.h" +#include "outputs.h" #include "polymesh.h" #include "tile.h" #include "maul-nav/bake.h" #include "maul-nav/base.h" #include "maul-nav/navmesh.h" +#include "maul-nav/query.h" #include #include #include -static MNAV_NOINLINE mnavBakeDefResult CreateNavmeshWork(const mnavBakeDef* def, - mnavNavmesh** navmeshOut) +static mnavNavmeshDefResult Refuse(mnavResult result, mnavNavmeshSetting setting) +{ + return (mnavNavmeshDefResult){result, setting, mnav_bakeSettingNone}; +} + +static mnavNavmeshDefResult CheckLimits(const mnavNavmeshLimits* limits) +{ + const struct + { + int32_t value; + int32_t max; + mnavNavmeshSetting setting; + } counts[] = { + {limits->tiles, MNAV_MAX_TILES, mnav_navmeshSettingTiles}, + {limits->tileLinks, MNAV_MAX_TILE_LINKS, mnav_navmeshSettingTileLinks}, + {limits->links, MNAV_MAX_LINKS, mnav_navmeshSettingLinks}, + }; + for (size_t i = 0; i < sizeof(counts) / sizeof(counts[0]); ++i) + { + if (counts[i].value < 1) + { + return Refuse(mnav_errorInvalid, counts[i].setting); + } + if (counts[i].value > counts[i].max) + { + return Refuse(mnav_errorLimit, counts[i].setting); + } + } + if (limits->memoryBytes == 0) + { + return Refuse(mnav_errorInvalid, mnav_navmeshSettingMemoryBytes); + } + return Refuse(mnav_success, mnav_navmeshSettingNone); +} + +// Checks a navmesh def and converts its bake def's agent to cells. +static mnavNavmeshDefResult CheckDef(const mnavNavmeshDef* def, mnavBakeCells* cells) +{ + if (def == nullptr) + { + return Refuse(mnav_errorInvalid, mnav_navmeshSettingNone); + } + if (def->cookie != MNAV_NAVMESH_DEF_COOKIE) + { + return Refuse(mnav_errorInvalid, mnav_navmeshSettingCookie); + } + if (def->version != MNAV_ABI_VERSION) + { + return Refuse(mnav_errorVersion, mnav_navmeshSettingNone); + } + if ((def->allocator.alloc == nullptr) != (def->allocator.free == nullptr)) + { + return Refuse(mnav_errorInvalid, mnav_navmeshSettingAllocator); + } + mnavBakeDefResult bake = mnavCheckBakeDef(&def->bake, cells); + if (bake.result != mnav_success) + { + return (mnavNavmeshDefResult){bake.result, mnav_navmeshSettingBake, bake.setting}; + } + if ((uint32_t)def->tier > mnav_tierDynamic) + { + return Refuse(mnav_errorInvalid, mnav_navmeshSettingTier); + } + return CheckLimits(&def->limits); +} + +static MNAV_NOINLINE mnavNavmeshDefResult CreateNavmeshWork(const mnavNavmeshDef* def, + mnavNavmesh** navmeshOut) { if (navmeshOut == nullptr) { - return (mnavBakeDefResult){mnav_errorInvalid, mnav_settingNone}; + return Refuse(mnav_errorInvalid, mnav_navmeshSettingNone); } *navmeshOut = nullptr; mnavBakeCells cells = {0}; - mnavBakeDefResult checked = mnavCheckBakeDef(def, &cells); + mnavNavmeshDefResult checked = CheckDef(def, &cells); if (checked.result != mnav_success) { return checked; @@ -41,7 +109,7 @@ static MNAV_NOINLINE mnavBakeDefResult CreateNavmeshWork(const mnavBakeDef* def, mnavAllocate(&memory, 1, sizeof(mnavNavmesh), alignof(mnavNavmesh), (void**)&navmesh); if (result != mnav_success) { - return (mnavBakeDefResult){result, mnav_settingNone}; + return Refuse(result, mnav_navmeshSettingNone); } *navmesh = (mnavNavmesh){0}; for (int32_t k = 0; k < MNAV_LINK_KINDS; ++k) @@ -54,18 +122,18 @@ static MNAV_NOINLINE mnavBakeDefResult CreateNavmeshWork(const mnavBakeDef* def, navmesh->memory = memory; navmesh->memory.host = &navmesh->host; *navmeshOut = navmesh; - return (mnavBakeDefResult){mnav_success, mnav_settingNone}; + return Refuse(mnav_success, mnav_navmeshSettingNone); } -mnavBakeDefResult mnavCreateNavmesh(const mnavBakeDef* def, mnavNavmesh** navmeshOut) +mnavNavmeshDefResult mnavCreateNavmesh(const mnavNavmeshDef* def, mnavNavmesh** navmeshOut) { uint64_t env = mnavFloatEnvEnter(); - mnavBakeDefResult result = CreateNavmeshWork(def, navmeshOut); + mnavNavmeshDefResult result = CreateNavmeshWork(def, navmeshOut); mnavFloatEnvLeave(env); return result; } -uint64_t mnavGetNavmeshMisuse(const mnavNavmesh* navmesh) +uint64_t mnavNavmesh_GetMisuse(const mnavNavmesh* navmesh) { return navmesh != nullptr ? mnavHostMisuse(&navmesh->host) : 0; } @@ -98,27 +166,42 @@ void mnavDestroyNavmesh(mnavNavmesh* navmesh) { return; } + // Each tile leaves its slot before it is freed, so a free hook given + // one of its ids finds it stale; each array is emptied before it is + // freed, so a later read finds nothing there rather than released + // memory. mnavMemory* memory = &navmesh->memory; for (int32_t s = 0; s < navmesh->stagedCount; ++s) { - FreeTile(memory, navmesh->staged[s].tile); + mnavTile* tile = navmesh->staged[s].tile; + navmesh->staged[s].tile = nullptr; + FreeTile(memory, tile); } for (int32_t s = 0; s < navmesh->slotCount; ++s) { - FreeTile(memory, navmesh->slots[s].tile); + mnavTile* tile = navmesh->slots[s].tile; + navmesh->slots[s].tile = nullptr; + FreeTile(memory, tile); } + navmesh->stagedCount = 0; mnavRelease(memory, navmesh->staged, (size_t)navmesh->stagedCapacity, sizeof(mnavStaged), alignof(mnavStaged)); + navmesh->linkSlots = 0; mnavRelease(memory, navmesh->links, (size_t)navmesh->linkCapacity, sizeof(mnavOffLink), alignof(mnavOffLink)); + navmesh->areaChangeCount = 0; mnavRelease(memory, navmesh->areaChanges, (size_t)navmesh->areaChangeCapacity, sizeof(mnavAreaChange), alignof(mnavAreaChange)); + navmesh->arrivalCount = 0; mnavRelease(memory, navmesh->arrivals, (size_t)navmesh->arrivalCapacity, sizeof(uint64_t), alignof(uint64_t)); + navmesh->attachmentCount = 0; mnavRelease(memory, navmesh->attachments, (size_t)navmesh->attachmentCapacity, sizeof(uint64_t), alignof(uint64_t)); + navmesh->placeCount = 0; mnavRelease(memory, navmesh->places, (size_t)navmesh->placeCapacity, sizeof(mnavPlace), alignof(mnavPlace)); + navmesh->slotCount = 0; mnavRelease(memory, navmesh->slots, (size_t)navmesh->slotCapacity, sizeof(mnavSlot), alignof(mnavSlot)); // The mark lives in the block being freed: it is set for good, so that @@ -130,7 +213,7 @@ void mnavDestroyNavmesh(mnavNavmesh* navmesh) mnavRelease(&last, navmesh, 1, sizeof(mnavNavmesh), alignof(mnavNavmesh)); } -static bool Before(int32_t ax, int32_t az, int32_t bx, int32_t bz) +MNAV_NODISCARD static bool Before(int32_t ax, int32_t az, int32_t bx, int32_t bz) { return ax != bx ? ax < bx : az < bz; } @@ -194,10 +277,13 @@ static mnavResult GetTileWork(const mnavNavmesh* navmesh, int32_t tileX, int32_t return tile != nullptr ? mnav_success : mnav_errorNotLoaded; } -mnavResult mnavGetTile(const mnavNavmesh* navmesh, int32_t tileX, int32_t tileZ, - mnavTileId* tileOut) +mnavResult mnavNavmesh_GetTile(const mnavNavmesh* navmesh, int32_t tileX, int32_t tileZ, + mnavTileId* tileOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), GetTileWork(navmesh, tileX, tileZ, tileOut)); + mnavClearOutput(tileOut, sizeof(*tileOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(navmesh), GetTileWork(navmesh, tileX, tileZ, tileOut)); + return mnavClearOnFailure(result, tileOut, sizeof(*tileOut)); } static uint32_t FloatBits(float f) @@ -215,9 +301,9 @@ static uint64_t DoubleBits(double d) } // Whether a tile was baked with the navmesh's settings, bit for bit. -static bool Matches(const mnavNavmesh* navmesh, const mnavTileInfo* info) +MNAV_NODISCARD static bool Matches(const mnavNavmesh* navmesh, const mnavTileInfo* info) { - const mnavBakeDef* def = &navmesh->def; + const mnavBakeDef* def = &navmesh->def.bake; const mnavBakeCells* cells = &navmesh->cells; return info->tileCells == def->tileCells && FloatBits(info->cellSize) == FloatBits(def->cellSize) && @@ -304,7 +390,7 @@ static MNAV_NOINLINE mnavTileResult StageTileCounted(mnavNavmesh* navmesh, const return result; } -mnavTileResult mnavStageTile(mnavNavmesh* navmesh, const uint8_t* bytes, size_t size) +mnavTileResult mnavNavmesh_StageTile(mnavNavmesh* navmesh, const uint8_t* bytes, size_t size) { uint64_t env = mnavFloatEnvEnter(); mnavTileResult result = StageTileCounted(navmesh, bytes, size); @@ -325,7 +411,7 @@ static mnavResult StageTileRemovalWork(mnavNavmesh* navmesh, int32_t tileX, int3 return Stage(navmesh, tileX, tileZ, nullptr); } -mnavResult mnavStageTileRemoval(mnavNavmesh* navmesh, int32_t tileX, int32_t tileZ) +mnavResult mnavNavmesh_StageTileRemoval(mnavNavmesh* navmesh, int32_t tileX, int32_t tileZ) { return mnavHostCountInvalid(MNAV_HOST(navmesh), StageTileRemovalWork(navmesh, tileX, tileZ)); } @@ -356,7 +442,7 @@ static Span SpanOf(const mnavPolyMesh* mesh, const mnavPolygon* polygon, int32_t // Whether two edges' heights at u, each interpolated along its edge, lie // within step of each other, compared exactly. -static bool CloseAt(const Span* a, const Span* b, int64_t u, int64_t step) +MNAV_NODISCARD static bool CloseAt(const Span* a, const Span* b, int64_t u, int64_t step) { int64_t da = a->u1 - a->u0; int64_t db = b->u1 - b->u0; @@ -371,7 +457,8 @@ static bool CloseAt(const Span* a, const Span* b, int64_t u, int64_t step) // The part of the side two facing edges share, with heights within the // agent's step at both its ends; false when they share none. -static bool Overlap(const Span* a, const Span* b, int64_t step, int32_t* low, int32_t* high) +MNAV_NODISCARD static bool Overlap(const Span* a, const Span* b, int64_t step, int32_t* low, + int32_t* high) { int64_t lo = a->u0 < a->u1 ? a->u0 : a->u1; int64_t hi = a->u0 < a->u1 ? a->u1 : a->u0; @@ -655,7 +742,7 @@ static void ReleasePlan(mnavMemory* memory, mnavTilePlan* plan) } // Whether slot's tile lies next to, or at, a staged place. -static bool NearChange(const mnavNavmesh* navmesh, const mnavSlot* slot) +MNAV_NODISCARD static bool NearChange(const mnavNavmesh* navmesh, const mnavSlot* slot) { for (int32_t s = 0; s < navmesh->stagedCount; ++s) { @@ -713,33 +800,36 @@ static mnavResult MakePlan(mnavNavmesh* navmesh, mnavTilePlan* plan) return result; } -// Swaps the plan in: frees the tiles it replaced or removed and the links -// it rebuilt, and takes its slots, places and links. +// Swaps the plan in, then frees what it replaced: the old tiles, the +// links of each rebuilt tile, and the old slots and places. Every free runs +// with the plan in place, so a free hook that reads the navmesh back finds +// a removed or replaced tile's ids stale and each rebuilt tile with its new +// links. static void Apply(mnavNavmesh* navmesh, mnavTilePlan* plan) { mnavMemory* memory = &navmesh->memory; - for (int32_t s = 0; s < navmesh->slotCount; ++s) - { - if (navmesh->slots[s].tile != plan->slots[s].tile) - { - FreeTile(memory, navmesh->slots[s].tile); - } - } + // Each relink takes the links its tile gives up. for (int32_t r = 0; r < plan->relinkCount; ++r) { mnavRelink* relink = &plan->relinks[r]; mnavTile* tile = plan->slots[relink->slot].tile; - ReleaseLinks(memory, tile); + mnavRelink old = {relink->slot, tile->links, tile->linkCount, tile->firstLink}; tile->links = relink->links; tile->linkCount = relink->linkCount; tile->firstLink = relink->firstLink; - } - mnavRelease(memory, plan->relinks, (size_t)plan->relinkCapacity, sizeof(mnavRelink), - alignof(mnavRelink)); - mnavRelease(memory, navmesh->slots, (size_t)navmesh->slotCapacity, sizeof(mnavSlot), - alignof(mnavSlot)); - mnavRelease(memory, navmesh->places, (size_t)navmesh->placeCapacity, sizeof(mnavPlace), - alignof(mnavPlace)); + *relink = old; + } + const mnavTilePlan old = { + .slots = navmesh->slots, + .slotCount = navmesh->slotCount, + .slotCapacity = navmesh->slotCapacity, + .places = navmesh->places, + .placeCount = navmesh->placeCount, + .placeCapacity = navmesh->placeCapacity, + .relinks = plan->relinks, + .relinkCount = plan->relinkCount, + .relinkCapacity = plan->relinkCapacity, + }; navmesh->slots = plan->slots; navmesh->slotCount = plan->slotCount; navmesh->slotCapacity = plan->slotCapacity; @@ -748,6 +838,27 @@ static void Apply(mnavNavmesh* navmesh, mnavTilePlan* plan) navmesh->placeCapacity = plan->placeCapacity; navmesh->stagedCount = 0; *plan = (mnavTilePlan){0}; + for (int32_t s = 0; s < old.slotCount; ++s) + { + if (old.slots[s].tile != navmesh->slots[s].tile) + { + FreeTile(memory, old.slots[s].tile); + } + } + for (int32_t r = 0; r < old.relinkCount; ++r) + { + const mnavRelink* relink = &old.relinks[r]; + int32_t polygons = navmesh->slots[relink->slot].tile->mesh.polygonCount; + mnavRelease(memory, relink->links, (size_t)relink->linkCount, sizeof(mnavLink), + alignof(mnavLink)); + mnavRelease(memory, relink->firstLink, (size_t)polygons + 1, sizeof(int32_t), + alignof(int32_t)); + } + mnavRelease(memory, old.relinks, (size_t)old.relinkCapacity, sizeof(mnavRelink), + alignof(mnavRelink)); + mnavRelease(memory, old.slots, (size_t)old.slotCapacity, sizeof(mnavSlot), alignof(mnavSlot)); + mnavRelease(memory, old.places, (size_t)old.placeCapacity, sizeof(mnavPlace), + alignof(mnavPlace)); } mnavResult mnavPlanTiles(mnavNavmesh* navmesh, mnavTilePlan* plan) @@ -781,7 +892,7 @@ void mnavApplyTiles(mnavNavmesh* navmesh, mnavTilePlan* plan) mnavFrame mnavFrameOf(const mnavNavmesh* navmesh, int32_t x, int32_t z) { - const mnavBakeDef* def = &navmesh->def; + const mnavBakeDef* def = &navmesh->def.bake; double size = (double)def->tileCells * (double)def->cellSize; return (mnavFrame){def->origin.x + (double)x * size, def->origin.z + (double)z * size, def->origin.y, (double)def->cellSize, (double)def->cellHeight}; @@ -805,3 +916,15 @@ mnavResult mnavCheckPolygon(const mnavNavmesh* navmesh, mnavPolygonId id) } return id.polygon < (uint32_t)slot->tile->mesh.polygonCount ? mnav_success : mnav_errorInvalid; } + +bool mnavInQueryReachGround(const mnavNavmesh* navmesh, double x, double z) +{ + const mnavPos3* o = &navmesh->def.bake.origin; + return fabs(x - o->x) <= MNAV_MAX_QUERY_DISTANCE && fabs(z - o->z) <= MNAV_MAX_QUERY_DISTANCE; +} + +bool mnavInQueryReach(const mnavNavmesh* navmesh, mnavPos3 p) +{ + return mnavInQueryReachGround(navmesh, p.x, p.z) && + fabs(p.y - navmesh->def.bake.origin.y) <= MNAV_MAX_QUERY_DISTANCE; +} diff --git a/third_party/maul-nav/src/navmesh.h b/third_party/maul-nav/src/navmesh.h index 063598530..ea914d89e 100644 --- a/third_party/maul-nav/src/navmesh.h +++ b/third_party/maul-nav/src/navmesh.h @@ -109,7 +109,7 @@ typedef struct mnavOffLink struct mnavNavmesh { - mnavBakeDef def; + mnavNavmeshDef def; mnavBakeCells cells; mnavMemory memory; // Marked while its allocator or a link generation's clearance test @@ -190,6 +190,15 @@ mnavFrame mnavFrameOf(const mnavNavmesh* navmesh, int32_t x, int32_t z); // tile was replaced or removed. mnavResult mnavCheckPolygon(const mnavNavmesh* navmesh, mnavPolygonId id); +// Whether a query's point lies within MNAV_MAX_QUERY_DISTANCE of the +// navmesh's origin on each axis; a point that is not finite does not. +// Every navmesh query that takes a point, an end or a center checks it, +// after the point's own checks, and refuses one past it with +// mnav_errorLimit; mnavInQueryReachGround checks x and z alone, for a +// point whose height the query never reads. +MNAV_NODISCARD bool mnavInQueryReach(const mnavNavmesh* navmesh, mnavPos3 p); +MNAV_NODISCARD bool mnavInQueryReachGround(const mnavNavmesh* navmesh, double x, double z); + // A mesh vertex's world position in a tile's frame. Inline: the searches // call it for every portal they open. static inline mnavPos3 mnavVertexWorld(const mnavFrame* f, const mnavMeshVertex* v) diff --git a/third_party/maul-nav/src/nearest.c b/third_party/maul-nav/src/nearest.c index b0f796393..eef2bf22d 100644 --- a/third_party/maul-nav/src/nearest.c +++ b/third_party/maul-nav/src/nearest.c @@ -8,6 +8,7 @@ #include "detail.h" #include "fpenv.h" #include "navmesh.h" +#include "outputs.h" #include "polymesh.h" #include "query_filter.h" #include "raster.h" @@ -105,8 +106,8 @@ static void Corners(const mnavTile* tile, const mnavDetailPart* part, int32_t t, // The height of the first triangle holding q, as the full scan finds it // when no triangle before it comes within rounding of q: only such a one // could reach q at distance 0 first. False when the scan must decide. -static bool HoldingHeight(const mnavTile* tile, const mnavDetailPart* part, Flat q, - double* heightOut) +MNAV_NODISCARD static bool HoldingHeight(const mnavTile* tile, const mnavDetailPart* part, Flat q, + double* heightOut) { for (int32_t t = 0; t < part->triangleCount; ++t) { @@ -207,7 +208,8 @@ typedef struct Search // Whether polygon p of a tile can lie in the box: its vertices' bounds on // the ground and its detail's heights. -static bool InBox(const Search* s, const mnavFrame* f, const mnavTile* tile, int32_t p) +MNAV_NODISCARD static bool InBox(const Search* s, const mnavFrame* f, const mnavTile* tile, + int32_t p) { const mnavPolygon* polygon = &tile->mesh.polygons[p]; double minX = (double)INFINITY; @@ -322,22 +324,26 @@ static int32_t FirstColumn(const mnavNavmesh* navmesh, int64_t x0) return low; } -static bool Finite(double v) +MNAV_NODISCARD static bool Finite(double v) { return isfinite(v); } +bool mnavGoodHalfExtents(mnavVec3 half) +{ + return half.x >= 0.0f && half.y >= 0.0f && half.z >= 0.0f && isfinite(half.x) && + isfinite(half.y) && isfinite(half.z); +} + // Whether a box is well formed: finite, with half sizes at least 0. -static bool GoodBox(mnavPos3 point, mnavVec3 half) +MNAV_NODISCARD static bool GoodBox(mnavPos3 point, mnavVec3 half) { - return Finite(point.x) && Finite(point.y) && Finite(point.z) && half.x >= 0.0f && - half.y >= 0.0f && half.z >= 0.0f && isfinite(half.x) && isfinite(half.y) && - isfinite(half.z); + return Finite(point.x) && Finite(point.y) && Finite(point.z) && mnavGoodHalfExtents(half); } mnavCover mnavCoverOf(const mnavNavmesh* navmesh, mnavPos3 point, mnavPos3 half) { - const mnavBakeDef* def = &navmesh->def; + const mnavBakeDef* def = &navmesh->def.bake; double size = (double)def->tileCells * (double)def->cellSize; int64_t reach = MNAV_MAX_EXTENT_CELLS / def->tileCells + 1; mnavCover c = {0}; @@ -355,6 +361,10 @@ mnavResult mnavFindPolygonsWork(const mnavNavmesh* navmesh, const mnavQueryFilte { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, center)) + { + return mnav_errorLimit; + } const mnavQueryFilter* usable = nullptr; mnavResult checked = mnavCheckFilter(filter, &usable); if (checked != mnav_success) @@ -402,14 +412,19 @@ static MNAV_NOINLINE mnavResult FindPolygonsCounted(const mnavNavmesh* navmesh, mnavPolygonId* polygonsOut, int32_t capacity, mnavFound* foundOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), - mnavFindPolygonsWork(navmesh, filter, center, halfExtents, - polygonsOut, capacity, foundOut)); + mnavClearOutput(foundOut, sizeof(*foundOut)); + mnavResult result = mnavHostCountInvalid( + MNAV_HOST(navmesh), mnavFindPolygonsWork(navmesh, filter, center, halfExtents, polygonsOut, + capacity, foundOut)); + mnavClearArray(result, result == mnav_errorCapacity, polygonsOut, capacity, + sizeof(*polygonsOut)); + return mnavClearUnlessCapacity(result, foundOut, sizeof(*foundOut)); } -mnavResult mnavFindPolygons(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - mnavPos3 center, mnavVec3 halfExtents, mnavPolygonId* polygonsOut, - int32_t capacity, mnavFound* foundOut) +mnavResult mnavNavmesh_FindPolygons(const mnavNavmesh* navmesh, mnavPos3 center, + mnavVec3 halfExtents, const mnavQueryFilter* filter, + mnavPolygonId* polygonsOut, int32_t capacity, + mnavFound* foundOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -425,6 +440,10 @@ mnavResult mnavFindNearestWork(const mnavNavmesh* navmesh, const mnavQueryFilter { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, point)) + { + return mnav_errorLimit; + } const mnavQueryFilter* usable = nullptr; mnavResult checked = mnavCheckFilter(filter, &usable); if (checked != mnav_success) @@ -432,7 +451,7 @@ mnavResult mnavFindNearestWork(const mnavNavmesh* navmesh, const mnavQueryFilter return checked; } *nearestOut = (mnavNearest){0}; - const mnavBakeDef* def = &navmesh->def; + const mnavBakeDef* def = &navmesh->def.bake; mnavPos3 half = {(double)halfExtents.x, (double)halfExtents.y, (double)halfExtents.z}; Search s = {navmesh, point, @@ -470,12 +489,14 @@ static MNAV_NOINLINE mnavResult FindNearestCounted(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPos3 point, mnavVec3 halfExtents, mnavNearest* nearestOut) { - return mnavHostCountInvalid( + mnavClearOutput(nearestOut, sizeof(*nearestOut)); + mnavResult result = mnavHostCountInvalid( MNAV_HOST(navmesh), mnavFindNearestWork(navmesh, filter, point, halfExtents, nearestOut)); + return mnavClearOnFailure(result, nearestOut, sizeof(*nearestOut)); } -mnavResult mnavFindNearest(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - mnavPos3 point, mnavVec3 halfExtents, mnavNearest* nearestOut) +mnavResult mnavNavmesh_FindNearest(const mnavNavmesh* navmesh, mnavPos3 point, mnavVec3 halfExtents, + const mnavQueryFilter* filter, mnavNearest* nearestOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindNearestCounted(navmesh, filter, point, halfExtents, nearestOut); diff --git a/third_party/maul-nav/src/nearest.h b/third_party/maul-nav/src/nearest.h index 0a53949ed..d6dfee6b7 100644 --- a/third_party/maul-nav/src/nearest.h +++ b/third_party/maul-nav/src/nearest.h @@ -32,6 +32,10 @@ double mnavSurfaceHeight(const mnavNavmesh* navmesh, int32_t slot, int32_t polyg // A detail vertex of the tile in slot, in world meters. mnavPos3 mnavDetailWorld(const mnavNavmesh* navmesh, int32_t slot, const mnavDetailVertex* v); +// Whether a box's half sizes are finite and at least 0, as every call +// taking a box checks them. +MNAV_NODISCARD bool mnavGoodHalfExtents(mnavVec3 half); + // The calls without the misuse count, for calls inside the library, // which run in its floating-point environment already. mnavResult mnavFindPolygonsWork(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, diff --git a/third_party/maul-nav/src/obstacle.c b/third_party/maul-nav/src/obstacle.c index de03f0c2b..715b0773b 100644 --- a/third_party/maul-nav/src/obstacle.c +++ b/third_party/maul-nav/src/obstacle.c @@ -27,12 +27,12 @@ // count as covered by it. #define COVERED 1e-9 -static bool HasNaN(mnavPos2 p) +MNAV_NODISCARD static bool HasNaN(mnavPos2 p) { return isnan(p.x) || isnan(p.y); } -static bool Within(mnavPos2 p, double bound) +MNAV_NODISCARD static bool Within(mnavPos2 p, double bound) { return fabs(p.x) <= bound && fabs(p.y) <= bound; } @@ -49,13 +49,13 @@ static double LeftOf(mnavPos2 a, mnavPos2 b, mnavPos2 c) // its MNAV_MAX_AVOIDANCE_ bound, an infinity included. static mnavResult CheckObstacle(const mnavObstacle* o) { - if (o->points == nullptr || o->pointCount < 1 || HasNaN(o->velocity) || isnan(o->radius) || - (o->pointCount == 1) != (o->radius > 0.0) || o->radius < 0.0) + if (o->points == nullptr || o->pointCount < 1 || HasNaN(mnavWide2(o->velocity)) || + isnan(o->radius) || (o->pointCount == 1) != (o->radius > 0.0) || o->radius < 0.0) { return mnav_errorInvalid; } - bool within = - Within(o->velocity, MNAV_MAX_AVOIDANCE_SPEED) && o->radius <= MNAV_MAX_AVOIDANCE_RADIUS; + bool within = Within(mnavWide2(o->velocity), MNAV_MAX_AVOIDANCE_SPEED) && + o->radius <= MNAV_MAX_AVOIDANCE_RADIUS; for (int32_t i = 0; i < o->pointCount; ++i) { mnavPos2 a = o->points[i]; @@ -104,8 +104,8 @@ mnavResult mnavBuildObstacles(const mnavObstacle* obstacles, int32_t count, c > 1 ? mnavNormalize2(mnavSub2(next, here)) : (mnavPos2){0.0, 0.0}; bool convex = c < 3 || LeftOf(previous, here, next) >= 0.0; vertices[n + i] = (mnavObstacleVertex){ - here, direction, o->velocity, o->radius, o->id, - i, n + (i + 1) % c, n + (i + c - 1) % c, convex, o->layers}; + here, direction, mnavWide2(o->velocity), o->radius, o->id, + i, n + (i + 1) % c, n + (i + c - 1) % c, convex, o->layers}; } n += c; } @@ -113,7 +113,8 @@ mnavResult mnavBuildObstacles(const mnavObstacle* obstacles, int32_t count, return mnav_success; } -static bool NearBefore(const mnavObstacleVertex* vertices, mnavObstacleNear a, mnavObstacleNear b) +MNAV_NODISCARD static bool NearBefore(const mnavObstacleVertex* vertices, mnavObstacleNear a, + mnavObstacleNear b) { if (a.distance != b.distance) { @@ -194,7 +195,7 @@ static double Entries(const mnavObstacleVertex* vertices, int32_t count, double return total; } -static bool CellBefore(const mnavObstacleCell* a, const mnavObstacleCell* b) +MNAV_NODISCARD static bool CellBefore(const mnavObstacleCell* a, const mnavObstacleCell* b) { if (a->x != b->x) { @@ -398,7 +399,8 @@ typedef struct View // Whether the lines before already cover the edge from o1 to o2: both // its cut-off points lie beyond one of them. -static bool Covered(const View* w, int32_t o1, int32_t o2, const mnavLine* lines, int32_t count) +MNAV_NODISCARD static bool Covered(const View* w, int32_t o1, int32_t o2, const mnavLine* lines, + int32_t count) { const mnavObstacleVertex* v1 = &w->vertices[o1]; mnavPos2 shift = v1->velocity; @@ -460,7 +462,7 @@ typedef struct Legs bool rightForeign; } Legs; -static bool FindLegs(const View* w, double s, double distSqLine, Legs* legs) +MNAV_NODISCARD static bool FindLegs(const View* w, double s, double distSqLine, Legs* legs) { const mnavObstacleVertex* vs = w->vertices; double radiusSq = w->radius * w->radius; @@ -513,7 +515,7 @@ static bool FindLegs(const View* w, double s, double distSqLine, Legs* legs) // Projects the velocity on the edge's truncated velocity obstacle and // writes the line, or returns false when it lands on a foreign leg. -static bool Project(const View* w, const Legs* legs, mnavLine* line) +MNAV_NODISCARD static bool Project(const View* w, const Legs* legs, mnavLine* line) { const mnavObstacleVertex* vs = w->vertices; mnavPos2 leftCutoff = mnavScale2(mnavSub2(vs[legs->o1].point, w->position), w->inverse); @@ -563,8 +565,8 @@ static bool Project(const View* w, const Legs* legs, mnavLine* line) // The line of an edge the agent already touches, or false when none: a // vertex RVO2 leaves to its neighbour, or no collision. -static bool Touching(const View* w, int32_t o1, int32_t o2, double s, double distSqLine, - bool* handled, mnavLine* line) +MNAV_NODISCARD static bool Touching(const View* w, int32_t o1, int32_t o2, double s, + double distSqLine, bool* handled, mnavLine* line) { const mnavObstacleVertex* vs = w->vertices; mnavPos2 rel1 = mnavSub2(vs[o1].point, w->position); @@ -594,7 +596,7 @@ static bool Touching(const View* w, int32_t o1, int32_t o2, double s, double dis } // The line of the edge from vertex o1, in the agent's frame, or false. -static bool EdgeLine(const View* w, int32_t o1, mnavLine* line) +MNAV_NODISCARD static bool EdgeLine(const View* w, int32_t o1, mnavLine* line) { const mnavObstacleVertex* vs = w->vertices; int32_t o2 = vs[o1].next; @@ -626,8 +628,8 @@ static mnavLine CircleLine(const mnavAgent* agent, const mnavObstacleVertex* o, double distanceSq = mnavDot2(rel, rel); if (distanceSq > combined * combined) { - return mnavPairLine(agent->position, agent->velocity, o->point, o->velocity, combined, 1.0, - horizon, step, agent->id < o->id); + return mnavPairLine(agent->position, mnavWide2(agent->velocity), o->point, o->velocity, + combined, 1.0, horizon, step, agent->id < o->id); } // On the center itself no way is out; a fixed one keeps it // deterministic. @@ -650,8 +652,8 @@ int32_t mnavObstacleLines(const mnavAgent* agent, const mnavObstacleVertex* vert lines[count++] = CircleLine(agent, o, horizon, step); continue; } - View w = {vertices, agent->position, mnavSub2(agent->velocity, o->velocity), agent->radius, - 1.0 / horizon}; + View w = {vertices, agent->position, mnavSub2(mnavWide2(agent->velocity), o->velocity), + agent->radius, 1.0 / horizon}; if (Covered(&w, near[k].vertex, o->next, lines, count)) { continue; diff --git a/third_party/maul-nav/src/offmesh.c b/third_party/maul-nav/src/offmesh.c index fea68dc23..b5d3f8b61 100644 --- a/third_party/maul-nav/src/offmesh.c +++ b/third_party/maul-nav/src/offmesh.c @@ -11,6 +11,7 @@ #include "invariant.h" #include "navmesh.h" #include "nearest.h" +#include "outputs.h" #include "sort.h" #include "maul-nav/bake.h" @@ -48,7 +49,7 @@ mnavAttachment mnavAttachmentOf(uint64_t key) (int32_t)(key >> 1 & ((1u << LINK_BITS) - 1)), (key & 1u) != 0}; } -static bool HasNaN(mnavPos3 p) +MNAV_NODISCARD static bool HasNaN(mnavPos3 p) { return isnan(p.x) || isnan(p.y) || isnan(p.z); } @@ -77,9 +78,9 @@ static mnavResult Find(const mnavNavmesh* navmesh, mnavLinkId id, int32_t* slotO // Whether a point lies within the extent bake input may have round the // navmesh's origin, so that the arithmetic on links stays finite. -static bool InExtent(const mnavNavmesh* navmesh, mnavPos3 p) +MNAV_NODISCARD static bool InExtent(const mnavNavmesh* navmesh, mnavPos3 p) { - const mnavBakeDef* def = &navmesh->def; + const mnavBakeDef* def = &navmesh->def.bake; double ground = (double)def->cellSize * (double)MNAV_MAX_EXTENT_CELLS; double vertical = (double)def->cellHeight * (double)MNAV_MAX_HEIGHT_CELLS; return fabs(p.x - def->origin.x) <= ground && fabs(p.z - def->origin.z) <= ground && @@ -97,7 +98,7 @@ static mnavResult CheckDef(const mnavNavmesh* navmesh, const mnavLinkDef* def) return mnav_errorLimit; } if (!(def->radius >= 0.0f && def->cost >= 0.0f && def->width >= 0.0f) || - def->kind >= MNAV_LINK_KINDS) + (uint32_t)def->kind >= MNAV_LINK_KINDS) { return mnav_errorInvalid; } @@ -124,8 +125,8 @@ static int32_t Crossings(const mnavNavmesh* navmesh, const mnavLinkDef* def) { return 1; } - double spacing = navmesh->def.agent.radius > 0.0f ? (double)navmesh->def.agent.radius - : (double)navmesh->def.cellSize; + double spacing = navmesh->def.bake.agent.radius > 0.0f ? (double)navmesh->def.bake.agent.radius + : (double)navmesh->def.bake.cellSize; double gaps = ceil((double)def->width / spacing); return gaps >= (double)(MNAV_MAX_LINK_CROSSINGS - 1) ? MNAV_MAX_LINK_CROSSINGS : 1 + (int32_t)gaps; @@ -230,7 +231,10 @@ static MNAV_NOINLINE mnavResult StageLinkWork(mnavNavmesh* navmesh, const mnavLi static MNAV_NOINLINE mnavResult StageLinkCounted(mnavNavmesh* navmesh, const mnavLinkDef* def, mnavLinkId* linkOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), StageLinkWork(navmesh, def, linkOut)); + mnavClearOutput(linkOut, sizeof(*linkOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(navmesh), StageLinkWork(navmesh, def, linkOut)); + return mnavClearOnFailure(result, linkOut, sizeof(*linkOut)); } mnavResult mnavStageLink(mnavNavmesh* navmesh, const mnavLinkDef* def, mnavLinkId* linkOut) @@ -333,7 +337,7 @@ static MNAV_NOINLINE mnavResult StageLinkEnabledCounted(mnavNavmesh* navmesh, mn return mnavHostCountInvalid(MNAV_HOST(navmesh), StageLinkEnabledWork(navmesh, id, enabled)); } -mnavResult mnavStageLinkEnabled(mnavNavmesh* navmesh, mnavLinkId id, bool enabled) +mnavResult mnavLink_StageEnable(mnavNavmesh* navmesh, mnavLinkId id, bool enabled) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = StageLinkEnabledCounted(navmesh, id, enabled); @@ -374,17 +378,20 @@ static MNAV_NOINLINE mnavResult GetLinkWork(const mnavNavmesh* navmesh, mnavLink } *stateOut = best >= 0 ? navmesh->links[best].state : (mnavLinkState){0}; stateOut->enabled = navmesh->links[s].state.enabled; - stateOut->crossings = attached; + stateOut->crossingCount = attached; return mnav_success; } static MNAV_NOINLINE mnavResult GetLinkCounted(const mnavNavmesh* navmesh, mnavLinkId id, mnavLinkState* stateOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), GetLinkWork(navmesh, id, stateOut)); + mnavClearOutput(stateOut, sizeof(*stateOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(navmesh), GetLinkWork(navmesh, id, stateOut)); + return mnavClearOnFailure(result, stateOut, sizeof(*stateOut)); } -mnavResult mnavGetLink(const mnavNavmesh* navmesh, mnavLinkId id, mnavLinkState* stateOut) +mnavResult mnavLink_GetState(const mnavNavmesh* navmesh, mnavLinkId id, mnavLinkState* stateOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = GetLinkCounted(navmesh, id, stateOut); @@ -392,22 +399,57 @@ mnavResult mnavGetLink(const mnavNavmesh* navmesh, mnavLinkId id, mnavLinkState* return result; } +static MNAV_NOINLINE mnavResult IsLinkEnabledWork(const mnavNavmesh* navmesh, mnavLinkId id, + bool* enabledOut) +{ + if (navmesh == nullptr || enabledOut == nullptr) + { + return mnav_errorInvalid; + } + int32_t s = -1; + mnavResult result = Find(navmesh, id, &s); + if (result == mnav_success && navmesh->links[s].phase == MNAV_LINK_ADDING) + { + result = mnav_errorState; + } + // The committed state; the link's own flag holds a staged toggle. + *enabledOut = result == mnav_success && navmesh->links[s].state.enabled; + return result; +} + +static MNAV_NOINLINE mnavResult IsLinkEnabledCounted(const mnavNavmesh* navmesh, mnavLinkId id, + bool* enabledOut) +{ + mnavClearOutput(enabledOut, sizeof(*enabledOut)); + mnavResult result = + mnavHostCountInvalid(MNAV_HOST(navmesh), IsLinkEnabledWork(navmesh, id, enabledOut)); + return mnavClearOnFailure(result, enabledOut, sizeof(*enabledOut)); +} + +mnavResult mnavLink_IsEnabled(const mnavNavmesh* navmesh, mnavLinkId id, bool* enabledOut) +{ + uint64_t env = mnavFloatEnvEnter(); + mnavResult result = IsLinkEnabledCounted(navmesh, id, enabledOut); + mnavFloatEnvLeave(env); + return result; +} + // The half sizes of the box a link end snaps within: its radius on the // ground and the agent's step in height. static mnavPos3 SnapHalf(const mnavNavmesh* navmesh, float radius) { double step = - (double)(float)((double)navmesh->cells.agentStep * (double)navmesh->def.cellHeight); + (double)(float)((double)navmesh->cells.agentStep * (double)navmesh->def.bake.cellHeight); return (mnavPos3){(double)radius, step, (double)radius}; } -static bool Covers(mnavCover c, int32_t x, int32_t z) +MNAV_NODISCARD static bool Covers(mnavCover c, int32_t x, int32_t z) { return x >= c.x0 && x <= c.x1 && z >= c.z0 && z <= c.z1; } // Whether the snap box round a point covers a place the commit changes. -static bool Changed(const mnavNavmesh* navmesh, mnavPos3 p, float radius) +MNAV_NODISCARD static bool Changed(const mnavNavmesh* navmesh, mnavPos3 p, float radius) { mnavCover c = mnavCoverOf(navmesh, p, SnapHalf(navmesh, radius)); for (int32_t s = 0; s < navmesh->stagedCount; ++s) @@ -485,8 +527,8 @@ void mnavDropAttachmentPlan(mnavNavmesh* navmesh, mnavAttachmentPlan* plan) // Snaps an end to the nearest polygon within the radius on the ground and // the agent's step in height. -static bool Snap(const mnavNavmesh* navmesh, mnavPos3 p, float radius, mnavPolygonId* polygon, - mnavPos3* at) +MNAV_NODISCARD static bool Snap(const mnavNavmesh* navmesh, mnavPos3 p, float radius, + mnavPolygonId* polygon, mnavPos3* at) { mnavPos3 half = SnapHalf(navmesh, radius); mnavNearest n; @@ -510,7 +552,7 @@ static void Resnap(mnavNavmesh* navmesh, mnavOffLink* link) state.attached = Snap(navmesh, link->def.start, link->def.radius, &state.startPolygon, &state.start) && Snap(navmesh, link->def.end, link->def.radius, &state.endPolygon, &state.end); - state.crossings = state.attached ? 1 : 0; + state.crossingCount = state.attached ? 1 : 0; link->state = state.attached ? state : (mnavLinkState){0}; } @@ -569,10 +611,13 @@ void mnavApplyAttachments(mnavNavmesh* navmesh, mnavAttachmentPlan* plan) } navmesh->linksHeld = held; navmesh->linksPending = 0; - mnavRelease(&navmesh->memory, navmesh->attachments, (size_t)navmesh->attachmentCapacity, - sizeof(uint64_t), alignof(uint64_t)); - mnavRelease(&navmesh->memory, navmesh->arrivals, (size_t)navmesh->arrivalCapacity, - sizeof(uint64_t), alignof(uint64_t)); + // The new arrays go in, and the old ones are freed only once the + // navmesh is whole again, so a free hook never finds it holding a + // released block. + uint64_t* attachments = navmesh->attachments; + int32_t attachmentCapacity = navmesh->attachmentCapacity; + uint64_t* arrivals = navmesh->arrivals; + int32_t arrivalCapacity = navmesh->arrivalCapacity; navmesh->attachments = plan->keys; navmesh->attachmentCapacity = plan->capacity; navmesh->arrivals = plan->arrivals; @@ -593,7 +638,6 @@ void mnavApplyAttachments(mnavNavmesh* navmesh, mnavAttachmentPlan* plan) count = Attach(navmesh, s, navmesh->attachments, count); } } - mnavDropAttachmentPlan(navmesh, plan); for (int32_t k = 0; k < MNAV_LINK_KINDS; ++k) { navmesh->costPerMeter[k] = (double)INFINITY; @@ -619,6 +663,11 @@ void mnavApplyAttachments(mnavNavmesh* navmesh, mnavAttachmentPlan* plan) (size_t)count) : 0; Arrive(navmesh, half); + mnavDropAttachmentPlan(navmesh, plan); + mnavRelease(&navmesh->memory, attachments, (size_t)attachmentCapacity, sizeof(uint64_t), + alignof(uint64_t)); + mnavRelease(&navmesh->memory, arrivals, (size_t)arrivalCapacity, sizeof(uint64_t), + alignof(uint64_t)); } // The keys of one polygon in a sorted key array: first receives the index diff --git a/third_party/maul-nav/src/orca.c b/third_party/maul-nav/src/orca.c index c3837280f..46dd3964c 100644 --- a/third_party/maul-nav/src/orca.c +++ b/third_party/maul-nav/src/orca.c @@ -15,7 +15,7 @@ // Narrows [left, right] along line n to where the lines before it allow; // false when nothing is left. -static bool Bound(const mnavLine* lines, int32_t n, double* left, double* right) +MNAV_NODISCARD static bool Bound(const mnavLine* lines, int32_t n, double* left, double* right) { const mnavLine* line = &lines[n]; for (int32_t i = 0; i < n; ++i) @@ -49,8 +49,8 @@ static bool Bound(const mnavLine* lines, int32_t n, double* left, double* right) // The 1D program on line n, bounded by the lines before it and the // circle: false when they leave nothing of it. -static bool Program1(const mnavLine* lines, int32_t n, double radius, mnavPos2 wanted, - bool directionOpt, mnavPos2* result) +MNAV_NODISCARD static bool Program1(const mnavLine* lines, int32_t n, double radius, + mnavPos2 wanted, bool directionOpt, mnavPos2* result) { const mnavLine* line = &lines[n]; double dot = mnavDot2(line->point, line->direction); @@ -112,7 +112,7 @@ int32_t mnavLinearProgram2(const mnavLine* lines, int32_t count, double radius, // The line where lines i and j are equally broken, along which the 3D // program's projection runs; false when they point the same way. -static bool Bisector(const mnavLine* i, const mnavLine* j, mnavLine* out) +MNAV_NODISCARD static bool Bisector(const mnavLine* i, const mnavLine* j, mnavLine* out) { double determinant = mnavDet2(i->direction, j->direction); if (fabs(determinant) <= MNAV_ORCA_PARALLEL) diff --git a/third_party/maul-nav/src/orca.h b/third_party/maul-nav/src/orca.h index a34639a2e..bc3ff3e75 100644 --- a/third_party/maul-nav/src/orca.h +++ b/third_party/maul-nav/src/orca.h @@ -30,6 +30,13 @@ static inline mnavPos2 mnavScale2(mnavPos2 a, double s) return (mnavPos2){a.x * s, a.y * s}; } +// A velocity the host gives, widened to the binary64 avoidance computes +// in. +static inline mnavPos2 mnavWide2(mnavVec2 v) +{ + return (mnavPos2){(double)v.x, (double)v.y}; +} + static inline double mnavDot2(mnavPos2 a, mnavPos2 b) { return a.x * b.x + a.y * b.y; diff --git a/third_party/maul-nav/src/orca3.c b/third_party/maul-nav/src/orca3.c index ecee50877..e8d9eb0f1 100644 --- a/third_party/maul-nav/src/orca3.c +++ b/third_party/maul-nav/src/orca3.c @@ -28,8 +28,8 @@ typedef struct Line3 // Narrows [left, right] along the line to where planes [0, n) allow; // false when nothing is left. -static bool Bound(const mnavPlane* planes, int32_t n, const Line3* line, double* left, - double* right) +MNAV_NODISCARD static bool Bound(const mnavPlane* planes, int32_t n, const Line3* line, + double* left, double* right) { for (int32_t i = 0; i < n; ++i) { @@ -62,8 +62,9 @@ static bool Bound(const mnavPlane* planes, int32_t n, const Line3* line, double* // The 1D program on a line, bounded by planes [0, n) and the sphere: // false when they leave nothing of it. -static bool Program1(const mnavPlane* planes, int32_t n, const Line3* line, double radius, - mnavPos3 wanted, bool directionOpt, mnavPos3* result) +MNAV_NODISCARD static bool Program1(const mnavPlane* planes, int32_t n, const Line3* line, + double radius, mnavPos3 wanted, bool directionOpt, + mnavPos3* result) { double dot = mnavDot3(line->point, line->direction); double discriminant = dot * dot + radius * radius - mnavDot3(line->point, line->point); @@ -94,8 +95,8 @@ static bool Program1(const mnavPlane* planes, int32_t n, const Line3* line, doub // The best point of plane n's disc within the sphere, before the planes // before it are kept: false when the plane misses the sphere. -static bool OnPlane(const mnavPlane* plane, double radius, mnavPos3 wanted, bool directionOpt, - mnavPos3* result) +MNAV_NODISCARD static bool OnPlane(const mnavPlane* plane, double radius, mnavPos3 wanted, + bool directionOpt, mnavPos3* result) { double distance = mnavDot3(plane->point, plane->normal); double distanceSq = distance * distance; @@ -135,8 +136,8 @@ static bool OnPlane(const mnavPlane* plane, double radius, mnavPos3 wanted, bool // The 2D program on plane n, bounded by planes [0, n) and the sphere: // false when they leave nothing of it. -static bool Program2(const mnavPlane* planes, int32_t n, double radius, mnavPos3 wanted, - bool directionOpt, mnavPos3* result) +MNAV_NODISCARD static bool Program2(const mnavPlane* planes, int32_t n, double radius, + mnavPos3 wanted, bool directionOpt, mnavPos3* result) { const mnavPlane* plane = &planes[n]; if (!OnPlane(plane, radius, wanted, directionOpt, result)) @@ -201,7 +202,7 @@ int32_t mnavPlaneProgram3(const mnavPlane* planes, int32_t count, double radius, // The plane where planes i and j are equally broken, through which the // 4D program's projection runs; false when they face the same way. -static bool Bisector(const mnavPlane* i, const mnavPlane* j, mnavPlane* out) +MNAV_NODISCARD static bool Bisector(const mnavPlane* i, const mnavPlane* j, mnavPlane* out) { mnavPos3 cross = mnavCross3(j->normal, i->normal); if (mnavDot3(cross, cross) <= PARALLEL * PARALLEL) diff --git a/third_party/maul-nav/src/orca3.h b/third_party/maul-nav/src/orca3.h index 453a6410e..0a2d249dd 100644 --- a/third_party/maul-nav/src/orca3.h +++ b/third_party/maul-nav/src/orca3.h @@ -31,6 +31,13 @@ static inline mnavPos3 mnavScale3(mnavPos3 a, double s) return (mnavPos3){a.x * s, a.y * s, a.z * s}; } +// A velocity the host gives, widened to the binary64 avoidance computes +// in. +static inline mnavPos3 mnavWide3(mnavVec3 v) +{ + return (mnavPos3){(double)v.x, (double)v.y, (double)v.z}; +} + static inline double mnavDot3(mnavPos3 a, mnavPos3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; diff --git a/third_party/maul-nav/src/outline.h b/third_party/maul-nav/src/outline.h index 7f3895e5e..e9469db85 100644 --- a/third_party/maul-nav/src/outline.h +++ b/third_party/maul-nav/src/outline.h @@ -18,10 +18,11 @@ // Whether a ring of points' bounds meet the tile's cells, border // included; (x, y) is the place (x, 0, y). -bool mnavRingTouchesTile(const mnavTileFrame* frame, const mnavVec2* points, int32_t count); +MNAV_NODISCARD bool mnavRingTouchesTile(const mnavTileFrame* frame, const mnavVec2* points, + int32_t count); // Whether an outline's bounds meet the tile's cells, border included. -bool mnavOutlineTouchesTile(const mnavTileFrame* frame, const mnavOutline* outline); +MNAV_NODISCARD bool mnavOutlineTouchesTile(const mnavTileFrame* frame, const mnavOutline* outline); // Called for a cell (x, z) of the tile; anything but mnav_success stops // the visit with that result. diff --git a/third_party/maul-nav/src/outputs.h b/third_party/maul-nav/src/outputs.h new file mode 100644 index 000000000..36fe42eee --- /dev/null +++ b/third_party/maul-nav/src/outputs.h @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Sirac Ozmen +// +// What a public call leaves in its outputs. A call whose first parameter is +// an owner object clears them before it checks anything, and again when it +// fails, so that a failure never leaves an earlier call's values, or part of +// its own, for the caller to read. A call that can be refused as busy, by a +// call back in from host code the object runs, fills copies of its outputs +// instead and gives them only when it was not (mnavGiveOutput): a busy +// refusal writes none of them. A call that answers "no" without failing +// writes nothing past the cleared outputs but the answer. A caller's array +// is zeroed over its capacity on failure (mnavClearArray). The outputs a +// failure defines (the size or count a buffer too small needed with the +// first capacity items, a bake's report) are left to the call +// (docs/internals/overview.md). + +#ifndef MAUL_NAV_SRC_OUTPUTS_H +#define MAUL_NAV_SRC_OUTPUTS_H + +#include "maul-nav/base.h" + +#include +#include +#include +#include + +// Sets an output's bytes to 0; NULL is no output. +static inline void mnavClearOutput(void* output, size_t size) +{ + if (output != nullptr) + { + memset(output, 0, size); + } +} + +// Clears an output when the call failed, and returns its result. +static inline mnavResult mnavClearOnFailure(mnavResult result, void* output, size_t size) +{ + if (result != mnav_success) + { + mnavClearOutput(output, size); + } + return result; +} + +// Clears a count or size when the call failed for another reason than a +// buffer too small, which defines it. +static inline mnavResult mnavClearUnlessCapacity(mnavResult result, void* output, size_t size) +{ + if (result != mnav_success && result != mnav_errorCapacity) + { + mnavClearOutput(output, size); + } + return result; +} + +// Gives the caller an output a call made in its own copy: the copy when +// the call succeeded or its failure defines the output, zeros on any other +// failure, and nothing when the call was refused as busy, which only a call +// back in from host code the object runs can be: the output may be one the +// call in progress has written or is filling. NULL is no output. +static inline void mnavGiveOutput(mnavResult result, bool defined, void* output, const void* made, + size_t size) +{ + if (output == nullptr || result == mnav_errorBusy) + { + return; + } + if (result == mnav_success || defined) + { + memcpy(output, made, size); + } + else + { + memset(output, 0, size); + } +} + +// Zeroes a caller's array over its capacity when the call failed and the +// failure defines none of it, so that no earlier call's items are left in +// it. The array is left when the call succeeded, when its failure defines +// the items written (a buffer too small keeps the first capacity), when it +// was refused as busy, as mnavGiveOutput leaves an output, and when the +// span itself is refused (no array, a capacity below 1, or more bytes than +// memory holds). +static inline void mnavClearArray(mnavResult result, bool defined, void* array, int64_t capacity, + size_t itemSize) +{ + if (result == mnav_success || defined || result == mnav_errorBusy || array == nullptr || + capacity < 1 || (uint64_t)capacity > SIZE_MAX / itemSize) + { + return; + } + memset(array, 0, (size_t)capacity * itemSize); +} + +// A byte buffer's size as mnavClearArray's capacity: -1, a span refused, +// for one past INT64_MAX, which no memory holds. A size_t of 32 bits never +// is, so the test is compiled only where one could be. +static inline int64_t mnavByteCapacity(size_t bytes) +{ +#if SIZE_MAX > INT64_MAX + if (bytes > (size_t)INT64_MAX) + { + return -1; + } +#endif + return (int64_t)bytes; +} + +#endif // MAUL_NAV_SRC_OUTPUTS_H diff --git a/third_party/maul-nav/src/path.c b/third_party/maul-nav/src/path.c index 18b591813..546c30e4a 100644 --- a/third_party/maul-nav/src/path.c +++ b/third_party/maul-nav/src/path.c @@ -9,6 +9,7 @@ #include "invariant.h" #include "navmesh.h" #include "offmesh.h" +#include "outputs.h" #include "polymesh.h" #include "query.h" #include "query_filter.h" @@ -24,7 +25,7 @@ // Whether entry a leaves the open list before entry b: the lower total, // then the node made first. -static bool Sooner(mnavHeapEntry a, mnavHeapEntry b) +MNAV_NODISCARD static bool Sooner(mnavHeapEntry a, mnavHeapEntry b) { return a.total != b.total ? a.total < b.total : a.node < b.node; } @@ -131,7 +132,7 @@ static void Open(mnavSearch* s, int32_t from, const Key* key, mnavPos3 a, mnavPo double linkCost) { mnavQuery* query = s->query; - if (s->inside != nullptr && s->inside[key->slot] == 0 && !s->beyond) + if (s->clusterOf != nullptr && s->clusterOf[key->slot] != s->cluster && !s->beyond) { return; } @@ -364,14 +365,14 @@ static void Expand(mnavSearch* s, int32_t n) ExpandOffMesh(s, n); } -static bool FinitePoint(mnavPos3 p) +MNAV_NODISCARD static bool FinitePoint(mnavPos3 p) { return isfinite(p.x) && isfinite(p.y) && isfinite(p.z); } // Whether node a lies nearer the end than node b: less still to go, then // the lower cost, then made first. -static bool Nearer(const mnavQuery* query, int32_t a, int32_t b) +MNAV_NODISCARD static bool Nearer(const mnavQuery* query, int32_t a, int32_t b) { const mnavSearchNode* na = &query->nodes[a]; const mnavSearchNode* nb = &query->nodes[b]; @@ -420,6 +421,10 @@ mnavResult mnavBeginPathWork(mnavQuery* query, const mnavNavmesh* navmesh, { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, start) || !mnavInQueryReach(navmesh, end)) + { + return mnav_errorLimit; + } mnavResult result = mnavCheckPolygon(navmesh, startPolygon); result = result == mnav_success ? mnavCheckPolygon(navmesh, endPolygon) : result; const mnavQueryFilter* usable = nullptr; @@ -449,6 +454,7 @@ mnavResult mnavBeginPathWork(mnavQuery* query, const mnavNavmesh* navmesh, 0, MNAV_NO_NODE, nullptr, + -1, false, false, {-1, -1, -1, -1}}; @@ -482,9 +488,9 @@ static MNAV_NOINLINE mnavResult BeginPathCounted(mnavQuery* query, const mnavNav mnavBeginPathWork(query, navmesh, filter, startPolygon, start, endPolygon, end)); } -mnavResult mnavBeginPath(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId startPolygon, mnavPos3 start, - mnavPolygonId endPolygon, mnavPos3 end) +mnavResult mnavQuery_BeginPath(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, + mnavPos3 end, const mnavQueryFilter* filter) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -493,10 +499,12 @@ mnavResult mnavBeginPath(mnavQuery* query, const mnavNavmesh* navmesh, return result; } -void mnavConfineSearch(mnavQuery* query, const uint8_t* inside, bool beyond, bool noEnd) +void mnavConfineSearch(mnavQuery* query, const int32_t* clusterOf, int32_t cluster, bool beyond, + bool noEnd) { mnavSearch* s = &query->search; - s->inside = inside; + s->clusterOf = clusterOf; + s->cluster = cluster; s->beyond = beyond; if (noEnd) { @@ -602,7 +610,7 @@ static mnavResult CheckSearch(const mnavQuery* query, const mnavNavmesh* navmesh return s->commits == navmesh->commits ? mnav_success : mnav_errorState; } -static bool Ended(const mnavQuery* query) +MNAV_NODISCARD static bool Ended(const mnavQuery* query) { return query->search.found != MNAV_NO_NODE || query->heapCount == 0; } @@ -640,7 +648,7 @@ mnavResult mnavContinuePathWork(mnavQuery* query, const mnavNavmesh* navmesh, in continue; } s->best = Nearer(query, n, s->best) ? n : s->best; - if (s->inside == nullptr || s->inside[node->slot] != 0) + if (s->clusterOf == nullptr || s->clusterOf[node->slot] == s->cluster) { Expand(s, n); } @@ -652,12 +660,16 @@ mnavResult mnavContinuePathWork(mnavQuery* query, const mnavNavmesh* navmesh, in static MNAV_NOINLINE mnavResult ContinuePathCounted(mnavQuery* query, const mnavNavmesh* navmesh, int32_t nodes, bool* endedOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - mnavContinuePathWork(query, navmesh, nodes, endedOut)); + bool endedMade = false; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + mnavContinuePathWork(query, navmesh, nodes, endedOut != nullptr ? &endedMade : nullptr)); + mnavGiveOutput(result, false, endedOut, &endedMade, sizeof(endedMade)); + return result; } -mnavResult mnavContinuePath(mnavQuery* query, const mnavNavmesh* navmesh, int32_t nodes, - bool* endedOut) +mnavResult mnavQuery_ContinuePath(mnavQuery* query, const mnavNavmesh* navmesh, int32_t nodes, + bool* endedOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = ContinuePathCounted(query, navmesh, nodes, endedOut); @@ -695,7 +707,10 @@ mnavResult mnavFinishPathWork(mnavQuery* query, const mnavNavmesh* navmesh, mnav query->points, pointCount, query->links, - linkCount}; + linkCount, + nullptr, + 0}; + mnavKeepPath(query, pathOut); s->active = false; return mnav_success; } @@ -703,11 +718,15 @@ mnavResult mnavFinishPathWork(mnavQuery* query, const mnavNavmesh* navmesh, mnav static MNAV_NOINLINE mnavResult FinishPathCounted(mnavQuery* query, const mnavNavmesh* navmesh, mnavPath* pathOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - mnavFinishPathWork(query, navmesh, pathOut)); + mnavPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + mnavFinishPathWork(query, navmesh, pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFinishPath(mnavQuery* query, const mnavNavmesh* navmesh, mnavPath* pathOut) +mnavResult mnavQuery_FinishPath(mnavQuery* query, const mnavNavmesh* navmesh, mnavPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FinishPathCounted(query, navmesh, pathOut); @@ -744,14 +763,18 @@ static MNAV_NOINLINE mnavResult FindPathCounted(mnavQuery* query, const mnavNavm mnavPolygonId endPolygon, mnavPos3 end, mnavPath* pathOut) { - return mnavHostCountInvalid( + mnavPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - mnavFindPathWork(query, navmesh, filter, startPolygon, start, endPolygon, end, pathOut)); + mnavFindPathWork(query, navmesh, filter, startPolygon, start, endPolygon, end, + pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFindPath(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, - mnavPos3 end, mnavPath* pathOut) +mnavResult mnavQuery_FindPath(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, mnavPolygonId endPolygon, + mnavPos3 end, const mnavQueryFilter* filter, mnavPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = @@ -796,15 +819,19 @@ static MNAV_NOINLINE mnavResult CheckReachableCounted(mnavQuery* query, const mn mnavPolygonId endPolygon, mnavPos3 end, mnavPathEnd* endOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - mnavCheckReachableWork(query, navmesh, filter, startPolygon, start, - endPolygon, end, endOut)); + mnavPathEnd endMade = 0; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + mnavCheckReachableWork(query, navmesh, filter, startPolygon, start, endPolygon, end, + endOut != nullptr ? &endMade : nullptr)); + mnavGiveOutput(result, false, endOut, &endMade, sizeof(endMade)); + return result; } -mnavResult mnavCheckReachable(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId startPolygon, - mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, - mnavPathEnd* endOut) +mnavResult mnavQuery_CheckReachable(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, + const mnavQueryFilter* filter, mnavPathEnd* endOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = diff --git a/third_party/maul-nav/src/planar.c b/third_party/maul-nav/src/planar.c index 20762d83d..c4f3ee6b8 100644 --- a/third_party/maul-nav/src/planar.c +++ b/third_party/maul-nav/src/planar.c @@ -42,8 +42,8 @@ bool mnavIntersectProper(const mnavContourVertex* a, const mnavContourVertex* b, } // Whether c lies on the closed segment ab. -static bool Between(const mnavContourVertex* a, const mnavContourVertex* b, - const mnavContourVertex* c) +MNAV_NODISCARD static bool Between(const mnavContourVertex* a, const mnavContourVertex* b, + const mnavContourVertex* c) { if (mnavArea2(a, b, c) != 0) { diff --git a/third_party/maul-nav/src/planar.h b/third_party/maul-nav/src/planar.h index 5d05e03c5..f51a4a68c 100644 --- a/third_party/maul-nav/src/planar.h +++ b/third_party/maul-nav/src/planar.h @@ -17,23 +17,26 @@ int64_t mnavArea2(const mnavContourVertex* a, const mnavContourVertex* b, // Whether c lies strictly left of the directed line a to b (negative // area), or left or on it. -bool mnavLeft(const mnavContourVertex* a, const mnavContourVertex* b, const mnavContourVertex* c); -bool mnavLeftOn(const mnavContourVertex* a, const mnavContourVertex* b, const mnavContourVertex* c); +MNAV_NODISCARD bool mnavLeft(const mnavContourVertex* a, const mnavContourVertex* b, + const mnavContourVertex* c); +MNAV_NODISCARD bool mnavLeftOn(const mnavContourVertex* a, const mnavContourVertex* b, + const mnavContourVertex* c); -bool mnavSameGround(const mnavContourVertex* a, const mnavContourVertex* b); +MNAV_NODISCARD bool mnavSameGround(const mnavContourVertex* a, const mnavContourVertex* b); // Whether segments ab and cd cross at a point inside both. -bool mnavIntersectProper(const mnavContourVertex* a, const mnavContourVertex* b, - const mnavContourVertex* c, const mnavContourVertex* d); +MNAV_NODISCARD bool mnavIntersectProper(const mnavContourVertex* a, const mnavContourVertex* b, + const mnavContourVertex* c, const mnavContourVertex* d); // Whether segments ab and cd meet at all, an end or a collinear overlap // included. -bool mnavIntersect(const mnavContourVertex* a, const mnavContourVertex* b, - const mnavContourVertex* c, const mnavContourVertex* d); +MNAV_NODISCARD bool mnavIntersect(const mnavContourVertex* a, const mnavContourVertex* b, + const mnavContourVertex* c, const mnavContourVertex* d); // Whether p lies in the cone at vertex at between prev and next; loose // counts the cone's edges as inside at a convex vertex. -bool mnavInCone(const mnavContourVertex* prev, const mnavContourVertex* at, - const mnavContourVertex* next, const mnavContourVertex* p, bool loose); +MNAV_NODISCARD bool mnavInCone(const mnavContourVertex* prev, const mnavContourVertex* at, + const mnavContourVertex* next, const mnavContourVertex* p, + bool loose); #endif // MAUL_NAV_SRC_PLANAR_H diff --git a/third_party/maul-nav/src/query.c b/third_party/maul-nav/src/query.c index 7a5d8834f..2e54025b0 100644 --- a/third_party/maul-nav/src/query.c +++ b/third_party/maul-nav/src/query.c @@ -10,6 +10,7 @@ #include "fpenv.h" #include "maul-nav/base.h" +#include "maul-nav/hierarchy.h" #include "maul-nav/query.h" #include @@ -24,6 +25,15 @@ void mnavDestroyQuery(mnavQuery* query) } mnavMemory* memory = &query->memory; size_t nodes = (size_t)query->limits.nodes; + size_t graph = (size_t)query->limits.transitions + 1; + mnavRelease(memory, query->graph.heap, graph, sizeof(int32_t), alignof(int32_t)); + mnavRelease(memory, query->graph.places, graph, sizeof(int32_t), alignof(int32_t)); + mnavRelease(memory, query->graph.parents, graph, sizeof(int32_t), alignof(int32_t)); + mnavRelease(memory, query->graph.joins, graph, sizeof(double), alignof(double)); + mnavRelease(memory, query->graph.costs, graph, sizeof(double), alignof(double)); + mnavRelease(memory, query->pathLinks, nodes, sizeof(mnavPathLink), alignof(mnavPathLink)); + mnavRelease(memory, query->pathPoints, 2 * nodes + 1, sizeof(mnavPos3), alignof(mnavPos3)); + mnavRelease(memory, query->pathPolygons, nodes, sizeof(mnavPolygonId), alignof(mnavPolygonId)); mnavRelease(memory, query->links, nodes, sizeof(mnavPathLink), alignof(mnavPathLink)); mnavRelease(memory, query->points, 2 * nodes + 1, sizeof(mnavPos3), alignof(mnavPos3)); mnavRelease(memory, query->portals, 2 * nodes + 1, sizeof(mnavPortal), alignof(mnavPortal)); @@ -41,18 +51,76 @@ void mnavDestroyQuery(mnavQuery* query) mnavRelease(&last, query, 1, sizeof(mnavQuery), alignof(mnavQuery)); } -uint64_t mnavGetQueryMisuse(const mnavQuery* query) +uint64_t mnavQuery_GetMisuse(const mnavQuery* query) { return query != nullptr ? mnavHostMisuse(&query->host) : 0; } +// The kept path's own arrays, as large as the ones a search writes. +static mnavResult AllocatePath(mnavQuery* query) +{ + size_t nodes = (size_t)query->limits.nodes; + mnavResult result = mnavAllocate(&query->memory, nodes, sizeof(mnavPolygonId), + alignof(mnavPolygonId), (void**)&query->pathPolygons); + if (result == mnav_success) + { + result = mnavAllocate(&query->memory, 2 * nodes + 1, sizeof(mnavPos3), alignof(mnavPos3), + (void**)&query->pathPoints); + } + if (result == mnav_success) + { + result = mnavAllocate(&query->memory, nodes, sizeof(mnavPathLink), alignof(mnavPathLink), + (void**)&query->pathLinks); + } + return result; +} -static MNAV_NOINLINE mnavResult CreateQueryWork(const mnavQueryDef* def, mnavQuery** queryOut) +// The search over a hierarchy's transitions, one entry more for the end. +static mnavResult AllocateGraph(mnavQuery* query) { - if (queryOut == nullptr) + size_t graph = (size_t)query->limits.transitions + 1; + mnavGraphSearch* g = &query->graph; + mnavResult result = + mnavAllocate(&query->memory, graph, sizeof(double), alignof(double), (void**)&g->costs); + if (result == mnav_success) + { + result = + mnavAllocate(&query->memory, graph, sizeof(double), alignof(double), (void**)&g->joins); + } + if (result == mnav_success) + { + result = mnavAllocate(&query->memory, graph, sizeof(int32_t), alignof(int32_t), + (void**)&g->parents); + } + if (result == mnav_success) + { + result = mnavAllocate(&query->memory, graph, sizeof(int32_t), alignof(int32_t), + (void**)&g->places); + } + if (result == mnav_success) + { + result = mnavAllocate(&query->memory, graph, sizeof(int32_t), alignof(int32_t), + (void**)&g->heap); + } + return result; +} + +static mnavResult CheckLimits(const mnavQueryLimits* limits) +{ + if (limits->nodes < 1 || !(limits->pathLength > 0.0f) || limits->transitions < 0) { return mnav_errorInvalid; } - *queryOut = nullptr; + if (limits->nodes > MNAV_MAX_QUERY_NODES || limits->pathLength > MNAV_MAX_PATH_LENGTH || + limits->transitions > MNAV_MAX_HIERARCHY_TRANSITIONS) + { + return mnav_errorLimit; + } + return mnav_success; +} + +// The def's cookie, its version, then its allocator and its limits. +static mnavResult CheckDef(const mnavQueryDef* def) +{ if (def == nullptr || def->cookie != MNAV_QUERY_DEF_COOKIE) { return mnav_errorInvalid; @@ -61,15 +129,26 @@ static MNAV_NOINLINE mnavResult CreateQueryWork(const mnavQueryDef* def, mnavQue { return mnav_errorVersion; } - const mnavQueryLimits* limits = &def->limits; - if (limits->nodes < 1 || !(limits->pathLength > 0.0f)) + if ((def->allocator.alloc == nullptr) != (def->allocator.free == nullptr)) { return mnav_errorInvalid; } - if (limits->nodes > MNAV_MAX_QUERY_NODES || limits->pathLength > MNAV_MAX_PATH_LENGTH) + return CheckLimits(&def->limits); +} + +static MNAV_NOINLINE mnavResult CreateQueryWork(const mnavQueryDef* def, mnavQuery** queryOut) +{ + if (queryOut == nullptr) { - return mnav_errorLimit; + return mnav_errorInvalid; + } + *queryOut = nullptr; + mnavResult checked = CheckDef(def); + if (checked != mnav_success) + { + return checked; } + const mnavQueryLimits* limits = &def->limits; mnavMemory memory = mnavMakeMemory(def->allocator, UINT64_MAX); mnavQuery* query = nullptr; mnavResult result = @@ -121,6 +200,8 @@ static MNAV_NOINLINE mnavResult CreateQueryWork(const mnavQueryDef* def, mnavQue result = mnavAllocate(&query->memory, nodes, sizeof(mnavPathLink), alignof(mnavPathLink), (void**)&query->links); } + result = result == mnav_success ? AllocatePath(query) : result; + result = result == mnav_success ? AllocateGraph(query) : result; if (result != mnav_success) { mnavDestroyQuery(query); @@ -177,3 +258,43 @@ uint32_t mnavFindNode(const mnavQuery* query, int32_t slot, int32_t polygon, int cell = (cell + 1) & query->tableMask; } } + +void mnavKeepPath(mnavQuery* query, mnavPath* path) +{ + // A search's counts are within the arrays it wrote, which these match. + if (path->polygonCount > 0) + { + memcpy(query->pathPolygons, path->polygons, + (size_t)path->polygonCount * sizeof(mnavPolygonId)); + path->polygons = query->pathPolygons; + } + if (path->pointCount > 0) + { + memcpy(query->pathPoints, path->points, (size_t)path->pointCount * sizeof(mnavPos3)); + path->points = query->pathPoints; + } + if (path->linkCount > 0) + { + memcpy(query->pathLinks, path->links, (size_t)path->linkCount * sizeof(mnavPathLink)); + path->links = query->pathLinks; + } + query->pathPolygonCount = path->polygonCount; + query->pathPointCount = path->pointCount; + query->pathLinkCount = path->linkCount; + query->generation += 1; + path->query = query; + path->generation = query->generation; +} + +const mnavPos3* mnavKeepPoints(mnavQuery* query, const mnavPos3* points, int32_t count) +{ + if (count > 0) + { + memcpy(query->pathPoints, points, (size_t)count * sizeof(mnavPos3)); + } + query->pathPolygonCount = 0; + query->pathPointCount = count; + query->pathLinkCount = 0; + query->generation += 1; + return count > 0 ? query->pathPoints : points; +} diff --git a/third_party/maul-nav/src/query.h b/third_party/maul-nav/src/query.h index eea7bc5bc..918a6a4af 100644 --- a/third_party/maul-nav/src/query.h +++ b/third_party/maul-nav/src/query.h @@ -128,9 +128,11 @@ typedef struct mnavSearch // The node nearest the end so far, and the end's node once found. int32_t best; int32_t found; - // A confined search's tiles, a flag per slot, or NULL for all; with - // beyond set, nodes outside are opened but never expanded. - const uint8_t* inside; + // A confined search's cluster: each slot's cluster, or NULL for every + // slot, and the one cluster whose slots it expands; with beyond set, + // nodes outside are opened but never expanded. + const int32_t* clusterOf; + int32_t cluster; bool beyond; // Whether off-mesh links are followed from where they land back to // where they take off, so that the costs found are those of walking to @@ -170,6 +172,19 @@ typedef struct mnavFlightSliced bool active; } mnavFlightSliced; +// The search over a hierarchy's transitions, with one entry more for the +// end: each one's cost and parent, its cost on to the end, and its place in +// the heap, and the heap. +typedef struct mnavGraphSearch +{ + double* costs; + double* joins; + int32_t* parents; + int32_t* places; + int32_t* heap; + int32_t heapCount; +} mnavGraphSearch; + struct mnavQuery { mnavMemory memory; @@ -224,8 +239,29 @@ struct mnavQuery mnavSearch search; mnavQueryFilter filter; mnavFlightSliced flight; + mnavGraphSearch graph; + // The last path a search gave, in arrays of its own that no other call + // writes: its polygons, points and links, their counts, and which path + // it is, so that a path handed back can be checked to be this one and + // within it. A flight path's points are kept in the same points. + mnavPolygonId* pathPolygons; + mnavPos3* pathPoints; + mnavPathLink* pathLinks; + int32_t pathPolygonCount; + int32_t pathPointCount; + int32_t pathLinkCount; + uint64_t generation; }; +// Keeps the path a search made in the path arrays, which it then names, +// so that a call that is no search leaves it whole; the path kept before +// it is replaced. +void mnavKeepPath(mnavQuery* query, mnavPath* path); + +// Keeps a flight path's points the same way, replacing the kept path, and +// gives where they now lie. +const mnavPos3* mnavKeepPoints(mnavQuery* query, const mnavPos3* points, int32_t count); + // Empties the node table for a search, unless it is empty already. void mnavClearTable(mnavQuery* query); @@ -234,11 +270,12 @@ void mnavClearTable(mnavQuery* query); uint32_t mnavFindNode(const mnavQuery* query, int32_t slot, int32_t polygon, int32_t tag, int32_t low); -// Confines the search just begun to the slots flagged in inside; with -// beyond, nodes outside are opened but never expanded; with no end, the -// search is Dijkstra's and never reaches the end, so that it runs until -// its open list empties. -void mnavConfineSearch(mnavQuery* query, const uint8_t* inside, bool beyond, bool noEnd); +// Confines the search just begun to the slots of one cluster, by each +// slot's cluster; with beyond, nodes outside are opened but never expanded; +// with no end, the search is Dijkstra's and never reaches the end, so that +// it runs until its open list empties. +void mnavConfineSearch(mnavQuery* query, const int32_t* clusterOf, int32_t cluster, bool beyond, + bool noEnd); // Turns the search just begun backward: off-mesh links are followed from // where they land to where they take off. Walks cost the same either way, diff --git a/third_party/maul-nav/src/query_filter.c b/third_party/maul-nav/src/query_filter.c index 68aa0f38b..9b3d71ac9 100644 --- a/third_party/maul-nav/src/query_filter.c +++ b/third_party/maul-nav/src/query_filter.c @@ -67,5 +67,5 @@ double mnavCheapest(const mnavQueryFilter* filter) bool mnavCrosses(const mnavQueryFilter* filter, mnavLinkKind kind) { - return kind < MNAV_LINK_KINDS && ((filter->kinds >> kind) & 1u) != 0; + return (uint32_t)kind < MNAV_LINK_KINDS && ((filter->kinds >> kind) & 1u) != 0; } diff --git a/third_party/maul-nav/src/query_filter.h b/third_party/maul-nav/src/query_filter.h index 86ddb7d9a..b26d20b7f 100644 --- a/third_party/maul-nav/src/query_filter.h +++ b/third_party/maul-nav/src/query_filter.h @@ -20,13 +20,13 @@ mnavResult mnavCheckFilter(const mnavQueryFilter* filter, const mnavQueryFilter* // Whether a filter includes an area type. No polygon has area 0. Inline: // every search step asks it. -static inline bool mnavIncludes(const mnavQueryFilter* filter, mnavAreaType area) +MNAV_NODISCARD static inline bool mnavIncludes(const mnavQueryFilter* filter, mnavAreaType area) { return area < MNAV_AREA_TYPES && ((filter->areas >> area) & 1u) != 0; } // Whether a filter lets the agent cross links of a kind. -bool mnavCrosses(const mnavQueryFilter* filter, mnavLinkKind kind); +MNAV_NODISCARD bool mnavCrosses(const mnavQueryFilter* filter, mnavLinkKind kind); // The lowest cost among the areas a filter includes, 1 when it includes // none. diff --git a/third_party/maul-nav/src/raster.c b/third_party/maul-nav/src/raster.c index 264e16276..e10972dda 100644 --- a/third_party/maul-nav/src/raster.c +++ b/third_party/maul-nav/src/raster.c @@ -149,7 +149,7 @@ static void Copy(float to[3], const float from[3]) // Whether a part of count corners has room for an edge's crossing, when // the edge crosses, and its end, when that goes to the part. -static bool Room(int32_t count, bool cross, bool end) +MNAV_NODISCARD static bool Room(int32_t count, bool cross, bool end) { return count + (cross ? 1 : 0) + (end ? 1 : 0) <= MNAV_RASTER_CORNERS; } @@ -315,7 +315,8 @@ mnavResult mnavAddFragment(mnavMemory* memory, mnavFragmentList* list, mnavFragm // tile's width and beyond. Likewise corners whose nearest lies from the // tile's corner to a cell short of its far side are in without dividing: // the first cell is from 0 to width - 1, and the last no lower. -static bool SpansTile(const mnavTileFrame* frame, float a, float b, float c, float min) +MNAV_NODISCARD static bool SpansTile(const mnavTileFrame* frame, float a, float b, float c, + float min) { float d[3] = {a - min, b - min, c - min}; float before = -frame->cellSize; diff --git a/third_party/maul-nav/src/raster.h b/third_party/maul-nav/src/raster.h index fe6f2e9b4..f59fd14c4 100644 --- a/third_party/maul-nav/src/raster.h +++ b/third_party/maul-nav/src/raster.h @@ -78,16 +78,17 @@ typedef struct mnavRasterRow // Splits a piece at axis = cut into the part below and the part above; a // corner on the cut goes to both. Returns false, with the parts // unspecified, when a part would pass MNAV_RASTER_CORNERS. -bool mnavSplitPoly(const mnavRasterPoly* in, int32_t axis, float cut, mnavRasterPoly* below, - mnavRasterPoly* above); +MNAV_NODISCARD bool mnavSplitPoly(const mnavRasterPoly* in, int32_t axis, float cut, + mnavRasterPoly* below, mnavRasterPoly* above); // mnavSplitPoly for a row's piece at u = cut. -bool mnavSplitRow(const mnavRasterRow* in, float cut, mnavRasterRow* below, mnavRasterRow* above); +MNAV_NODISCARD bool mnavSplitRow(const mnavRasterRow* in, float cut, mnavRasterRow* below, + mnavRasterRow* above); // The frame of tile (tileX, tileZ). Returns false when the tile lies past // the extent input may have. -bool mnavMakeTileFrame(const mnavBakeDef* def, const mnavBakeCells* cells, int32_t tileX, - int32_t tileZ, mnavTileFrame* frame); +MNAV_NODISCARD bool mnavMakeTileFrame(const mnavBakeDef* def, const mnavBakeCells* cells, + int32_t tileX, int32_t tileZ, mnavTileFrame* frame); // The area a triangle rasterizes with: its own, or mnav_areaNone when it // is steeper than the slope or faces down. Returns -1 for a triangle with @@ -95,7 +96,7 @@ bool mnavMakeTileFrame(const mnavBakeDef* def, const mnavBakeCells* cells, int32 int32_t mnavTriangleArea(const mnavVec3 corners[3], mnavAreaType area, float cosMaxSlope); // Whether a triangle touches the tile's cells, border included. -bool mnavTriangleTouchesTile(const mnavTileFrame* frame, const mnavVec3 corners[3]); +MNAV_NODISCARD bool mnavTriangleTouchesTile(const mnavTileFrame* frame, const mnavVec3 corners[3]); // Adds the fragments of a triangle that touches the tile; // mnav_errorLimit when a cut piece would pass MNAV_MAX_PIECE_CORNERS. diff --git a/third_party/maul-nav/src/raycast.c b/third_party/maul-nav/src/raycast.c index 730c787e1..2bfa39525 100644 --- a/third_party/maul-nav/src/raycast.c +++ b/third_party/maul-nav/src/raycast.c @@ -5,6 +5,7 @@ #include "fpenv.h" #include "navmesh.h" +#include "outputs.h" #include "polymesh.h" #include "query.h" #include "query_filter.h" @@ -46,7 +47,7 @@ static double Leaves(const Walk* w, mnavPos3 a, mnavPos3 b) // The polygon the ray enters through edge j at parameter t: true with the // walk moved there, false at a wall; notLoaded is set at a side with no // tile beyond. -static bool Follow(Walk* w, int32_t j, double t, bool* notLoaded) +MNAV_NODISCARD static bool Follow(Walk* w, int32_t j, double t, bool* notLoaded) { const mnavSlot* slot = &w->navmesh->slots[w->slot]; const mnavTile* tile = slot->tile; @@ -130,8 +131,7 @@ static void Wall(mnavRay* ray, mnavPos3 a, mnavPos3 b) double ez = b.z - a.z; double length = sqrt(ex * ex + ez * ez); ray->end = mnav_rayWall; - ray->normalX = ez / length; - ray->normalZ = -ex / length; + ray->normal = (mnavVec3){(float)(ez / length), 0.0f, (float)(-ex / length)}; } // A ray's arguments: a call back in from host code, then the arguments @@ -150,6 +150,10 @@ static mnavResult CheckRay(mnavQuery* query, const mnavNavmesh* navmesh, { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, start) || !mnavInQueryReachGround(navmesh, end.x, end.z)) + { + return mnav_errorLimit; + } mnavResult result = mnavCheckPolygon(navmesh, startPolygon); return result == mnav_success ? mnavCheckFilter(filter, usable) : result; } @@ -171,7 +175,7 @@ mnavResult mnavRaycastWork(mnavQuery* query, const mnavNavmesh* navmesh, usable, (int32_t)startPolygon.slot - 1, (int32_t)startPolygon.polygon}; - *rayOut = (mnavRay){mnav_rayReached, 1.0, 0.0, 0.0, query->corridor, 0}; + *rayOut = (mnavRay){mnav_rayReached, 1.0, {0.0f, 0.0f, 0.0f}, query->corridor, 0}; double t = 0.0; int32_t count = 0; Record(query, &w, &count); @@ -229,13 +233,18 @@ static MNAV_NOINLINE mnavResult RaycastCounted(mnavQuery* query, const mnavNavme mnavPolygonId startPolygon, mnavPos3 start, mnavPos3 end, mnavRay* rayOut) { - return mnavHostCountInvalid( - mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - mnavRaycastWork(query, navmesh, filter, startPolygon, start, end, rayOut)); + mnavRay rayMade = {0}; + mnavResult result = + mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + mnavRaycastWork(query, navmesh, filter, startPolygon, start, end, + rayOut != nullptr ? &rayMade : nullptr)); + mnavGiveOutput(result, false, rayOut, &rayMade, sizeof(rayMade)); + return result; } -mnavResult mnavRaycast(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - mnavPolygonId startPolygon, mnavPos3 start, mnavPos3 end, mnavRay* rayOut) +mnavResult mnavQuery_Raycast(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, mnavPos3 end, + const mnavQueryFilter* filter, mnavRay* rayOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = RaycastCounted(query, navmesh, filter, startPolygon, start, end, rayOut); diff --git a/third_party/maul-nav/src/region.c b/third_party/maul-nav/src/region.c index 4273864f5..664c0e0cb 100644 --- a/third_party/maul-nav/src/region.c +++ b/third_party/maul-nav/src/region.c @@ -46,7 +46,7 @@ typedef struct Graph uint64_t* overlaps; } Graph; -static bool IsRegion(uint32_t id) +MNAV_NODISCARD static bool IsRegion(uint32_t id) { return id != 0 && (id & MNAV_BORDER_REGION) == 0; } diff --git a/third_party/maul-nav/src/scalar.h b/third_party/maul-nav/src/scalar.h index 0187968b0..9f5939daf 100644 --- a/third_party/maul-nav/src/scalar.h +++ b/third_party/maul-nav/src/scalar.h @@ -8,6 +8,8 @@ #ifndef MAUL_NAV_SRC_SCALAR_H #define MAUL_NAV_SRC_SCALAR_H +#include "maul-nav/base.h" + #include #include @@ -19,6 +21,7 @@ float mnavCosDegrees(float degrees); // integer counts as that integer; otherwise it rounds up or down. Returns // false when the quotient exceeds max. meters and cell must be finite, // meters at least 0 and cell positive. -bool mnavToCells(float meters, float cell, bool roundUp, int32_t max, int32_t* cellsOut); +MNAV_NODISCARD bool mnavToCells(float meters, float cell, bool roundUp, int32_t max, + int32_t* cellsOut); #endif // MAUL_NAV_SRC_SCALAR_H diff --git a/third_party/maul-nav/src/shortest.c b/third_party/maul-nav/src/shortest.c index 84bc71596..ac7210579 100644 --- a/third_party/maul-nav/src/shortest.c +++ b/third_party/maul-nav/src/shortest.c @@ -19,9 +19,11 @@ #include "invariant.h" #include "navmesh.h" #include "offmesh.h" +#include "outputs.h" #include "polymesh.h" #include "query.h" #include "query_filter.h" +#include "shortest_geometry.h" #include "maul-nav/base.h" #include "maul-nav/navmesh.h" @@ -91,20 +93,6 @@ typedef struct Piece int32_t entry; } Piece; -// Twice the signed area of triangle o, a, b on the ground: positive when -// b lies left of the way from o to a. -static double Cross(mnavPos3 o, mnavPos3 a, mnavPos3 b) -{ - return (a.x - o.x) * (b.z - o.z) - (a.z - o.z) * (b.x - o.x); -} - -static double Flat(mnavPos3 a, mnavPos3 b) -{ - double dx = a.x - b.x; - double dz = a.z - b.z; - return sqrt(dx * dx + dz * dz); -} - static double Distance(mnavPos3 a, mnavPos3 b) { double dx = a.x - b.x; @@ -113,52 +101,11 @@ static double Distance(mnavPos3 a, mnavPos3 b) return sqrt(dx * dx + dy * dy + dz * dz); } -static mnavPos3 Lerp(mnavPos3 a, mnavPos3 b, double t) -{ - return (mnavPos3){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t}; -} - -static bool Same(mnavPos3 a, mnavPos3 b) +MNAV_NODISCARD static bool Same(mnavPos3 a, mnavPos3 b) { return a.x == b.x && a.y == b.y && a.z == b.z; } -// The point of segment a, b nearest p on the ground. -static mnavPos3 Nearest(mnavPos3 p, mnavPos3 a, mnavPos3 b) -{ - double dx = b.x - a.x; - double dz = b.z - a.z; - double length2 = dx * dx + dz * dz; - double t = length2 > 0.0 ? ((p.x - a.x) * dx + (p.z - a.z) * dz) / length2 : 0.0; - return Lerp(a, b, t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t)); -} - -// The shortest distance on the ground from root r through the interval -// from a to b to point e: an e on r's side of the interval's line is -// mirrored through it first, then the way is straight when it meets the -// interval and turns at the nearer end otherwise. -static double Through(mnavPos3 r, mnavPos3 a, mnavPos3 b, mnavPos3 e) -{ - double side = Cross(a, b, r); - double eSide = Cross(a, b, e); - double dx = b.x - a.x; - double dz = b.z - a.z; - double length2 = dx * dx + dz * dz; - if (length2 > 0.0 && ((side > 0.0 && eSide > 0.0) || (side < 0.0 && eSide < 0.0))) - { - double t = ((e.x - a.x) * dx + (e.z - a.z) * dz) / length2; - e.x = 2.0 * (a.x + t * dx) - e.x; - e.z = 2.0 * (a.z + t * dz) - e.z; - } - if (Cross(r, a, e) >= 0.0 && Cross(r, b, e) <= 0.0) - { - return Flat(r, e); - } - double viaA = Flat(r, a) + Flat(a, e); - double viaB = Flat(r, b) + Flat(b, e); - return viaA < viaB ? viaA : viaB; -} - // The table cell for a root's position: the one holding a node with that // root, or the empty cell where it would go. static uint32_t RootCell(const mnavQuery* query, mnavPos3 root) @@ -193,7 +140,7 @@ static uint64_t Mix(uint64_t h, double v) return h ^ (h >> 31); } -static bool SameNode(const mnavSearchNode* a, const mnavSearchNode* b) +MNAV_NODISCARD static bool SameNode(const mnavSearchNode* a, const mnavSearchNode* b) { return Same(a->at, b->at) && Same(a->a, b->a) && Same(a->b, b->b) && a->slot == b->slot && a->polygon == b->polygon && a->tag == b->tag && a->low == b->low; @@ -225,7 +172,7 @@ static uint32_t MadeCell(const Shortest* s, const mnavSearchNode* node) } // Whether a way to a root at a cost is no dearer than the best known. -static bool Cheapest(const mnavQuery* query, uint32_t cell, double cost) +MNAV_NODISCARD static bool Cheapest(const mnavQuery* query, uint32_t cell, double cost) { int32_t n = query->table[cell]; return n == MNAV_NO_NODE || cost <= query->nodes[n].cost + 1e-9 * (1.0 + fabs(cost)); @@ -236,11 +183,12 @@ static bool Cheapest(const mnavQuery* query, uint32_t cell, double cost) // root, the polygons about it form a ring when no wall meets it. A root // on the interval's line but beyond the interval turns at its nearer // end, a way of its own. -static bool Repeats(const mnavQuery* query, int32_t parent, const mnavSearchNode* made) +MNAV_NODISCARD static bool Repeats(const mnavQuery* query, int32_t parent, + const mnavSearchNode* made) { - double span = Flat(made->a, made->b); - if (made->tag < 0 || fabs(Cross(made->a, made->b, made->at)) > 1e-9 * (1.0 + span * span) || - Flat(made->at, made->a) > span || Flat(made->at, made->b) > span) + double span = mnavFlat(made->a, made->b); + if (made->tag < 0 || fabs(mnavCross(made->a, made->b, made->at)) > 1e-9 * (1.0 + span * span) || + mnavFlat(made->at, made->a) > span || mnavFlat(made->at, made->b) > span) { return false; } @@ -293,7 +241,8 @@ static void Open(Shortest* s, int32_t parent, mnavSearchNode made, double ahead) query->table[cell] = n; } mnavPushNode(query, n); - double toEnd = made.tag == KIND_END ? 0.0 : Flat(Nearest(s->end, made.a, made.b), s->end); + double toEnd = + made.tag == KIND_END ? 0.0 : mnavFlat(mnavNearestOn(s->end, made.a, made.b), s->end); if (toEnd < s->bestAhead || (toEnd == s->bestAhead && made.cost < query->nodes[s->best].cost)) { s->best = n; @@ -305,17 +254,18 @@ static void Open(Shortest* s, int32_t parent, mnavSearchNode made, double ahead) // interval: straight when the root sees p through the interval, else // turning at the interval's end on p's side, when that end is a corner. // With full, the root sees the whole polygon. -static bool Toward(const Shortest* s, const mnavSearchNode* node, Seen* seen, bool full, mnavPos3 p) +MNAV_NODISCARD static bool Toward(const Shortest* s, const mnavSearchNode* node, Seen* seen, + bool full, mnavPos3 p) { if (full) { return true; } // A point on a side of the cone, to within rounding, is in it. - double sideA = Cross(seen->root, node->a, p); - double sideB = Cross(seen->root, node->b, p); - double slackA = 1e-9 * Flat(seen->root, node->a) * (1.0 + Flat(node->a, p)); - double slackB = 1e-9 * Flat(seen->root, node->b) * (1.0 + Flat(node->b, p)); + double sideA = mnavCross(seen->root, node->a, p); + double sideB = mnavCross(seen->root, node->b, p); + double slackA = 1e-9 * mnavFlat(seen->root, node->a) * (1.0 + mnavFlat(node->a, p)); + double slackB = 1e-9 * mnavFlat(seen->root, node->b) * (1.0 + mnavFlat(node->b, p)); if (sideA >= -slackA && sideB <= slackB) { return true; @@ -326,7 +276,7 @@ static bool Toward(const Shortest* s, const mnavSearchNode* node, Seen* seen, bo return false; } mnavPos3 corner = viaB ? node->b : node->a; - double step = Flat(seen->root, corner); + double step = mnavFlat(seen->root, corner); *seen = (Seen){corner, seen->cost + step * s->cost, seen->length + step}; return true; } @@ -339,7 +289,7 @@ static void ReachEnd(Shortest* s, int32_t n, Seen seen, bool full) { return; } - double rest = Flat(seen.root, s->end); + double rest = mnavFlat(seen.root, s->end); mnavSearchNode made = {s->end, s->end, seen.root, seen.cost, seen.length, rest * s->cost, node->slot, node->polygon, KIND_END, 0, MNAV_NO_NODE, MNAV_NO_NODE}; @@ -371,8 +321,8 @@ static void ReachTakeoffs(Shortest* s, int32_t n, Seen seen, bool full) { continue; } - double walk = Flat(way.root, takeoff); - double after = Flat(land, s->end); + double walk = mnavFlat(way.root, takeoff); + double after = mnavFlat(land, s->end); double remaining = walk * s->cost + (double)link->def.cost + after * s->scale; mnavSearchNode made = {takeoff, takeoff, way.root, way.cost, @@ -380,7 +330,7 @@ static void ReachTakeoffs(Shortest* s, int32_t n, Seen seen, bool full) node->slot, node->polygon, KIND_TAKEOFF, attachment.link * 2 + (attachment.reverse ? 1 : 0), MNAV_NO_NODE, MNAV_NO_NODE}; - Open(s, n, made, walk + Flat(takeoff, land) + after); + Open(s, n, made, walk + mnavFlat(takeoff, land) + after); } } @@ -394,9 +344,9 @@ static void Land(Shortest* s, int32_t n) bool reverse = (node->low & 1) != 0; mnavPolygonId landing = reverse ? state->startPolygon : state->endPolygon; mnavPos3 land = reverse ? state->start : state->end; - double walk = Flat(node->at, node->a); - double span = Flat(node->a, land); - double after = Flat(land, s->end); + double walk = mnavFlat(node->at, node->a); + double span = mnavFlat(node->a, land); + double after = mnavFlat(land, s->end); mnavSearchNode made = {land, land, land, @@ -416,7 +366,7 @@ static void Land(Shortest* s, int32_t n) // at least 0; false when nothing of it remains. A crossing within 1e-9 of // either end is that end: a cone's side through a vertex, rounded, would // else make the vertex a point of no corner. -static bool AtLeastZero(double f0, double f1, double* lo, double* hi) +MNAV_NODISCARD static bool AtLeastZero(double f0, double f1, double* lo, double* hi) { if (f0 < 0.0 && f1 < 0.0) { @@ -437,11 +387,11 @@ static bool AtLeastZero(double f0, double f1, double* lo, double* hi) // interval's a lies toward p1. static void Emit(Shortest* s, int32_t n, const Piece* piece, Seen seen, double u0, double u1) { - mnavPos3 a = Lerp(piece->p0, piece->p1, u1); - mnavPos3 b = Lerp(piece->p0, piece->p1, u0); + mnavPos3 a = mnavLerp(piece->p0, piece->p1, u1); + mnavPos3 b = mnavLerp(piece->p0, piece->p1, u0); int32_t tag = piece->entry | (u1 == piece->t1 ? CORNER_A : 0) | (u0 == piece->t0 ? CORNER_B : 0); - double ahead = Through(seen.root, a, b, s->end); + double ahead = mnavThrough(seen.root, a, b, s->end); mnavSearchNode made = {a, b, seen.root, @@ -469,15 +419,15 @@ static void Spread(Shortest* s, int32_t n, const Piece* piece, Seen seen, bool f } const mnavSearchNode* node = &s->query->nodes[n]; mnavPos3 r = seen.root; - double a0 = Cross(r, node->a, piece->p0); - double a1 = Cross(r, node->a, piece->p1); - double b0 = Cross(r, node->b, piece->p0); - double b1 = Cross(r, node->b, piece->p1); + double a0 = mnavCross(r, node->a, piece->p0); + double a1 = mnavCross(r, node->a, piece->p1); + double b0 = mnavCross(r, node->b, piece->p0); + double b1 = mnavCross(r, node->b, piece->p1); double lo = piece->t0; double hi = piece->t1; if ((node->tag & CORNER_B) != 0 && AtLeastZero(b0, b1, &lo, &hi)) { - double step = Flat(r, node->b); + double step = mnavFlat(r, node->b); Emit(s, n, piece, (Seen){node->b, seen.cost + step * s->cost, seen.length + step}, lo, hi); } lo = piece->t0; @@ -490,7 +440,7 @@ static void Spread(Shortest* s, int32_t n, const Piece* piece, Seen seen, bool f hi = piece->t1; if ((node->tag & CORNER_A) != 0 && AtLeastZero(-a0, -a1, &lo, &hi)) { - double step = Flat(r, node->a); + double step = mnavFlat(r, node->a); Emit(s, n, piece, (Seen){node->a, seen.cost + step * s->cost, seen.length + step}, lo, hi); } } @@ -616,7 +566,7 @@ static void SpreadEdges(Shortest* s, int32_t n, Seen seen, bool full) // the interval's line sees it whole from the interval, or else from the // interval's nearer end, turning there when that end is a corner; false // when it cannot. -static bool Facing(const Shortest* s, int32_t n, Seen* seen, bool* full) +MNAV_NODISCARD static bool Facing(const Shortest* s, int32_t n, Seen* seen, bool* full) { const mnavSearchNode* node = &s->query->nodes[n]; *full = node->tag == KIND_START || node->tag == KIND_LANDING; @@ -624,15 +574,15 @@ static bool Facing(const Shortest* s, int32_t n, Seen* seen, bool* full) { return true; } - double span = Flat(node->a, node->b); - double side = Cross(node->a, node->b, seen->root); + double span = mnavFlat(node->a, node->b); + double side = mnavCross(node->a, node->b, seen->root); if (fabs(side) > 1e-9 * (1.0 + span * span)) { return true; } *full = true; - double toA = Flat(seen->root, node->a); - double toB = Flat(seen->root, node->b); + double toA = mnavFlat(seen->root, node->a); + double toB = mnavFlat(seen->root, node->b); if (toA <= span && toB <= span) { return true; @@ -696,8 +646,35 @@ static double OneCost(const mnavQueryFilter* filter) return cost; } +// Whether a point lies on one of a polygon's edges, a corner included: on +// a tile side, a corner of the tile across may be within an edge here. +MNAV_NODISCARD static bool Touches(const mnavNavmesh* navmesh, mnavPolygonId id, mnavPos3 p) +{ + const mnavSlot* slot = &navmesh->slots[id.slot - 1]; + const mnavFrame f = mnavFrameOf(navmesh, slot->x, slot->z); + const mnavPolygon* polygon = &slot->tile->mesh.polygons[id.polygon]; + for (int32_t k = 0; k < polygon->count; ++k) + { + const mnavMeshVertex* vertices = slot->tile->mesh.vertices; + mnavPos3 a = mnavVertexWorld(&f, &vertices[polygon->vertices[k]]); + mnavPos3 b = mnavVertexWorld(&f, &vertices[polygon->vertices[(k + 1) % polygon->count]]); + if (mnavCross(a, b, p) == 0.0 && (p.x - a.x) * (p.x - b.x) <= 0.0 && + (p.z - a.z) * (p.z - b.z) <= 0.0) + { + return true; + } + } + return false; +} + // Writes the polygons from the start to node last into the corridor: // nodes in one polygon are one visit, except across an off-mesh link. +// Polygons are convex, so a shortest way that leaves one and comes back +// without a link only touched the polygons between at a corner: from a +// root on it, every polygon round it is entered at no cost, and rounding +// may favor a way round. Such a loop is cut back to the first visit, +// scanning only the polygons whose edges hold the root. Until then +// each visit holds the node that entered it in its generation's place. static int32_t Polygons(mnavQuery* query, const mnavNavmesh* navmesh, int32_t last) { int32_t count = 0; @@ -705,16 +682,16 @@ static int32_t Polygons(mnavQuery* query, const mnavNavmesh* navmesh, int32_t la for (int32_t n = last; n != MNAV_NO_NODE; n = query->nodes[n].parent) { const mnavSearchNode* node = &query->nodes[n]; - mnavPolygonId id = {(uint32_t)node->slot + 1, navmesh->slots[node->slot].generation, - (uint32_t)node->polygon}; const mnavPolygonId* previous = count > 0 ? &query->corridor[count - 1] : nullptr; - if (previous == nullptr || crossed || previous->slot != id.slot || - previous->polygon != id.polygon) + if (previous == nullptr || crossed || previous->slot != (uint32_t)node->slot + 1 || + previous->polygon != (uint32_t)node->polygon) { // A visit per node at most, and the nodes fit the corridor. MNAV_ASSERT(count < query->limits.nodes); - query->corridor[count++] = id; + query->corridor[count++] = + (mnavPolygonId){(uint32_t)node->slot + 1, 0, (uint32_t)node->polygon}; } + query->corridor[count - 1].generation = (uint32_t)n; crossed = node->tag == KIND_LANDING; } for (int32_t i = 0; i < count / 2; ++i) @@ -723,7 +700,38 @@ static int32_t Polygons(mnavQuery* query, const mnavNavmesh* navmesh, int32_t la query->corridor[i] = query->corridor[count - 1 - i]; query->corridor[count - 1 - i] = swap; } - return count; + int32_t kept = 0; + int32_t landed = 0; + for (int32_t i = 0; i < count; ++i) + { + mnavPolygonId visit = query->corridor[i]; + const mnavSearchNode* node = &query->nodes[visit.generation]; + landed = node->tag == KIND_LANDING ? kept : landed; + int32_t back = kept - 1; + while (back >= landed && node->tag != KIND_LANDING && + (query->corridor[back].slot != visit.slot || + query->corridor[back].polygon != visit.polygon) && + Touches(navmesh, query->corridor[back], node->at)) + { + back -= 1; + } + bool again = back >= landed && node->tag != KIND_LANDING && + query->corridor[back].slot == visit.slot && + query->corridor[back].polygon == visit.polygon; + if (again) + { + kept = back + 1; + } + else + { + query->corridor[kept++] = visit; + } + } + for (int32_t i = 0; i < kept; ++i) + { + query->corridor[i].generation = navmesh->slots[query->corridor[i].slot - 1].generation; + } + return kept; } static void Push(mnavQuery* query, int32_t* count, mnavPos3 p) @@ -747,7 +755,7 @@ static int32_t Points(Shortest* s, int32_t last, int32_t* linkCount, double* lin int32_t count = 0; *linkCount = 0; *linkCost = 0.0; - Push(query, &count, tail->tag < 0 ? tail->a : Nearest(s->end, tail->a, tail->b)); + Push(query, &count, tail->tag < 0 ? tail->a : mnavNearestOn(s->end, tail->a, tail->b)); for (int32_t n = last; n != MNAV_NO_NODE; n = query->nodes[n].parent) { const mnavSearchNode* node = &query->nodes[n]; @@ -801,9 +809,11 @@ static void Finish(Shortest* s, int32_t last, mnavPathEnd end, mnavPath* pathOut length += step; next += link ? 1 : 0; } - *pathOut = (mnavPath){ - end, cost, length, query->corridor, Polygons(query, s->navmesh, last), - query->points, pointCount, query->links, linkCount}; + int32_t polygons = Polygons(query, s->navmesh, last); + *pathOut = + (mnavPath){end, cost, length, query->corridor, polygons, query->points, + pointCount, query->links, linkCount, nullptr, 0}; + mnavKeepPath(query, pathOut); } // Checks the arguments and starts the search from its start node. @@ -835,7 +845,7 @@ static mnavResult Begin(Shortest* s, const mnavQueryFilter* filter, mnavPolygonI mnavClearTable(query); s->made = query->madeTable; memset(s->made, 0xFF, ((size_t)query->tableMask + 1) * sizeof(int32_t)); - double ahead = Flat(start, s->end); + double ahead = mnavFlat(start, s->end); query->nodes[0] = (mnavSearchNode){start, start, start, @@ -874,6 +884,10 @@ static MNAV_NOINLINE mnavResult FindShortestPathWork(mnavQuery* query, const mna { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, start) || !mnavInQueryReach(navmesh, end)) + { + return mnav_errorLimit; + } Shortest s = {.query = query, .navmesh = navmesh, .end = end, .found = MNAV_NO_NODE}; mnavResult result = Begin(&s, filter, startPolygon, start, endPolygon); if (result != mnav_success) @@ -917,15 +931,19 @@ static MNAV_NOINLINE mnavResult FindShortestPathCounted(mnavQuery* query, mnavPolygonId endPolygon, mnavPos3 end, mnavPath* pathOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - FindShortestPathWork(query, navmesh, filter, startPolygon, start, - endPolygon, end, pathOut)); + mnavPath pathMade = {0}; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + FindShortestPathWork(query, navmesh, filter, startPolygon, start, endPolygon, end, + pathOut != nullptr ? &pathMade : nullptr)); + mnavGiveOutput(result, false, pathOut, &pathMade, sizeof(pathMade)); + return result; } -mnavResult mnavFindShortestPath(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId startPolygon, - mnavPos3 start, mnavPolygonId endPolygon, mnavPos3 end, - mnavPath* pathOut) +mnavResult mnavQuery_FindShortestPath(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, + mnavPolygonId endPolygon, mnavPos3 end, + const mnavQueryFilter* filter, mnavPath* pathOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindShortestPathCounted(query, navmesh, filter, startPolygon, start, diff --git a/third_party/maul-nav/src/shortest_geometry.h b/third_party/maul-nav/src/shortest_geometry.h new file mode 100644 index 000000000..63b726976 --- /dev/null +++ b/third_party/maul-nav/src/shortest_geometry.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 Sirac Ozmen +// +// The geometry on the ground that the exact shortest-path search measures +// its intervals with. + +#ifndef MAUL_NAV_SRC_SHORTEST_GEOMETRY_H +#define MAUL_NAV_SRC_SHORTEST_GEOMETRY_H + +#include "maul-nav/base.h" + +#include + +// Twice the signed area of triangle o, a, b on the ground: positive when +// b lies left of the way from o to a. +static inline double mnavCross(mnavPos3 o, mnavPos3 a, mnavPos3 b) +{ + return (a.x - o.x) * (b.z - o.z) - (a.z - o.z) * (b.x - o.x); +} + +static inline double mnavFlat(mnavPos3 a, mnavPos3 b) +{ + double dx = a.x - b.x; + double dz = a.z - b.z; + return sqrt(dx * dx + dz * dz); +} + +static inline mnavPos3 mnavLerp(mnavPos3 a, mnavPos3 b, double t) +{ + return (mnavPos3){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t}; +} + +// The point of segment a, b nearest p on the ground. +static inline mnavPos3 mnavNearestOn(mnavPos3 p, mnavPos3 a, mnavPos3 b) +{ + double dx = b.x - a.x; + double dz = b.z - a.z; + double length2 = dx * dx + dz * dz; + double t = length2 > 0.0 ? ((p.x - a.x) * dx + (p.z - a.z) * dz) / length2 : 0.0; + return mnavLerp(a, b, t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t)); +} + +// The shortest distance on the ground from root r through the interval +// from a to b to point e: an e on r's side of the interval's line is +// mirrored through it first, then the way is straight when it meets the +// interval and turns at the nearer end otherwise. Infinite when r or e +// lies too far from a for its distance's square to be a double, past +// about 1e154 m: the way is no shorter than that distance less the +// interval's length, past MNAV_MAX_PATH_LENGTH, which caps every search's +// length, and the mirror's t could overflow there and make e NaN through +// a 0 times it. +static inline double mnavThrough(mnavPos3 r, mnavPos3 a, mnavPos3 b, mnavPos3 e) +{ + double toA = mnavFlat(r, a); + double fromA = mnavFlat(a, e); + if (isinf(toA) || isinf(fromA)) + { + return (double)INFINITY; + } + double side = mnavCross(a, b, r); + double eSide = mnavCross(a, b, e); + double dx = b.x - a.x; + double dz = b.z - a.z; + double length2 = dx * dx + dz * dz; + if (length2 > 0.0 && ((side > 0.0 && eSide > 0.0) || (side < 0.0 && eSide < 0.0))) + { + double t = ((e.x - a.x) * dx + (e.z - a.z) * dz) / length2; + e.x = 2.0 * (a.x + t * dx) - e.x; + e.z = 2.0 * (a.z + t * dz) - e.z; + } + if (mnavCross(r, a, e) >= 0.0 && mnavCross(r, b, e) <= 0.0) + { + return mnavFlat(r, e); + } + double viaA = toA + mnavFlat(a, e); + double viaB = mnavFlat(r, b) + mnavFlat(b, e); + return viaA < viaB ? viaA : viaB; +} + +#endif // MAUL_NAV_SRC_SHORTEST_GEOMETRY_H diff --git a/third_party/maul-nav/src/spatial.c b/third_party/maul-nav/src/spatial.c index 80921bc38..0d02d4de7 100644 --- a/third_party/maul-nav/src/spatial.c +++ b/third_party/maul-nav/src/spatial.c @@ -4,7 +4,8 @@ // Spatial queries: heights, the nearest wall within a // radius, and random points from a seed. The walls search is Detour's // findDistanceToWall: Dijkstra over polygons from the center's, through -// the edges within the radius; the random point near a center samples the +// the edges within the radius, its radius kept whole and its walls those +// of the center's floor; the random point near a center samples the // polygons it reaches by the area they share with the circle, an // inscribed 32-gon, so that, unlike findRandomPointAroundCircle, the point // lies in the circle. @@ -15,6 +16,7 @@ #include "invariant.h" #include "navmesh.h" #include "nearest.h" +#include "outputs.h" #include "query.h" #include "query_filter.h" @@ -83,6 +85,10 @@ mnavResult mnavGetHeightWork(const mnavNavmesh* navmesh, mnavPolygonId polygon, { return mnav_errorInvalid; } + if (!mnavInQueryReachGround(navmesh, x, z)) + { + return mnav_errorLimit; + } mnavResult result = mnavCheckPolygon(navmesh, polygon); if (result != mnav_success) { @@ -112,12 +118,14 @@ mnavResult mnavGetHeightWork(const mnavNavmesh* navmesh, mnavPolygonId polygon, static MNAV_NOINLINE mnavResult GetHeightCounted(const mnavNavmesh* navmesh, mnavPolygonId polygon, double x, double z, double* heightOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), - mnavGetHeightWork(navmesh, polygon, x, z, heightOut)); + mnavClearOutput(heightOut, sizeof(*heightOut)); + mnavResult result = mnavHostCountInvalid(MNAV_HOST(navmesh), + mnavGetHeightWork(navmesh, polygon, x, z, heightOut)); + return mnavClearOnFailure(result, heightOut, sizeof(*heightOut)); } -mnavResult mnavGetHeight(const mnavNavmesh* navmesh, mnavPolygonId polygon, double x, double z, - double* heightOut) +mnavResult mnavPolygon_GetHeight(const mnavNavmesh* navmesh, mnavPolygonId polygon, double x, + double z, double* heightOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = GetHeightCounted(navmesh, polygon, x, z, heightOut); @@ -229,12 +237,14 @@ static MNAV_NOINLINE mnavResult FindRandomPointCounted(const mnavNavmesh* navmes const mnavQueryFilter* filter, uint64_t seed, mnavRandomPoint* pointOut) { - return mnavHostCountInvalid(MNAV_HOST(navmesh), - FindRandomPointWork(navmesh, filter, seed, pointOut)); + mnavClearOutput(pointOut, sizeof(*pointOut)); + mnavResult result = mnavHostCountInvalid(MNAV_HOST(navmesh), + FindRandomPointWork(navmesh, filter, seed, pointOut)); + return mnavClearOnFailure(result, pointOut, sizeof(*pointOut)); } -mnavResult mnavFindRandomPoint(const mnavNavmesh* navmesh, const mnavQueryFilter* filter, - uint64_t seed, mnavRandomPoint* pointOut) +mnavResult mnavNavmesh_FindRandomPoint(const mnavNavmesh* navmesh, uint64_t seed, + const mnavQueryFilter* filter, mnavRandomPoint* pointOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindRandomPointCounted(navmesh, filter, seed, pointOut); @@ -251,17 +261,21 @@ typedef struct Ring mnavPos3 center; double radius; bool limited; - mnavWall wall; + mnavNearestWall nearest; // Whether the walls are listed into a buffer, the nearest kept, rather // than the nearest noted. bool listing; - mnavWallSegment* walls; + mnavWall* walls; int32_t capacity; int32_t count; + // The heights a wall's nearest point may lie between to be on the + // center's floor; unbounded for a search that notes no wall. + double low; + double high; } Ring; // The open list, by cost, then by node index. -static bool Sooner(const mnavQuery* q, int32_t a, int32_t b) +MNAV_NODISCARD static bool Sooner(const mnavQuery* q, int32_t a, int32_t b) { double ca = q->nodes[a].cost; double cb = q->nodes[b].cost; @@ -326,21 +340,30 @@ static double SegmentDistance(mnavPos3 p, mnavPos3 a, mnavPos3 b, mnavPos3* near return dx * dx + dz * dz; } +// Wall ab, its point nearest the center at a distance. +static mnavWall MakeWall(mnavPos3 a, mnavPos3 b, mnavPos3 nearest, double distance) +{ + double ex = b.x - a.x; + double ez = b.z - a.z; + double length = sqrt(ex * ex + ez * ez); + // A polygon lies to the negative side of its edges, which is this way. + mnavVec3 normal = length > 0.0 ? (mnavVec3){(float)(ez / length), 0.0f, (float)(-ex / length)} + : (mnavVec3){0.0f, 0.0f, 0.0f}; + return (mnavWall){a, b, nearest, normal, distance}; +} + // Notes wall ab when nearer than the nearest so far; ties keep the first. static void NoteWall(Ring* r, mnavPos3 a, mnavPos3 b) { mnavPos3 nearest; double distance = sqrt(SegmentDistance(r->center, a, b, &nearest)); - if (distance > r->radius || (r->wall.found && distance >= r->wall.distance)) + if (distance > r->radius || nearest.y < r->low || nearest.y > r->high || + (r->nearest.found && distance >= r->nearest.wall.distance)) { return; } - double dx = r->center.x - nearest.x; - double dz = r->center.z - nearest.z; - double length = sqrt(dx * dx + dz * dz); - mnavPos3 normal = - length > 0.0 ? (mnavPos3){dx / length, 0.0, dz / length} : (mnavPos3){0.0, 0.0, 0.0}; - r->wall = (mnavWall){true, r->wall.limited, distance, nearest, normal}; + r->nearest.found = true; + r->nearest.wall = MakeWall(a, b, nearest, distance); } // Lists wall ab when within the radius among the nearest the buffer holds, @@ -349,7 +372,7 @@ static void ListWall(Ring* r, mnavPos3 a, mnavPos3 b) { mnavPos3 nearest; double distance = sqrt(SegmentDistance(r->center, a, b, &nearest)); - if (distance > r->radius) + if (distance > r->radius || nearest.y < r->low || nearest.y > r->high) { return; } @@ -365,14 +388,8 @@ static void ListWall(Ring* r, mnavPos3 a, mnavPos3 b) return; } int32_t last = held < r->capacity ? held : r->capacity - 1; - memmove(&r->walls[at + 1], &r->walls[at], (size_t)(last - at) * sizeof(mnavWallSegment)); - double ex = b.x - a.x; - double ez = b.z - a.z; - double length = sqrt(ex * ex + ez * ez); - // A polygon lies to the negative side of its edges, which is this way. - mnavPos3 normal = - length > 0.0 ? (mnavPos3){ez / length, 0.0, -ex / length} : (mnavPos3){0.0, 0.0, 0.0}; - r->walls[at] = (mnavWallSegment){a, b, normal, distance}; + memmove(&r->walls[at + 1], &r->walls[at], (size_t)(last - at) * sizeof(mnavWall)); + r->walls[at] = MakeWall(a, b, nearest, distance); } // Opens or lowers the node of polygon (slot, polygon) entered through @@ -456,7 +473,7 @@ static mnavPos3 Between(mnavPos3 a, mnavPos3 b, double t) } // Whether a link crosses edge j into a polygon the filter includes. -static bool Crosses(const Ring* r, const mnavLink* link, int32_t j) +MNAV_NODISCARD static bool Crosses(const Ring* r, const mnavLink* link, int32_t j) { const mnavTile* target = r->navmesh->slots[link->target.slot - 1].tile; return link->edge == j && @@ -567,6 +584,23 @@ static void Visit(Ring* r, int32_t n) } } +// Bounds the walls to the center's floor: from the agent's step height +// below its height on its polygon to the agent's height above, by the +// agent the navmesh's tiles are baked for; its own height when it lies +// off the polygon. A wall of another floor above or below is no wall here. +static void Floor(Ring* r, mnavPolygonId polygon) +{ + double height = r->center.y; + double on = 0.0; + if (mnavGetHeightWork(r->navmesh, polygon, r->center.x, r->center.z, &on) == mnav_success) + { + height = on; + } + const mnavAgentProfile* agent = &r->navmesh->def.bake.agent; + r->low = height - (double)agent->stepHeight; + r->high = height + (double)agent->height; +} + static mnavResult BeginRing(Ring* r, mnavPolygonId polygon) { const mnavQueryFilter* usable = nullptr; @@ -590,42 +624,56 @@ static mnavResult BeginRing(Ring* r, mnavPolygonId polygon) q->nodeCount = 1; q->indexHeap[0] = 0; q->heapCount = 1; - r->wall = (mnavWall){false, false, r->radius, r->center, {0.0, 0.0, 0.0}}; + r->nearest = (mnavNearestWall){0}; return mnav_success; } -static bool GoodRing(const mnavQuery* query, const mnavNavmesh* navmesh, mnavPos3 center, - double radius) +MNAV_NODISCARD static bool GoodRing(const mnavQuery* query, const mnavNavmesh* navmesh, + mnavPos3 center, double radius) { return query != nullptr && navmesh != nullptr && isfinite(center.x) && isfinite(center.y) && isfinite(center.z) && isfinite(radius) && radius >= 0.0; } +// Whether a well formed ring's center and radius lie within +// MNAV_MAX_QUERY_DISTANCE: refused with mnav_errorLimit when not. +MNAV_NODISCARD static bool InReach(const mnavNavmesh* navmesh, mnavPos3 center, double radius) +{ + return mnavInQueryReach(navmesh, center) && radius <= MNAV_MAX_QUERY_DISTANCE; +} + static MNAV_NOINLINE mnavResult FindWallDistanceWork(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPolygonId polygon, mnavPos3 center, - double radius, mnavWall* wallOut) + double radius, mnavNearestWall* nearestOut) { if (query != nullptr && mnavHostCalledBack(&query->host)) { return mnav_errorBusy; } - if (wallOut == nullptr || !GoodRing(query, navmesh, center, radius)) + if (nearestOut == nullptr || !GoodRing(query, navmesh, center, radius)) { return mnav_errorInvalid; } - Ring r = {query, navmesh, filter, center, radius, false, {0}, false, nullptr, 0, 0}; + if (!InReach(navmesh, center, radius)) + { + return mnav_errorLimit; + } + Ring r = { + query, navmesh, filter, center, radius, false, {0}, false, nullptr, 0, + 0, -(double)INFINITY, (double)INFINITY}; mnavResult result = BeginRing(&r, polygon); if (result != mnav_success) { return result; } + Floor(&r, polygon); while (query->heapCount > 0) { Visit(&r, Pop(query)); } - r.wall.limited = r.limited; - *wallOut = r.wall; + r.nearest.limited = r.limited; + *nearestOut = r.nearest; return mnav_success; } @@ -633,29 +681,32 @@ static MNAV_NOINLINE mnavResult FindWallDistanceCounted(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPolygonId polygon, mnavPos3 center, - double radius, mnavWall* wallOut) + double radius, mnavNearestWall* nearestOut) { - return mnavHostCountInvalid( - mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - FindWallDistanceWork(query, navmesh, filter, polygon, center, radius, wallOut)); + mnavNearestWall nearestMade = {0}; + mnavResult result = + mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + FindWallDistanceWork(query, navmesh, filter, polygon, center, radius, + nearestOut != nullptr ? &nearestMade : nullptr)); + mnavGiveOutput(result, false, nearestOut, &nearestMade, sizeof(nearestMade)); + return result; } -mnavResult mnavFindWallDistance(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId polygon, - mnavPos3 center, double radius, mnavWall* wallOut) +mnavResult mnavQuery_FindWallDistance(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId polygon, mnavPos3 center, double radius, + const mnavQueryFilter* filter, mnavNearestWall* nearestOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = - FindWallDistanceCounted(query, navmesh, filter, polygon, center, radius, wallOut); + FindWallDistanceCounted(query, navmesh, filter, polygon, center, radius, nearestOut); mnavFloatEnvLeave(env); return result; } static MNAV_NOINLINE mnavResult FindWallsWork(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPolygonId polygon, - mnavPos3 center, double radius, - mnavWallSegment* wallsOut, int32_t capacity, - mnavWallsFound* foundOut) + mnavPos3 center, double radius, mnavWall* wallsOut, + int32_t capacity, mnavWallsFound* foundOut) { if (query != nullptr && mnavHostCalledBack(&query->host)) { @@ -666,12 +717,20 @@ static MNAV_NOINLINE mnavResult FindWallsWork(mnavQuery* query, const mnavNavmes { return mnav_errorInvalid; } - Ring r = {query, navmesh, filter, center, radius, false, {0}, true, wallsOut, capacity, 0}; + if (!InReach(navmesh, center, radius)) + { + return mnav_errorLimit; + } + Ring r = {query, navmesh, filter, center, + radius, false, {0}, true, + wallsOut, capacity, 0, -(double)INFINITY, + (double)INFINITY}; mnavResult result = BeginRing(&r, polygon); if (result != mnav_success) { return result; } + Floor(&r, polygon); while (query->heapCount > 0) { Visit(&r, Pop(query)); @@ -683,18 +742,22 @@ static MNAV_NOINLINE mnavResult FindWallsWork(mnavQuery* query, const mnavNavmes static MNAV_NOINLINE mnavResult FindWallsCounted(mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPolygonId polygon, mnavPos3 center, - double radius, mnavWallSegment* wallsOut, + double radius, mnavWall* wallsOut, int32_t capacity, mnavWallsFound* foundOut) { - return mnavHostCountInvalid(mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - FindWallsWork(query, navmesh, filter, polygon, center, radius, - wallsOut, capacity, foundOut)); + mnavWallsFound foundMade = {0}; + mnavResult result = mnavHostCountInvalid( + mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), + FindWallsWork(query, navmesh, filter, polygon, center, radius, wallsOut, capacity, + foundOut != nullptr ? &foundMade : nullptr)); + mnavClearArray(result, result == mnav_errorCapacity, wallsOut, capacity, sizeof(*wallsOut)); + mnavGiveOutput(result, result == mnav_errorCapacity, foundOut, &foundMade, sizeof(foundMade)); + return result; } -mnavResult mnavFindWalls(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId polygon, mnavPos3 center, - double radius, mnavWallSegment* wallsOut, int32_t capacity, - mnavWallsFound* foundOut) +mnavResult mnavQuery_FindWalls(mnavQuery* query, const mnavNavmesh* navmesh, mnavPolygonId polygon, + mnavPos3 center, double radius, const mnavQueryFilter* filter, + mnavWall* wallsOut, int32_t capacity, mnavWallsFound* foundOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindWallsCounted(query, navmesh, filter, polygon, center, radius, wallsOut, @@ -715,7 +778,13 @@ static MNAV_NOINLINE mnavResult FindRandomPointAroundWork( { return mnav_errorInvalid; } - Ring r = {query, navmesh, filter, center, radius, false, {0}, false, nullptr, 0, 0}; + if (!InReach(navmesh, center, radius)) + { + return mnav_errorLimit; + } + Ring r = { + query, navmesh, filter, center, radius, false, {0}, false, nullptr, 0, + 0, -(double)INFINITY, (double)INFINITY}; mnavResult result = BeginRing(&r, polygon); if (result != mnav_success) { @@ -773,15 +842,19 @@ static MNAV_NOINLINE mnavResult FindRandomPointAroundCounted( mnavQuery* query, const mnavNavmesh* navmesh, const mnavQueryFilter* filter, mnavPolygonId polygon, mnavPos3 center, double radius, uint64_t seed, mnavRandomPoint* pointOut) { - return mnavHostCountInvalid( + mnavRandomPoint pointMade = {0}; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - FindRandomPointAroundWork(query, navmesh, filter, polygon, center, radius, seed, pointOut)); + FindRandomPointAroundWork(query, navmesh, filter, polygon, center, radius, seed, + pointOut != nullptr ? &pointMade : nullptr)); + mnavGiveOutput(result, false, pointOut, &pointMade, sizeof(pointMade)); + return result; } -mnavResult mnavFindRandomPointAround(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId polygon, - mnavPos3 center, double radius, uint64_t seed, - mnavRandomPoint* pointOut) +mnavResult mnavQuery_FindRandomPointAround(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId polygon, mnavPos3 center, double radius, + uint64_t seed, const mnavQueryFilter* filter, + mnavRandomPoint* pointOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = FindRandomPointAroundCounted(query, navmesh, filter, polygon, center, diff --git a/third_party/maul-nav/src/steer.c b/third_party/maul-nav/src/steer.c index 6e16d70b5..33a89b6e7 100644 --- a/third_party/maul-nav/src/steer.c +++ b/third_party/maul-nav/src/steer.c @@ -13,12 +13,12 @@ #include #include -static bool InRange(float v, float most) +MNAV_NODISCARD static bool InRange(float v, float most) { return v >= 0.0f && v <= most; } -static bool Finite(mnavPos3 p) +MNAV_NODISCARD static bool Finite(mnavPos3 p) { return isfinite(p.x) && isfinite(p.y) && isfinite(p.z); } @@ -30,10 +30,59 @@ static double Length(double x, double z) return sqrt(x * x + z * z); } -// The ground distance from a to b. +// Whether a lies within 2^500 of the origin on the ground, where the +// difference from another such point and its square overflow no double. +MNAV_NODISCARD static bool Within(mnavPos3 a) +{ + return fabs(a.x) <= 0x1p500 && fabs(a.z) <= 0x1p500; +} + +// For a pair of corners not both within 2^500: half of b - a, which no two +// finite corners overflow, scaled by a power of its own size so that its +// squares are neither past nor below a double's range; *scale receives +// how many times that is half the true difference, a power of two. +static void Far(mnavPos3 a, mnavPos3 b, double* x, double* z, double* scale) +{ + *x = b.x * 0.5 - a.x * 0.5; + *z = b.z * 0.5 - a.z * 0.5; + double m = fabs(*x) > fabs(*z) ? fabs(*x) : fabs(*z); + *scale = 2.0; + if (m > 0x1p500) + { + *x *= 0x1p-600; + *z *= 0x1p-600; + *scale = 0x1p601; + } +} + +// The ground distance from a to b: as it is between corners within 2^500, +// and otherwise from the scaled half difference, so that no square +// overflows or vanishes; inf when the distance is past a double's range. static double Ground(mnavPos3 a, mnavPos3 b) { - return Length(b.x - a.x, b.z - a.z); + if (Within(a) && Within(b)) + { + return Length(b.x - a.x, b.z - a.z); + } + double x; + double z; + double scale; + Far(a, b, &x, &z, &scale); + return Length(x, z) * scale; +} + +// The unit direction on the ground from a to b, or 0 for none. +static mnavPos3 Toward(mnavPos3 a, mnavPos3 b) +{ + double x = b.x - a.x; + double z = b.z - a.z; + double scale = 1.0; + if (!Within(a) || !Within(b)) + { + Far(a, b, &x, &z, &scale); + } + double length = Length(x, z); + return length > 0.0 ? (mnavPos3){x / length, 0.0, z / length} : (mnavPos3){0.0, 0.0, 0.0}; } // The direction to steer in, of length 1, or 0 when there is none: @@ -43,6 +92,23 @@ static mnavPos3 Direction(const mnavCorners* corners, int32_t n, bool anticipate { mnavPos3 p = corners->points[0]; mnavPos3 next = corners->points[n]; + bool far = !Within(p) || !Within(next) || + (anticipate && n + 1 < corners->pointCount && !Within(corners->points[n + 1])); + if (far) + { + // The same direction with the length to the next corner taken out: + // toward it less half the direction to the corner after it. + mnavPos3 u = Toward(p, next); + bool none = u.x == 0.0 && u.z == 0.0; + if (!none && anticipate && n + 1 < corners->pointCount && + Ground(p, corners->points[n + 1]) > 0.001) + { + mnavPos3 v = Toward(p, corners->points[n + 1]); + u = Toward((mnavPos3){0.0, 0.0, 0.0}, + (mnavPos3){u.x - v.x * 0.5, 0.0, u.z - v.z * 0.5}); + } + return u; + } double x = next.x - p.x; double z = next.z - p.z; if (anticipate && n + 1 < corners->pointCount) @@ -77,7 +143,8 @@ static MNAV_NOINLINE mnavResult SteerWork(const mnavCorners* corners, const mnav mnavSteering* steeringOut) { if (corners == nullptr || def == nullptr || steeringOut == nullptr || corners->pointCount < 1 || - corners->points == nullptr || (corners->linkCount > 0 && corners->links == nullptr) || + corners->points == nullptr || corners->linkCount < 0 || + (corners->linkCount > 0 && corners->links == nullptr) || def->cookie != MNAV_STEER_DEF_COOKIE) { return mnav_errorInvalid; @@ -103,7 +170,7 @@ static MNAV_NOINLINE mnavResult SteerWork(const mnavCorners* corners, const mnav return mnav_errorInvalid; } } - *steeringOut = (mnavSteering){{0.0, 0.0, 0.0}, mnav_steerArrived, -1}; + *steeringOut = (mnavSteering){{0.0f, 0.0f, 0.0f}, mnav_steerArrived, -1}; double left = Left(corners); if (left <= (double)def->arriveDistance) { @@ -113,7 +180,7 @@ static MNAV_NOINLINE mnavResult SteerWork(const mnavCorners* corners, const mnav bool takeoff = corners->linkCount > 0 && corners->links[0].point == 1; if (takeoff && Ground(corners->points[0], corners->points[1]) <= (double)def->arriveDistance) { - *steeringOut = (mnavSteering){{0.0, 0.0, 0.0}, mnav_steerAtLink, 0}; + *steeringOut = (mnavSteering){{0.0f, 0.0f, 0.0f}, mnav_steerAtLink, 0}; return mnav_success; } // The first corner away from the agent on the ground: one straight @@ -124,14 +191,14 @@ static MNAV_NOINLINE mnavResult SteerWork(const mnavCorners* corners, const mnav n += 1; } // Toward a takeoff, straight: the link starts there. - mnavPos3 direction = Direction(corners, n, def->anticipateTurns && !takeoff); + mnavPos3 direction = Direction(corners, n, def->enableTurnAnticipation && !takeoff); double speed = (double)def->maxSpeed; if (left < (double)def->slowDistance) { speed *= left / (double)def->slowDistance; } - *steeringOut = - (mnavSteering){{direction.x * speed, 0.0, direction.z * speed}, mnav_steerMoving, -1}; + *steeringOut = (mnavSteering){ + {(float)(direction.x * speed), 0.0f, (float)(direction.z * speed)}, mnav_steerMoving, -1}; return mnav_success; } diff --git a/third_party/maul-nav/src/surface.c b/third_party/maul-nav/src/surface.c index 7a89b632c..a0d66b811 100644 --- a/third_party/maul-nav/src/surface.c +++ b/third_party/maul-nav/src/surface.c @@ -7,6 +7,7 @@ #include "fpenv.h" #include "navmesh.h" #include "nearest.h" +#include "outputs.h" #include "polymesh.h" #include "query.h" #include "query_filter.h" @@ -119,7 +120,7 @@ static Ground At(const Edge* e, double u) } // Whether a link of edge j leads to a polygon the filter includes. -static bool Open(const Mover* m, const mnavLink* link, int32_t j) +MNAV_NODISCARD static bool Open(const Mover* m, const mnavLink* link, int32_t j) { const mnavTile* across = m->navmesh->slots[link->target.slot - 1].tile; return link->edge == j && @@ -128,8 +129,8 @@ static bool Open(const Mover* m, const mnavLink* link, int32_t j) // Whether u lies on a wall of edge j: in the closure of the part of the // side no open link covers. -static bool OnWall(const Mover* m, const mnavTile* tile, int32_t polygon, int32_t j, const Edge* e, - double u) +MNAV_NODISCARD static bool OnWall(const Mover* m, const mnavTile* tile, int32_t polygon, int32_t j, + const Edge* e, double u) { bool below = u <= (e->au < e->bu ? e->au : e->bu); bool above = u >= (e->au < e->bu ? e->bu : e->au); @@ -184,7 +185,7 @@ static void Side(Mover* m, int32_t n, const mnavTile* tile, int32_t polygon, int } // Whether the wanted point lies in a polygon, its edges included. -static bool Holds(const Ground* corners, int32_t count, Ground p) +MNAV_NODISCARD static bool Holds(const Ground* corners, int32_t count, Ground p) { for (int32_t k = 0; k < count; ++k) { @@ -200,7 +201,7 @@ static bool Holds(const Ground* corners, int32_t count, Ground p) // Walks node n's polygon: true when it holds the wanted point; otherwise // visits its open neighbours and offers its walls. -static bool Walk(Mover* m, int32_t n) +MNAV_NODISCARD static bool Walk(Mover* m, int32_t n) { const mnavSearchNode* node = &m->query->nodes[n]; const mnavSlot* slot = &m->navmesh->slots[node->slot]; @@ -286,6 +287,10 @@ mnavResult mnavMoveAlongSurfaceWork(mnavQuery* query, const mnavNavmesh* navmesh { return mnav_errorInvalid; } + if (!mnavInQueryReach(navmesh, start) || !mnavInQueryReachGround(navmesh, end.x, end.z)) + { + return mnav_errorLimit; + } const mnavQueryFilter* usable = nullptr; mnavResult result = mnavCheckPolygon(navmesh, startPolygon); result = result == mnav_success ? mnavCheckFilter(filter, &usable) : result; @@ -299,7 +304,7 @@ mnavResult mnavMoveAlongSurfaceWork(mnavQuery* query, const mnavNavmesh* navmesh Ground from = {start.x, start.z}; Ground to = {end.x, end.z}; Ground center = {(from.x + to.x) * 0.5, (from.z + to.z) * 0.5}; - double radius = sqrt(Distance2(from, to)) * 0.5 + (double)navmesh->def.cellSize; + double radius = sqrt(Distance2(from, to)) * 0.5 + (double)navmesh->def.bake.cellSize; Mover m = {query, navmesh, usable, to, center, radius * radius, from, Distance2(from, to), 0, false, false, 1}; // The move takes the node table a sliced search holds: that search @@ -351,14 +356,18 @@ static MNAV_NOINLINE mnavResult MoveAlongSurfaceCounted(mnavQuery* query, mnavPolygonId startPolygon, mnavPos3 start, mnavPos3 end, mnavMove* moveOut) { - return mnavHostCountInvalid( + mnavMove moveMade = {0}; + mnavResult result = mnavHostCountInvalid( mnavHostFirst(MNAV_HOST(query), MNAV_HOST(navmesh)), - mnavMoveAlongSurfaceWork(query, navmesh, filter, startPolygon, start, end, moveOut)); + mnavMoveAlongSurfaceWork(query, navmesh, filter, startPolygon, start, end, + moveOut != nullptr ? &moveMade : nullptr)); + mnavGiveOutput(result, false, moveOut, &moveMade, sizeof(moveMade)); + return result; } -mnavResult mnavMoveAlongSurface(mnavQuery* query, const mnavNavmesh* navmesh, - const mnavQueryFilter* filter, mnavPolygonId startPolygon, - mnavPos3 start, mnavPos3 end, mnavMove* moveOut) +mnavResult mnavQuery_MoveAlongSurface(mnavQuery* query, const mnavNavmesh* navmesh, + mnavPolygonId startPolygon, mnavPos3 start, mnavPos3 end, + const mnavQueryFilter* filter, mnavMove* moveOut) { uint64_t env = mnavFloatEnvEnter(); mnavResult result = diff --git a/third_party/maul-nav/src/terrain.c b/third_party/maul-nav/src/terrain.c index cba821825..1516ea003 100644 --- a/third_party/maul-nav/src/terrain.c +++ b/third_party/maul-nav/src/terrain.c @@ -8,6 +8,7 @@ #include "terrain.h" +#include "input.h" #include "raster.h" #include "maul-nav/bake.h" @@ -18,7 +19,7 @@ static mnavInputResult Refuse(mnavResult result, mnavInputElement element, int32_t index) { - return (mnavInputResult){result, element, index}; + return mnavInputElementResult(result, element, index); } // A sample's place. @@ -31,7 +32,7 @@ static mnavVec3 Sample(const mnavTerrain* t, int32_t column, int32_t row) (float)((double)t->origin.z + (double)row * (double)t->spacingZ)}; } -static bool GoodShape(const mnavTerrain* t) +MNAV_NODISCARD static bool GoodShape(const mnavTerrain* t) { return t->columns >= 2 && t->rows >= 2 && t->heights != nullptr && isfinite(t->spacingX) && isfinite(t->spacingZ) && t->spacingX > 0.0f && t->spacingZ > 0.0f && @@ -39,7 +40,7 @@ static bool GoodShape(const mnavTerrain* t) } // Whether a place lies within the extent input may have. -static bool InExtent(const mnavBakeDef* def, mnavVec3 v) +MNAV_NODISCARD static bool InExtent(const mnavBakeDef* def, mnavVec3 v) { float ground = def->cellSize * (float)MNAV_MAX_EXTENT_CELLS; float vertical = def->cellHeight * (float)MNAV_MAX_HEIGHT_CELLS; @@ -103,8 +104,8 @@ int64_t mnavTerrainTriangles(const mnavTerrain* terrain) // The cells along one axis whose span, from the first sample at origin // with the spacing given, meets low to high; false when none do. -static bool Span(double origin, double spacing, int32_t samples, double low, double high, - int32_t* first, int32_t* last) +MNAV_NODISCARD static bool Span(double origin, double spacing, int32_t samples, double low, + double high, int32_t* first, int32_t* last) { double a = floor((low - origin) / spacing) - 1.0; double b = floor((high - origin) / spacing) + 1.0; diff --git a/third_party/maul-nav/src/terrain.h b/third_party/maul-nav/src/terrain.h index 05bc928b5..b7714206b 100644 --- a/third_party/maul-nav/src/terrain.h +++ b/third_party/maul-nav/src/terrain.h @@ -22,12 +22,12 @@ int64_t mnavTerrainTriangles(const mnavTerrain* terrain); // The cells that may reach a tile's cells, border included: columns c0 to // c1 and rows r0 to r1. False when none may. -bool mnavTerrainCells(const mnavTerrain* terrain, const mnavTileFrame* frame, int32_t* c0, - int32_t* c1, int32_t* r0, int32_t* r1); +MNAV_NODISCARD bool mnavTerrainCells(const mnavTerrain* terrain, const mnavTileFrame* frame, + int32_t* c0, int32_t* c1, int32_t* r0, int32_t* r1); // Triangle k (0 or 1) of cell (column, row): its corners and given area. // False for a hole. -bool mnavTerrainTriangle(const mnavTerrain* terrain, int32_t column, int32_t row, int32_t k, - mnavVec3 corners[3], mnavAreaType* area); +MNAV_NODISCARD bool mnavTerrainTriangle(const mnavTerrain* terrain, int32_t column, int32_t row, + int32_t k, mnavVec3 corners[3], mnavAreaType* area); #endif // MAUL_NAV_SRC_TERRAIN_H diff --git a/third_party/maul-nav/src/tile.c b/third_party/maul-nav/src/tile.c index 77e203453..4115738aa 100644 --- a/third_party/maul-nav/src/tile.c +++ b/third_party/maul-nav/src/tile.c @@ -26,7 +26,7 @@ #define VERTEX_BYTES 6 #define POLYGON_BYTES 2 #define EDGE_BYTES 5 -#define PART_BYTES 2 +#define PART_BYTES 3 #define TRIANGLE_BYTES 3 #define INFO_BYTES 48 @@ -97,7 +97,7 @@ static void WriteField(mnavByteWriter* w, int32_t field, const mnavTileInfo* inf for (int32_t p = 0; p < mesh->polygonCount; ++p) { mnavPutU8(w, (uint64_t)(detail->parts[p].vertexCount - mesh->polygons[p].count)); - mnavPutU8(w, detail->parts[p].triangleCount); + mnavPutU16(w, detail->parts[p].triangleCount); } break; case FIELD_TRIANGLES: @@ -198,7 +198,7 @@ mnavResult mnavEncodeTile(mnavMemory* memory, const mnavTileInfo* info, const mn } } MNAV_ASSERT(w.at == out + total); - uint64_t hash = mnavHash64(MNAV_HASH_INIT, out + MNAV_TILE_HEADER_BYTES, (int32_t)payload); + uint64_t hash = mnavHash64(MNAV_HASH_INIT, out + MNAV_TILE_HEADER_BYTES, (int64_t)payload); w.at = out; WriteHeader(&w, info, payload, hash, fields); *bytes = out; @@ -230,13 +230,13 @@ typedef struct Counts int64_t detailTriangles; } Counts; -static bool Finite(double v) +MNAV_NODISCARD static bool Finite(double v) { return isfinite(v); } // Checks the settings the TILE field records. -static bool ValidInfo(const mnavTileInfo* info) +MNAV_NODISCARD static bool ValidInfo(const mnavTileInfo* info) { int32_t reach = MNAV_MAX_EXTENT_CELLS / (info->tileCells > 0 ? info->tileCells : 1); bool grid = info->tileCells >= MNAV_MIN_TILE_CELLS && info->tileCells <= MNAV_MAX_TILE_CELLS && @@ -271,7 +271,7 @@ static mnavTileResult ReadHeader(const uint8_t* bytes, size_t size, mnavTileInfo // The payload's hash takes a 32-bit size; no tile within the caps // comes near it. if (payload != size - MNAV_TILE_HEADER_BYTES || payload > INT32_MAX || - mnavHash64(MNAV_HASH_INIT, bytes + MNAV_TILE_HEADER_BYTES, (int32_t)payload) != hash) + mnavHash64(MNAV_HASH_INIT, bytes + MNAV_TILE_HEADER_BYTES, (int64_t)payload) != hash) { return Refuse(mnav_errorFormat, mnav_tilePayload, -1); } @@ -401,8 +401,8 @@ static mnavTileResult ReadVertices(mnavByteReader* r, mnavPolyMesh* mesh, int32_ } // Reads one polygon's head and edges and checks each value alone. -static bool ReadPolygon(mnavByteReader* r, const mnavPolyMesh* mesh, int32_t polygons, - mnavPolygon* polygon) +MNAV_NODISCARD static bool ReadPolygon(mnavByteReader* r, const mnavPolyMesh* mesh, + int32_t polygons, mnavPolygon* polygon) { *polygon = (mnavPolygon){0}; memset(polygon->vertices, 0xFF, sizeof(polygon->vertices)); @@ -441,7 +441,7 @@ static int64_t Area2(const mnavMeshVertex* a, const mnavMeshVertex* b, const mna // Whether the polygon is convex with a positive area: no edge of zero // length, and every vertex on the inner side of every edge or on it. A // vertex used twice fails it too, as the edges round it double back. -static bool Convex(const mnavPolyMesh* mesh, const mnavPolygon* polygon) +MNAV_NODISCARD static bool Convex(const mnavPolyMesh* mesh, const mnavPolygon* polygon) { int64_t area = 0; for (int32_t k = 0; k < polygon->count; ++k) @@ -486,7 +486,7 @@ static uint8_t SideOf(const mnavMeshVertex* a, const mnavMeshVertex* b, int32_t // Whether each edge's neighbor runs the same edge back to it, and each // edge without one carries exactly the tile side it lies on. A polygon // naming itself fails, as it never runs its own edge backward. -static bool Linked(const mnavPolyMesh* mesh, int32_t p) +MNAV_NODISCARD static bool Linked(const mnavPolyMesh* mesh, int32_t p) { const mnavPolygon* polygon = &mesh->polygons[p]; for (int32_t k = 0; k < polygon->count; ++k) @@ -550,14 +550,14 @@ static mnavTileResult ReadParts(mnavByteReader* r, const mnavPolyMesh* mesh, con for (int32_t p = 0; p < mesh->polygonCount; ++p) { int64_t count = mesh->polygons[p].count + (int64_t)mnavGetU8(r); - int64_t parts = (int64_t)mnavGetU8(r); - if (r->exhausted || count > MNAV_DETAIL_VERTICES || parts < 1 || - parts > 2 * MNAV_DETAIL_VERTICES) + int64_t parts = (int64_t)mnavGetU16(r); + if (r->exhausted || count > MNAV_MAX_DETAIL_VERTICES || parts < 1 || + parts > 2 * MNAV_MAX_DETAIL_VERTICES) { return Refuse(mnav_errorFormat, mnav_tileDetailParts, p); } - detail->parts[p] = - (mnavDetailPart){(int32_t)vertices, (int32_t)triangles, (uint8_t)count, (uint8_t)parts}; + detail->parts[p] = (mnavDetailPart){(int32_t)vertices, (int32_t)triangles, (uint8_t)count, + (uint16_t)parts}; vertices += count; extra += count - mesh->polygons[p].count; triangles += parts; diff --git a/third_party/maul-nav/src/tile_cache.c b/third_party/maul-nav/src/tile_cache.c index 1a184c99c..e1f747381 100644 --- a/third_party/maul-nav/src/tile_cache.c +++ b/third_party/maul-nav/src/tile_cache.c @@ -143,7 +143,7 @@ mnavHostCode* mnavTileCacheHost(mnavTileCache* cache) return cache != nullptr ? &cache->host : nullptr; } -uint64_t mnavGetTileCacheMisuse(const mnavTileCache* cache) +uint64_t mnavTileCache_GetMisuse(const mnavTileCache* cache) { return cache != nullptr ? mnavHostMisuse(&cache->host) : 0; } @@ -160,7 +160,7 @@ void mnavUncacheTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ) cache->count -= 1; } -void mnavDropCachedTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ) +void mnavTileCache_DropTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ) { if (cache != nullptr && !mnavHostCalledBack(&cache->host)) { @@ -168,7 +168,7 @@ void mnavDropCachedTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ) } } -uint64_t mnavGetTileCacheBytes(const mnavTileCache* cache) +uint64_t mnavTileCache_GetBytes(const mnavTileCache* cache) { return cache != nullptr ? cache->memory.used : 0; } diff --git a/third_party/maul-nav/src/tile_cache.h b/third_party/maul-nav/src/tile_cache.h index 2d619fffc..ec6161e23 100644 --- a/third_party/maul-nav/src/tile_cache.h +++ b/third_party/maul-nav/src/tile_cache.h @@ -66,7 +66,7 @@ mnavResult mnavCacheTile(mnavTileCache* cache, mnavMemory* scratch, const mnavCa // What the cache holds for a tile, or NULL. const mnavCachedTile* mnavFindCachedTile(const mnavTileCache* cache, int32_t tileX, int32_t tileZ); -// Drops what the cache holds for a tile, as mnavDropCachedTile does, for +// Drops what the cache holds for a tile, as mnavTileCache_DropTile does, for // a call that has marked the cache itself. void mnavUncacheTile(mnavTileCache* cache, int32_t tileX, int32_t tileZ); diff --git a/third_party/maul-nav/src/tile_index.c b/third_party/maul-nav/src/tile_index.c index b4a6e3c84..d32736b21 100644 --- a/third_party/maul-nav/src/tile_index.c +++ b/third_party/maul-nav/src/tile_index.c @@ -268,8 +268,7 @@ static mnavBakeReport* Begin(mnavBakeReport* reportOut, mnavBakeReport* ignored) mnavBakeReport* report = reportOut != nullptr ? reportOut : ignored; *report = (mnavBakeReport){0}; report->stage = mnav_stageInput; - report->mesh = -1; - report->input = (mnavInputResult){mnav_success, mnav_elementNone, -1}; + report->input = mnavInputElementResult(mnav_success, mnav_elementNone, -1); return report; } @@ -300,8 +299,7 @@ static MNAV_NOINLINE mnavResult CreateTileIndexWork(const mnavBakeDef* def, mnavInputResult input = mnavCheckTriangleMesh(def, &meshes[m]); if (input.result != mnav_success) { - report->mesh = m; - report->input = input; + report->input = mnavInputNamed(input, mnav_inputMesh, m); return report->result = input.result; } } @@ -347,8 +345,7 @@ static MNAV_NOINLINE mnavResult CreateTileIndex2DWork(const mnavBakeDef* def, mnavInputResult input = mnavCheckOutline(def, &outlines[o]); if (input.result != mnav_success) { - report->mesh = o; - report->input = input; + report->input = mnavInputNamed(input, mnav_inputOutline, o); return report->result = input.result; } } @@ -379,15 +376,15 @@ mnavHostCode* mnavTileIndexHost(const mnavTileIndex* index) return index != nullptr ? (mnavHostCode*)&index->host : nullptr; } -uint64_t mnavGetTileIndexMisuse(const mnavTileIndex* index) +uint64_t mnavTileIndex_GetMisuse(const mnavTileIndex* index) { return index != nullptr ? mnavHostMisuse(&index->host) : 0; } // Whether an index was made for the def's grid, of this kind of input and // this many of it. -static bool FitsGrid(const mnavTileIndex* index, const mnavBakeDef* def, const mnavBakeCells* cells, - bool outlines, int32_t count) +MNAV_NODISCARD static bool FitsGrid(const mnavTileIndex* index, const mnavBakeDef* def, + const mnavBakeCells* cells, bool outlines, int32_t count) { return index->tileCells == def->tileCells && index->cellSize == def->cellSize && index->border == cells->border && index->outlines == outlines && diff --git a/third_party/maul-nav/src/tile_index.h b/third_party/maul-nav/src/tile_index.h index ac32b6713..eedad2590 100644 --- a/third_party/maul-nav/src/tile_index.h +++ b/third_party/maul-nav/src/tile_index.h @@ -52,14 +52,14 @@ struct mnavTileIndex mnavHostCode* mnavTileIndexHost(const mnavTileIndex* index); // Whether an index was built for this grid and these meshes' counts. -bool mnavTileIndexFits(const mnavTileIndex* index, const mnavBakeDef* def, - const mnavBakeCells* cells, const mnavTriangleMesh* meshes, - int32_t meshCount); +MNAV_NODISCARD bool mnavTileIndexFits(const mnavTileIndex* index, const mnavBakeDef* def, + const mnavBakeCells* cells, const mnavTriangleMesh* meshes, + int32_t meshCount); // Whether an index was built for this grid and these outlines' counts. -bool mnavTileIndexFits2D(const mnavTileIndex* index, const mnavBakeDef* def, - const mnavBakeCells* cells, const mnavOutline* outlines, - int32_t outlineCount); +MNAV_NODISCARD bool mnavTileIndexFits2D(const mnavTileIndex* index, const mnavBakeDef* def, + const mnavBakeCells* cells, const mnavOutline* outlines, + int32_t outlineCount); // The items listed for tile (tileX, tileZ), triangles (mesh, triangle) or // outlines (outline, 0): none past the tiles the index covers. diff --git a/third_party/maul-nav/src/triangulate.c b/third_party/maul-nav/src/triangulate.c index c3cdeac97..96594fa06 100644 --- a/third_party/maul-nav/src/triangulate.c +++ b/third_party/maul-nav/src/triangulate.c @@ -35,7 +35,7 @@ static const mnavContourVertex* At(const Ring* ring, int32_t i) // Whether the segment from i to j crosses no edge of the ring other than // those at i and j; loose lets it touch. -static bool Clear(const Ring* ring, int32_t i, int32_t j, bool loose) +MNAV_NODISCARD static bool Clear(const Ring* ring, int32_t i, int32_t j, bool loose) { const mnavContourVertex* d0 = At(ring, i); const mnavContourVertex* d1 = At(ring, j); @@ -67,7 +67,7 @@ static bool Clear(const Ring* ring, int32_t i, int32_t j, bool loose) // touch the positions of i and j, as it must for the two copies of a hole // bridge's ends, and so cannot see a vertex both of whose edges run to // those positions; this test can. -static bool Empty(const Ring* ring, int32_t i, int32_t j) +MNAV_NODISCARD static bool Empty(const Ring* ring, int32_t i, int32_t j) { int32_t middle = Next(i, ring->n); const mnavContourVertex* a = At(ring, i); @@ -89,7 +89,7 @@ static bool Empty(const Ring* ring, int32_t i, int32_t j) } // Whether i to j, which skips one vertex, is a diagonal inside the ring. -static bool Diagonal(const Ring* ring, int32_t i, int32_t j, bool loose) +MNAV_NODISCARD static bool Diagonal(const Ring* ring, int32_t i, int32_t j, bool loose) { int32_t n = ring->n; bool inCone = diff --git a/third_party/maul-nav/src/volume.c b/third_party/maul-nav/src/volume.c index 6bea70266..c2c564d5d 100644 --- a/third_party/maul-nav/src/volume.c +++ b/third_party/maul-nav/src/volume.c @@ -9,6 +9,7 @@ #include "allocator.h" #include "compact.h" +#include "input.h" #include "outline.h" #include "raster.h" @@ -22,13 +23,14 @@ static mnavInputResult Refuse(mnavResult result, mnavInputElement element, int32_t index) { - return (mnavInputResult){result, element, index}; + return mnavInputElementResult(result, element, index); } mnavInputResult mnavCheckVolume(const mnavBakeDef* def, const mnavBakeVolume* volume) { bool area = volume->kind == mnav_volumeArea; - if (volume->pointCount < 3 || volume->points == nullptr || volume->kind > mnav_volumeArea || + if (volume->pointCount < 3 || volume->points == nullptr || + (uint32_t)volume->kind > mnav_volumeArea || (area && (volume->area == mnav_areaNone || volume->area >= MNAV_AREA_TYPES)) || !isfinite(volume->minY) || !isfinite(volume->maxY) || volume->minY > volume->maxY) {