Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/operations/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
25 changes: 15 additions & 10 deletions modules/navigation/src/avoidance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>(velocity.x), .y = static_cast<float>(velocity.y)};
}

} // namespace

struct Avoidance::State {
mnavAvoidance* set = nullptr;
std::size_t maximumAgents = 0;
std::vector<mnavAgent> agents;
std::vector<mnavPos2> velocities;
std::vector<mnavVec2> velocities;

~State() {
mnavDestroyAvoidance(set);
Expand Down Expand Up @@ -81,21 +86,21 @@ result::Status Avoidance::avoid(std::span<const AvoidingAgent> 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<std::int32_t>(state.agents.size()),
nullptr,
0,
seconds,
state.velocities.data()),
RAWFRAME_TRY(checked(mnavAvoidance_Avoid(state.set,
state.agents.data(),
static_cast<std::int32_t>(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};
Expand Down
59 changes: 33 additions & 26 deletions modules/navigation/src/navmesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -79,7 +80,7 @@ struct Navmesh::State {
}

[[nodiscard]] double tileSide() const noexcept {
return static_cast<double>(def.cellSize) * def.tileCells;
return static_cast<double>(def.bake.cellSize) * def.bake.tileCells;
}

/// Puts back what `staged` held before a bake that failed, so nothing
Expand All @@ -88,18 +89,19 @@ struct Navmesh::State {
for (const Place& kPlace : staged) {
const auto kFound = tiles.find(kPlace);
if (kFound != tiles.end()) {
static_cast<void>(mnavStageTile(navmesh, kFound->second.bytes.data(), kFound->second.bytes.size()));
static_cast<void>(
mnavNavmesh_StageTile(navmesh, kFound->second.bytes.data(), kFound->second.bytes.size()));
} else {
static_cast<void>(mnavStageTileRemoval(navmesh, kPlace.first, kPlace.second));
static_cast<void>(mnavNavmesh_StageTileRemoval(navmesh, kPlace.first, kPlace.second));
}
}
}

[[nodiscard]] mnavNearest nearestOf(const Point& point, double reach) const noexcept {
mnavNearest nearest{};
const auto kReach = static_cast<float>(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;
}
Expand All @@ -114,8 +116,9 @@ Navmesh::~Navmesh() = default;

result::Result<std::unique_ptr<Navmesh>> Navmesh::create(const Settings& settings) {
auto state = std::make_unique<State>();
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<float>(settings.cellSize);
def.cellHeight = static_cast<float>(settings.cellHeight);
Expand All @@ -126,18 +129,21 @@ result::Result<std::unique_ptr<Navmesh>> Navmesh::create(const Settings& setting
.maxSlopeDegrees = static_cast<float>(settings.agent.maxSlope)};
def.minRegionArea = static_cast<float>(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<result::Error>{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();
Expand All @@ -147,7 +153,7 @@ result::Result<std::unique_ptr<Navmesh>> Navmesh::create(const Settings& setting

result::Status Navmesh::bake(std::span<const Mesh> 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<std::vector<mnavVec3>> vertices;
std::vector<mnavTriangleMesh> input;
Expand Down Expand Up @@ -194,8 +200,8 @@ result::Status Navmesh::bake(std::span<const Mesh> meshes, const Area& area) {
for (auto row = static_cast<std::int32_t>(kFirstRow); row <= static_cast<std::int32_t>(kLastRow); ++row) {
mnavBakeReport report{};
const mnavBakeInput kInput{.meshes = input.data(), .meshCount = static_cast<std::int32_t>(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<result::Error>{kBaked.error()
Expand All @@ -205,16 +211,16 @@ result::Status Navmesh::bake(std::span<const Mesh> meshes, const Area& area) {
.withContext("stage", std::to_string(report.stage))};
}
staged.emplace_back(column, row);
if (report.polygons == 0) {
static_cast<void>(mnavStageTileRemoval(state.navmesh, column, row));
if (report.polygonCount == 0) {
static_cast<void>(mnavNavmesh_StageTileRemoval(state.navmesh, column, row));
continue;
}
std::size_t size = 0;
static_cast<void>(mnavCopyBakedTile(state.baker, nullptr, 0, &size));
static_cast<void>(mnavBaker_CopyTile(state.baker, nullptr, 0, &size));
std::vector<std::uint8_t> 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;
}
Expand All @@ -223,10 +229,11 @@ result::Status Navmesh::bake(std::span<const Mesh> 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<std::uint64_t>(report.polygons)});
Tile{.bytes = std::move(bytes), .polygons = static_cast<std::uint64_t>(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;
Expand Down Expand Up @@ -258,8 +265,8 @@ result::Result<Path> 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<std::size_t>(found.pointCount));
Expand Down Expand Up @@ -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<std::int32_t, 2> kAt{kPlace.first, kPlace.second};
hash = mnavHash64(hash, kAt.data(), static_cast<std::int32_t>(sizeof kAt));
hash = mnavHash64(hash, kTile.bytes.data(), static_cast<std::int32_t>(kTile.bytes.size()));
hash = mnavHash64(hash, kAt.data(), static_cast<std::int64_t>(sizeof kAt));
hash = mnavHash64(hash, kTile.bytes.data(), static_cast<std::int64_t>(kTile.bytes.size()));
}
return hash;
}
Expand Down
2 changes: 1 addition & 1 deletion modules/navigation/tests/navmesh_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
4 changes: 3 additions & 1 deletion third_party/maul-nav/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions third_party/maul-nav/cmake/CompilerFlags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 4 additions & 1 deletion third_party/maul-nav/cmake/ReadVersion.cmake
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
17 changes: 13 additions & 4 deletions third_party/maul-nav/cmake/Testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 <PP>_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 <PP>_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)
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading