From f041e2d701163b18ec821da3d2093a335fe20287 Mon Sep 17 00:00:00 2001 From: namreeb Date: Thu, 25 Jul 2024 15:10:18 -1000 Subject: [PATCH 1/5] Fixed compile warning --- pathfind/Map.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pathfind/Map.cpp b/pathfind/Map.cpp index d9e4d9f..2eff796 100644 --- a/pathfind/Map.cpp +++ b/pathfind/Map.cpp @@ -594,8 +594,8 @@ const Tile* Map::GetTile(float x, float y) const math::Convert::WorldToTile({x, y, 0.f}, tileX, tileY); else { - tileX = (m_globalWmoOriginY - y) / MeshSettings::TileSize; - tileY = (m_globalWmoOriginX - x) / MeshSettings::TileSize; + tileX = static_cast((m_globalWmoOriginY - y) / MeshSettings::TileSize); + tileY = static_cast((m_globalWmoOriginX - x) / MeshSettings::TileSize); } auto const tile = m_tiles.find({tileX, tileY}); @@ -831,7 +831,7 @@ bool Map::FindHeight(const math::Vertex& source, float x, float y, float& z) con // use the source Z as an initial guess math::Convert::VertexToRecast({x, y, source.Z}, recastTarget); - dtPolyRef hit_path[100]; + dtPolyRef hit_path[100] = {}; dtRaycastHit hit; hit.path = hit_path; hit.maxPath = sizeof(hit_path) / sizeof(hit_path[0]); From ec305d3e2fd2003b7f4492ad170b7fd7587a3123 Mon Sep 17 00:00:00 2001 From: namreeb Date: Mon, 29 Jul 2024 10:06:09 -1000 Subject: [PATCH 2/5] Added WMO rotation test to manual thorough tests. This is from issue #67. --- test/manual_tests.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/manual_tests.py b/test/manual_tests.py index 24e9d90..9c8347e 100644 --- a/test/manual_tests.py +++ b/test/manual_tests.py @@ -41,7 +41,7 @@ def test_build_data(wow_data, nav_data, jobs): bvh_file_count = mapbuild.build_bvh(wow_data, nav_data, jobs) build_time = time.time() - start - min_bvh_count = 800 + min_bvh_count = 230 if bvh_file_count < min_bvh_count: raise RuntimeError('Expected %d BVH files, found only %d' % ( min_bvh_count, bvh_file_count)) @@ -110,6 +110,11 @@ def test_use_data(nav_data): assert zone == 1497 and area == 1497 print('Undercity area check succeeded') + + # This WMO is rotated around all axis and serves as a check that the translation is working well + azeroth.load_adt_at(-1173.688, -2046.505) + z_values = azeroth.query_heights(-1173.688, -2046.505) + assert len(z_values) == 2 and approximate(z_values[0], 37.323) def main(): parser = argparse.ArgumentParser() @@ -136,23 +141,11 @@ def main(): test_use_data(args.navdata) finally: if args.build_nav_data: + print('Removing temporary directory') shutil.rmtree(args.navdata) return 0 if __name__ == '__main__': sys.exit(main()) - wow_data = os.getenv('NAM_WOW_DATA') - if wow_data is None: - wow_data = r'f:\wow 1.12.1\data' - if wow_data is not None and not os.path.isdir(wow_data): - raise RuntimeError('Data dir %s does not exist' % wow_data) - - existing_mesh_data = os.getenv('NAM_MESH_DATA') - - if existing_mesh_data is not None and not \ - os.path.isdir(existing_mesh_data): - raise RuntimeError('NAM_MESH_DATA %s does not exist' % existing_mesh_data) - - unittest.main() From 48659c316cc932a51f7fbc6e4113753cfb419e5f Mon Sep 17 00:00:00 2001 From: namreeb Date: Mon, 29 Jul 2024 10:06:48 -1000 Subject: [PATCH 3/5] Removed single z check. Instead, the Z of the final hop on a path will be corrected automatically. --- MapViewer/main.cpp | 23 ++++------- pathfind/Map.cpp | 69 +++++++++----------------------- pathfind/Map.hpp | 16 ++------ pathfind/pathfind_c_bindings.cpp | 27 ------------- pathfind/pathfind_c_bindings.hpp | 10 ----- pathfind/python.cpp | 19 --------- test/smoke_tests.py | 13 +++--- 7 files changed, 37 insertions(+), 140 deletions(-) diff --git a/MapViewer/main.cpp b/MapViewer/main.cpp index 75bc4c5..48cff96 100644 --- a/MapViewer/main.cpp +++ b/MapViewer/main.cpp @@ -299,23 +299,16 @@ LRESULT CALLBACK GuiWindowProc(HWND hWnd, UINT message, WPARAM wParam, } auto const& prev_hop = path[path.size() - 2]; - float height; - if (gNavMesh->FindHeight(prev_hop, hit.X, hit.Y, height)) - { - std::stringstream ss; - ss << "Destination: " << hit.X << ", " << hit.Y - << ", " << hit.Z - << " FindHeight returns: " << height << "\n"; + std::stringstream ss; + ss << "Destination: " << hit.X << ", " << hit.Y + << ", " << hit.Z << "\n"; - std::vector heights; - if (gNavMesh->FindHeights(hit.X, hit.Y, heights)) - for (auto const& h : heights) - ss << "Possible correct values: " << h << "\n"; + std::vector heights; + if (gNavMesh->FindHeights(hit.X, hit.Y, heights)) + for (auto const& h : heights) + ss << "Possible correct values: " << h << "\n"; - OutputDebugStringA(ss.str().c_str()); - } - else - OutputDebugStringA("No height found"); + OutputDebugStringA(ss.str().c_str()); } else { diff --git a/pathfind/Map.cpp b/pathfind/Map.cpp index 2eff796..627e6df 100644 --- a/pathfind/Map.cpp +++ b/pathfind/Map.cpp @@ -581,7 +581,25 @@ bool Map::FindPath(const math::Vertex& start, const math::Vertex& end, for (auto i = 0; i < pathLength; ++i) math::Convert::VertexToWow(&pathBuffer[i * 3], output[i]); - return true; + // for the last point, let us refine the z. we know that the value + // returned from Detour will be within MeshSettings::DetailSampleMaxError + // yards above or below. to begin with, we shift that error range upwards + // so we know that we are above the true value. + output[pathLength - 1].Z += MeshSettings::DetailSampleMaxError; + + // nudge it up by the smallest possible amount to make sure we are OVER + // the correct value and not exactly at it + output[pathLength - 1].Z = std::nextafter(output[pathLength - 1].Z, + output[pathLength - 1].Z + 1.f); + + auto const endTile = + GetTile(output[pathLength - 1].X, output[pathLength - 1].Y); + + // if for some reason we fail, fail hard! + return !!endTile && + FindNextZ(endTile, output[pathLength - 1].X, + output[pathLength - 1].Y, output[pathLength - 1].Z, true, + output[pathLength - 1].Z); } const Tile* Map::GetTile(float x, float y) const @@ -813,55 +831,6 @@ bool Map::FindRandomPointAroundCircle(const math::Vertex& centerPosition, return true; } - -bool Map::FindHeight(const math::Vertex& source, float x, float y, float& z) const -{ - // ray cast along navmesh from source to target - float recastSource[3]; - math::Convert::VertexToRecast(source, recastSource); - - constexpr float extents[] = {1.f, 1.f, 1.f}; - - dtPolyRef startRef; - if (m_navQuery.findNearestPoly(recastSource, extents, &m_queryFilter, - &startRef, nullptr) != DT_SUCCESS) - return false; - - float recastTarget[3]; - // use the source Z as an initial guess - math::Convert::VertexToRecast({x, y, source.Z}, recastTarget); - - dtPolyRef hit_path[100] = {}; - dtRaycastHit hit; - hit.path = hit_path; - hit.maxPath = sizeof(hit_path) / sizeof(hit_path[0]); - - if (m_navQuery.raycast(startRef, recastSource, recastTarget, &m_queryFilter, - 0, &hit) != DT_SUCCESS) - return false; - - if (!hit.pathCount) - return false; - - // if we reach here, it means we have a path and know the poly ref for - // the poly where the ray hit. so let's use that reference and query - // the height at the requested x,y. - if (m_navQuery.getPolyHeight(hit.path[hit.pathCount - 1], recastTarget, - &z) != DT_SUCCESS) - return false; - - auto const tile = GetTile(x, y); - - if (!tile) - return false; - - // take the imprecise z value from the mesh, and return the precise value - if (!FindNextZ(tile, x, y, z, true, z)) - return false; - - return true; -} - bool Map::FindHeights(float x, float y, std::vector& output) const { auto const tile = GetTile(x, y); diff --git a/pathfind/Map.hpp b/pathfind/Map.hpp index 1bb5951..14df453 100644 --- a/pathfind/Map.hpp +++ b/pathfind/Map.hpp @@ -139,19 +139,9 @@ class Map std::vector& output, bool allowPartial = false) const; - // for finding height(s) at a given (x, y), there are two scenarios: - // 1: we want to find exactly one z for a given path which has this (x, y) - // as a hop. in this case, there should only be one correct value, - // and it is the value that would be achieved by walking either from the - // previous hop to this one, or from this hop to the next one. - // 2: for a given (x, y), we want to find all possible z values - // - // NOTE: if your usage is outside of both of these scenarios, you are - // probably doing something wrong - bool FindHeight(const math::Vertex& source, float x, float y, - float& z) const; // scenario one - bool FindHeights(float x, float y, - std::vector& output) const; // scenario two + // WARNING: I'm not sure why you need this function, and therefore may + // remove it. If someone needs it, could you please let me know? + bool FindHeights(float x, float y, std::vector& output) const; bool ZoneAndArea(const math::Vertex& position, unsigned int& zone, unsigned int& area) const; diff --git a/pathfind/pathfind_c_bindings.cpp b/pathfind/pathfind_c_bindings.cpp index d1c7ddd..79971ff 100644 --- a/pathfind/pathfind_c_bindings.cpp +++ b/pathfind/pathfind_c_bindings.cpp @@ -278,33 +278,6 @@ PathfindResultType pathfind_find_heights(pathfind::Map* const map, } } -PathfindResultType pathfind_find_height(pathfind::Map* const map, float start_x, - float start_y, float start_z, - float stop_x, float stop_y, - float* const stop_z) -{ - try - { - math::Vertex start {start_x, start_y, start_z}; - float result; - if (map->FindHeight(start, stop_x, stop_y, result)) - { - *stop_z = result; - return static_cast(Result::SUCCESS); - } - - return static_cast(Result::UNKNOWN_HEIGHT); - } - catch (utility::exception& e) - { - return static_cast(e.ResultCode()); - } - catch (...) - { - return static_cast(Result::UNKNOWN_EXCEPTION); - } -} - PathfindResultType pathfind_line_of_sight(pathfind::Map* map, float start_x, float start_y, float start_z, float stop_x, float stop_y, float stop_z, diff --git a/pathfind/pathfind_c_bindings.hpp b/pathfind/pathfind_c_bindings.hpp index a5ca5f8..5d7279a 100644 --- a/pathfind/pathfind_c_bindings.hpp +++ b/pathfind/pathfind_c_bindings.hpp @@ -105,16 +105,6 @@ PathfindResultType pathfind_find_heights(pathfind::Map* const map, float x, floa unsigned int buffer_length, unsigned int* const amount_of_heights); -/* - Returns the `stop_z` for the path between `start_x`, `start_y`, `start_z` - and `stop_x`, `stop_y`. - This is the value that would be achieved by walking from start to stop. -*/ -PathfindResultType pathfind_find_height(pathfind::Map* const map, float start_x, - float start_y, float start_z, - float stop_x, float stop_y, - float* const stop_z); - /* Calculates whether there is line of sight between `start_x`, `start_y`, `start_z` and `stop_x`, `stop_y`, `stop_z`. diff --git a/pathfind/python.cpp b/pathfind/python.cpp index 8bb3636..e7b42d9 100644 --- a/pathfind/python.cpp +++ b/pathfind/python.cpp @@ -83,14 +83,6 @@ py::list python_query_heights(const pathfind::Map& map, float x, float y) return result; } -std::optional python_query_z(const pathfind::Map& map, float start_x, float start_y, float start_z, - float stop_x, float stop_y) -{ - float result; - if (!map.FindHeight({start_x, start_y, start_z}, stop_x, stop_y, result)) - return {}; - return result; -} bool los(const pathfind::Map& map, float start_x, float start_y, float start_z, float stop_x, float stop_y, float stop_z, bool doodads) @@ -179,17 +171,6 @@ Returns a list of points if a path was found, otherwise an empty list.)del", py::arg("x"), py::arg("y") ) - .def("query_z", - &python_query_z, - R"del(Returns the `stop_z` value for a given `start_x`, `start_y`, `start_z` and `stop_x`, `stop_y`. - -This is the value that would be achieved by walking from start to stop.)del", - py::arg("start_x"), - py::arg("start_y"), - py::arg("start_z"), - py::arg("stop_x"), - py::arg("stop_y") - ) .def("get_zone_and_area", &get_zone_and_area, "Returns the zone and area values for a specific coordinate.", diff --git a/test/smoke_tests.py b/test/smoke_tests.py index b4670a4..10fdc26 100644 --- a/test/smoke_tests.py +++ b/test/smoke_tests.py @@ -148,15 +148,16 @@ def compute_path_length(path): print("Should-pass doodad LoS check succeeded") - query_z = map_data.query_z(16232.7373, 16828.2734, 37.1330833, 16208.6, 16830.7) + path = map_data.find_path(16232.7373, 16828.2734, 37.1330833, 16208.6, 16830.7, 37) - if query_z is None: - raise Exception("Query Z failed with None") + if path is None: + raise Exception("Z correction in find_path failed") - if not approximate(query_z, 36.86227): - raise Exception("Query Z failed with {}".format(query_z)) + final_z = path[-1][2] + if not approximate(final_z, 36.86227): + raise Exception("Z correction test failed with {}".format(final_z)) - print("Query Z succeeded") + print("Z correction succeeded") map_data = pathfind.Map(temp_dir, "bladesedgearena") map_data.load_adt_at(6225, 250) From 7a9dd392be80b7ebb187f9328024d098bdac639e Mon Sep 17 00:00:00 2001 From: namreeb Date: Tue, 12 Nov 2024 14:14:23 -1000 Subject: [PATCH 4/5] Added zone and area to Z query in map viewer --- MapViewer/main.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/MapViewer/main.cpp b/MapViewer/main.cpp index 48cff96..79d855d 100644 --- a/MapViewer/main.cpp +++ b/MapViewer/main.cpp @@ -784,8 +784,18 @@ void SearchZValues() result << "Heights at (" << posX << ", " << posY << "):\n"; for (auto const h : output) { - result << h << "\n"; - gRenderer->AddSphere({posX, posY, h}, 0.75f); + const math::Vertex pos {posX, posY, h}; + unsigned int zone, area; + + result << h; + + if (gNavMesh->ZoneAndArea(pos, zone, area)) + result << " Zone: " << zone << " Area: " << area; + else + result << " ZoneAndArea failed"; + result << "\n"; + + gRenderer->AddSphere(pos, 0.75f); } MessageBoxA(nullptr, result.str().c_str(), "Z Search Results", 0); From 290cc047c10ad7844203432b94f46471b365d318 Mon Sep 17 00:00:00 2001 From: namreeb Date: Tue, 12 Nov 2024 14:16:59 -1000 Subject: [PATCH 5/5] More robust zone and area query. This fixes issue #78. --- pathfind/Map.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pathfind/Map.cpp b/pathfind/Map.cpp index 627e6df..23f2b7e 100644 --- a/pathfind/Map.cpp +++ b/pathfind/Map.cpp @@ -907,6 +907,23 @@ bool Map::ZoneAndArea(const math::Vertex& position, unsigned int& zone, area = adtArea; } + // if both queries failed, try a ray facing up instead + if (!rayResult && !adtResult) + { + math::Ray upRay { + {position.X, position.Y, position.Z}, + {position.X, position.Y, tile->m_bounds.getMaximum().Z}}; + + if (RayCast(upRay, tiles, false, &localZone, &localArea)) + { + zone = localZone; + area = localArea; + return true; + } + + // if it fails, fall through to below + } + assert(rayResult || adtResult); return rayResult || adtResult;