From f711a196a228ac3110c51f6f240d47b91af9a421 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 30 Jan 2026 17:48:05 +0000 Subject: [PATCH 01/82] A little re-arranging to begin with --- mplot/NavMesh.h | 112 ++++++++++++++++++++++++++-------------- mplot/VisualModelBase.h | 3 +- 2 files changed, 73 insertions(+), 42 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index e3ae2eb5..55097c6b 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -96,6 +97,18 @@ namespace mplot */ sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; + /*! + * For triangles[i], one_neighbours[i] should contain the indices of the triangles that are + * its one-vertex-shared-neighbours + */ + std::unordered_map> one_neighbours; + + /*! + * For triangles[i], two_neighbours[i] should contain the indices of the triangles that are + * its two-vertices-shared-neighbours. Could be sm::vec as can never be >3? + */ + std::unordered_map> two_neighbours; + /*! * Maps index in vertex to the original parent->indices index. populated by * VisualModel::make_navmesh() @@ -190,6 +203,8 @@ namespace mplot return rtn; } +#if 0 // unused functions + // Determine if ti0 is on the edge of the model (with < 3 edge neighbours), If so, place 1 // in its final element. Also mark as on edge any nighbours sharing one of its vertices uint32_t mark_if_on_edge (std::array& _ti0) @@ -305,6 +320,63 @@ namespace mplot } return rtn; } +#endif + // Find all the neighbours of triangle *vertex* index a. + // \return tuple containing triangle vertex indices and triangle normal. + std::vector, sm::vec>> + find_neighbours (const uint32_t a) const + { + std::vector, sm::vec>> rtn = {}; + for (auto tri : triangles) { + auto [ti, tn, tnc, tnd] = tri; + if (ti[0] == a || ti[1] == a || ti[2] == a) { rtn.push_back({ti, tn}); } + } + return rtn; + } + + // Find all the one-neighbours of 'of_this' + std::vector, sm::vec>> + find_one_neighbours (const std::array& of_this) const + { + std::vector, sm::vec>> rtn = {}; + auto a = of_this[0]; + auto b = of_this[1]; + auto c = of_this[2]; + for (auto tri : triangles) { + auto [ti, tn, tnc, tnd] = tri; + if ((ti[0] == a && ti[1] != b && ti[1] != c && ti[2] != b && ti[2] != c) + || (ti[1] == a && ti[2] != b && ti[2] != c && ti[0] != b && ti[0] != c) + || (ti[2] == a && ti[0] != b && ti[0] != c && ti[1] != b && ti[1] != c) + || + (ti[0] == b && ti[1] != c && ti[1] != a && ti[2] != c && ti[2] != a) + || (ti[1] == b && ti[2] != c && ti[2] != a && ti[0] != c && ti[0] != a) + || (ti[2] == b && ti[0] != c && ti[0] != a && ti[1] != c && ti[1] != a) + || + (ti[0] == c && ti[1] != a && ti[1] != b && ti[2] != a && ti[2] != b) + || (ti[1] == c && ti[2] != a && ti[2] != b && ti[0] != a && ti[0] != b) + || (ti[2] == c && ti[0] != a && ti[0] != b && ti[1] != a && ti[1] != b)) { + + rtn.push_back ({ti, tn}); + } + } + return rtn; + } + + /* + * Populate containers of neighbour relations between the triangles. That's + * this->one_neighbours and this->two_neighbours. Can I do this in a non-n^2 way? + * The key is the half-edge data structure. + * See: https://jerryyin.info/geometry-processing-algorithms/half-edge/ + */ + void compute_neighbour_relations() + { +#if 0 + for (auto tri : triangles) { + auto [ti, tn, tnc, tnd] = tri; + find_one_neighbours() // but returning index. This loops through all triangles. + } +#endif + } std::tuple, sm::vec> first_triangle_containing (uint32_t _idx) const @@ -443,46 +515,6 @@ namespace mplot return {other, other_n}; } - // Find all the one-neighbours of 'of_this' - std::vector, sm::vec>> - find_one_neighbours (const std::array& of_this) const - { - std::vector, sm::vec>> rtn = {}; - auto a = of_this[0]; - auto b = of_this[1]; - auto c = of_this[2]; - for (auto tri : triangles) { - auto [ti, tn, tnc, tnd] = tri; - if ((ti[0] == a && ti[1] != b && ti[1] != c && ti[2] != b && ti[2] != c) - || (ti[1] == a && ti[2] != b && ti[2] != c && ti[0] != b && ti[0] != c) - || (ti[2] == a && ti[0] != b && ti[0] != c && ti[1] != b && ti[1] != c) - || - (ti[0] == b && ti[1] != c && ti[1] != a && ti[2] != c && ti[2] != a) - || (ti[1] == b && ti[2] != c && ti[2] != a && ti[0] != c && ti[0] != a) - || (ti[2] == b && ti[0] != c && ti[0] != a && ti[1] != c && ti[1] != a) - || - (ti[0] == c && ti[1] != a && ti[1] != b && ti[2] != a && ti[2] != b) - || (ti[1] == c && ti[2] != a && ti[2] != b && ti[0] != a && ti[0] != b) - || (ti[2] == c && ti[0] != a && ti[0] != b && ti[1] != a && ti[1] != b)) { - - rtn.push_back ({ti, tn}); - } - } - return rtn; - } - - // Find all the neighbours of triangle vertex index a - std::vector, sm::vec>> - find_neighbours (const uint32_t a) const - { - std::vector, sm::vec>> rtn = {}; - for (auto tri : triangles) { - auto [ti, tn, tnc, tnd] = tri; - if (ti[0] == a || ti[1] == a || ti[2] == a) { rtn.push_back({ti, tn}); } - } - return rtn; - } - sm::vec find_vertex_normal (const uint32_t ti) const { auto neighbs = this->find_neighbours (ti); diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 88cda242..252a0ad5 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -381,8 +381,7 @@ namespace mplot } if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles" << std::endl; } - //navmesh->mark_edge_triangles(); - //if constexpr (debug_mn) { std::cout << "make_navmesh: Marked edge triangles and done." << std::endl; } + navmesh->compute_neighbour_relations(); } /** From 241e27830e90337bdca1e7c9d9aaa56bc45fdb03 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sat, 31 Jan 2026 18:58:11 +0000 Subject: [PATCH 02/82] half edge structs. Thinking about what to replace in existing code --- mplot/NavMesh.h | 41 +++++++++++++++++++++++++++++++++++++++-- mplot/VisualModelBase.h | 2 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 55097c6b..0617778a 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -71,6 +71,34 @@ namespace mplot std::vector> tris; }; + /* + * Half-edge data structures for ordered meshes + */ + template requires std::is_integral_v + struct he_edge + { + static constexpr I max = std::numeric_limits::max(); + sm::vec i = {max, max}; // two he_vertex indices for start and end of this halfedge + I twin = max; // Index of twin half edge + I next = max; // Index of next half edge in face (or hole) + I prev = max; // Index of prev half edge in face (or hole) + }; + + template requires std::is_integral_v + struct he_vertex + { + static constexpr I max = std::numeric_limits::max(); + sm::vec p = {}; // Coordinate position of vertex + I he = max; // A halfedge emanating from this he_vertex + }; + + template requires std::is_integral_v + struct he_face + { + static constexpr I max = std::numeric_limits::max(); + sm::vec i = {}; // Indices of vertices of a simplex in the mesh in ccw order (dunno what ccw would be for N>3 though) + }; + /*! * Navigation mesh of triangles. * @@ -83,19 +111,27 @@ namespace mplot * VisualModel::make_navmesh() */ std::vector> vertex; + // to become: std::vector> + + // The vector of half edges in the mesh + std::vector> halfedges; /*! * The edges that make up the same triangles as are shown with the parent VisualModel's * indices & vertexPositions, but in terms of this->vertex. Each edge must be two indices * in *ascending numerical order*. populated by VisualModel::make_navmesh() */ - std::set> edges; + std::set> edges; // This is edges, not half edges /*! * Triangles too. Might be more useful than edges. Triangle given as indices into * this->vertex. populated by VisualModel::make_navmesh() + * + * Tuple contains: triangle vertices (+flags), triangle normal, triangle 'x' edge vector, triangle 'y' edge vector. */ sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; + // To become: + //sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; /*! * For triangles[i], one_neighbours[i] should contain the indices of the triangles that are @@ -373,7 +409,8 @@ namespace mplot #if 0 for (auto tri : triangles) { auto [ti, tn, tnc, tnd] = tri; - find_one_neighbours() // but returning index. This loops through all triangles. + //find_one_neighbours() // but returning index. This loops through all triangles. + } #endif } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 252a0ad5..2f996db0 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -377,7 +377,7 @@ namespace mplot n = -n; // Also reverse n } - navmesh->triangles.push_back ({t, n, nx, ny}); // n is computed normal + navmesh->triangles.push_back ({t, n, nx, ny}); // n is computed normal, nx, ny never used? } if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles" << std::endl; } From 93cfc4fd00cf141aa7640ee0b57ddfab90ea7516 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 1 Feb 2026 09:17:12 +0000 Subject: [PATCH 03/82] Now stored vertex in the he_vertex scheme --- mplot/NavMesh.h | 34 ++++++++++++++++++---------------- mplot/VisualModelBase.h | 8 ++++---- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 0617778a..e0d8fa9c 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -110,8 +110,8 @@ namespace mplot * Minimum set of vertices to generate a topological mesh. populated by * VisualModel::make_navmesh() */ - std::vector> vertex; - // to become: std::vector> + //std::vector> vertex; + std::vector> vertex; // The vector of half edges in the mesh std::vector> halfedges; @@ -184,7 +184,7 @@ namespace mplot // Brute force it. (But we have a mesh; can this guarantee a faster search? I don't think so) float min_d = std::numeric_limits::max(); for (uint32_t j = 0; j < this->vertex.size(); ++j) { - sm::vec vcoord = (viewmatrix * this->vertex[j]).less_one_dim(); + sm::vec vcoord = (viewmatrix * this->vertex[j].p).less_one_dim(); float d = (scene_coord - vcoord).length(); if (d < min_d) { min_d = d; @@ -198,9 +198,9 @@ namespace mplot sm::vec, 3> triangle_vertices (const std::array& tri_indices) const { sm::vec, 3> trivert; - if (tri_indices[0] < this->vertex.size()) { trivert[0] = this->vertex[tri_indices[0]]; } - if (tri_indices[1] < this->vertex.size()) { trivert[1] = this->vertex[tri_indices[1]]; } - if (tri_indices[2] < this->vertex.size()) { trivert[2] = this->vertex[tri_indices[2]]; } + if (tri_indices[0] < this->vertex.size()) { trivert[0] = this->vertex[tri_indices[0]].p; } + if (tri_indices[1] < this->vertex.size()) { trivert[1] = this->vertex[tri_indices[1]].p; } + if (tri_indices[2] < this->vertex.size()) { trivert[2] = this->vertex[tri_indices[2]].p; } return trivert; } @@ -208,9 +208,9 @@ namespace mplot sm::vec, 3> triangle_vertices (const std::array& tri_indices, const sm::mat& transform) const { sm::vec, 3> trivert; - if (tri_indices[0] < this->vertex.size()) { trivert[0] = (transform * this->vertex[tri_indices[0]]).less_one_dim(); } - if (tri_indices[1] < this->vertex.size()) { trivert[1] = (transform * this->vertex[tri_indices[1]]).less_one_dim(); } - if (tri_indices[2] < this->vertex.size()) { trivert[2] = (transform * this->vertex[tri_indices[2]]).less_one_dim(); } + if (tri_indices[0] < this->vertex.size()) { trivert[0] = (transform * this->vertex[tri_indices[0]].p).less_one_dim(); } + if (tri_indices[1] < this->vertex.size()) { trivert[1] = (transform * this->vertex[tri_indices[1]].p).less_one_dim(); } + if (tri_indices[2] < this->vertex.size()) { trivert[2] = (transform * this->vertex[tri_indices[2]].p).less_one_dim(); } return trivert; } @@ -457,9 +457,9 @@ namespace mplot // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml[0] != std::numeric_limits::max()) { - sm::vec v0 = this->vertex[ti_ml[0]]; - sm::vec v1 = this->vertex[ti_ml[1]]; - sm::vec v2 = this->vertex[ti_ml[2]]; + sm::vec v0 = this->vertex[ti_ml[0]].p; + sm::vec v1 = this->vertex[ti_ml[1]].p; + sm::vec v2 = this->vertex[ti_ml[2]].p; auto [isect, p] = sm::geometry::ray_tri_intersection (v0, v1, v2, vstart, vdir); if (isect) { float d = (p - vstart).sos(); @@ -479,7 +479,9 @@ namespace mplot for (auto tri : this->triangles) { auto [ti, tn, tnc, tnd] = tri; - auto [isect, p] = sm::geometry::ray_tri_intersection (this->vertex[ti[0]], this->vertex[ti[1]], this->vertex[ti[2]], vstart, vdir); + auto [isect, p] = sm::geometry::ray_tri_intersection (this->vertex[ti[0]].p, + this->vertex[ti[1]].p, + this->vertex[ti[2]].p, vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use // vdir.sos() to exclude those that are too far and the distance^2 to find the // closest one that isn't. @@ -501,11 +503,11 @@ namespace mplot sm::vec vertex_n = this->find_vertex_normal (ti); // also loops vertex_n.renormalize(); vstart = coord_mf + (vertex_n / 2.0f); - if (sm::geometry::ray_point_intersection (this->vertex[ti], vstart, -vertex_n)) { - float d = (this->vertex[ti] - vstart).sos(); + if (sm::geometry::ray_point_intersection (this->vertex[ti].p, vstart, -vertex_n)) { + float d = (this->vertex[ti].p - vstart).sos(); if (d < isect_d && d < vdir.sos()) { std::cout << "Register vertex triangle_crossing\n"; - isect_p = this->vertex[ti]; + isect_p = this->vertex[ti].p; auto [_ti, _tn] = this->first_triangle_containing (ti); isect_ti = _ti; isect_tn = _tn; diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 2f996db0..23e539a7 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -318,7 +318,7 @@ namespace mplot // as needed using equiv.first navmesh->vertex.resize (equiv.size(), {0}); i = 0; - for (auto eq : equiv) { navmesh->vertex[i++] = (*vp)[eq.first]; } // FIXME? + for (auto eq : equiv) { navmesh->vertex[i++] = { (*vp)[eq.first], std::numeric_limits::max()}; } // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. @@ -360,9 +360,9 @@ namespace mplot // Compute trinorm as well and compare with the one from the mesh - perhaps it's // different? We really want the right normal. - const sm::vec& tv0 = navmesh->vertex[t[0]]; - const sm::vec& tv1 = navmesh->vertex[t[1]]; - const sm::vec& tv2 = navmesh->vertex[t[2]]; + const sm::vec& tv0 = navmesh->vertex[t[0]].p; + const sm::vec& tv1 = navmesh->vertex[t[1]].p; + const sm::vec& tv2 = navmesh->vertex[t[2]].p; sm::vec nx = (tv1 - tv0); sm::vec ny = (tv2 - tv0); sm::vec n = nx.cross (ny); From 6daa86151a7465f175a0cdc6f773213400e9a175 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 1 Feb 2026 12:58:28 +0000 Subject: [PATCH 04/82] WIP converting to use of mesh::face<> --- examples/model_crawler.cpp | 2 +- mplot/NavMesh.h | 567 ++++++++++++++----------------------- mplot/NormalsVisual.h | 12 +- mplot/VisualModelBase.h | 24 +- 4 files changed, 235 insertions(+), 370 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 0cc8bb45..51aac4fa 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -93,7 +93,7 @@ int main (int argc, char** argv) // Find the triangle that we're initially located above with // mplot::NavMesh::find_triangle_hit. This updates internal state in NavMesh. It could be // executed automatically in compute_mesh_movement - auto[hp_scene, tn0, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); + auto[hp_scene, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); std::cout << "Find hit finds hit point " << hp_scene << std::endl; int move_counter = 0; diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index e0d8fa9c..cb647abb 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -29,13 +29,48 @@ namespace mplot { + namespace mesh + { + /* + * Half-edge data structures for ordered meshes + */ + template requires std::is_integral_v + struct halfedge + { + static constexpr I max = std::numeric_limits::max(); + sm::vec i = {max, max}; // two he_vertex indices for start and end of this halfedge + I twin = max; // Index of twin half edge + I next = max; // Index of next half edge in face (or hole) + I prev = max; // Index of prev half edge in face (or hole) + }; + + template requires std::is_integral_v + struct vertex + { + static constexpr I max = std::numeric_limits::max(); + sm::vec p = {}; // Coordinate position of vertex + I he = max; // A halfedge emanating from this he_vertex + }; + + template requires std::is_integral_v + struct face + { + static constexpr I max = std::numeric_limits::max(); + // Indices of vertices of a simplex in the mesh in ccw order (dunno what ccw would be + // for N>3 though). i[0] == max means unset. + sm::vec i = { max }; + // store normal vector in here? + sm::vec n = {}; + }; + } + // Exception that returns triangles that were near the location of the error struct NavException : public std::exception { enum class type : uint32_t { generic, no_intersection, zero_mv, mv_to_vertex, undetected_crossing, nan_mv, off_edge }; NavException (const type _type) : m_type(_type) {} - NavException (const type _type, const std::vector>& t) : m_type(_type) { this->tris = t; } + NavException (const type _type, const std::vector>& t) : m_type(_type) { this->tris = t; } using std::exception::what; const char* what() @@ -68,35 +103,7 @@ namespace mplot // Error type determines message generated type m_type = type::generic; // Triangles of interest (as indices into NavMesh::vertex) - std::vector> tris; - }; - - /* - * Half-edge data structures for ordered meshes - */ - template requires std::is_integral_v - struct he_edge - { - static constexpr I max = std::numeric_limits::max(); - sm::vec i = {max, max}; // two he_vertex indices for start and end of this halfedge - I twin = max; // Index of twin half edge - I next = max; // Index of next half edge in face (or hole) - I prev = max; // Index of prev half edge in face (or hole) - }; - - template requires std::is_integral_v - struct he_vertex - { - static constexpr I max = std::numeric_limits::max(); - sm::vec p = {}; // Coordinate position of vertex - I he = max; // A halfedge emanating from this he_vertex - }; - - template requires std::is_integral_v - struct he_face - { - static constexpr I max = std::numeric_limits::max(); - sm::vec i = {}; // Indices of vertices of a simplex in the mesh in ccw order (dunno what ccw would be for N>3 though) + std::vector> tris; }; /*! @@ -110,11 +117,10 @@ namespace mplot * Minimum set of vertices to generate a topological mesh. populated by * VisualModel::make_navmesh() */ - //std::vector> vertex; - std::vector> vertex; + std::vector> vertex; // The vector of half edges in the mesh - std::vector> halfedges; + std::vector> halfedges; /*! * The edges that make up the same triangles as are shown with the parent VisualModel's @@ -129,9 +135,8 @@ namespace mplot * * Tuple contains: triangle vertices (+flags), triangle normal, triangle 'x' edge vector, triangle 'y' edge vector. */ - sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; - // To become: - //sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; + //sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; + std::vector> triangles; /*! * For triangles[i], one_neighbours[i] should contain the indices of the triangles that are @@ -155,13 +160,13 @@ namespace mplot sm::range> bb; //! When navigating, this is the 'current triangle' that you're located over/near - std::array ti0 = {}; + mesh::face<> ti0; /*! * The normal of ti0. This is the current triangle normal (in our mesh's frame of * reference) that our agent/camera is 'next to' */ - sm::vec tn0 = {}; + //sm::vec tn0 = {}; // now in mesh::face /*! * Stabilisation flag: if true, no rotation is applied when moving over a triangle boundary @@ -195,22 +200,22 @@ namespace mplot } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex - sm::vec, 3> triangle_vertices (const std::array& tri_indices) const + sm::vec, 3> triangle_vertices (const mesh::face<>& tri_indices) const { sm::vec, 3> trivert; - if (tri_indices[0] < this->vertex.size()) { trivert[0] = this->vertex[tri_indices[0]].p; } - if (tri_indices[1] < this->vertex.size()) { trivert[1] = this->vertex[tri_indices[1]].p; } - if (tri_indices[2] < this->vertex.size()) { trivert[2] = this->vertex[tri_indices[2]].p; } + if (tri_indices.i[0] < this->vertex.size()) { trivert[0] = this->vertex[tri_indices.i[0]].p; } + if (tri_indices.i[1] < this->vertex.size()) { trivert[1] = this->vertex[tri_indices.i[1]].p; } + if (tri_indices.i[2] < this->vertex.size()) { trivert[2] = this->vertex[tri_indices.i[2]].p; } return trivert; } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex transformed by transform - sm::vec, 3> triangle_vertices (const std::array& tri_indices, const sm::mat& transform) const + sm::vec, 3> triangle_vertices (const mesh::face<>& tri_indices, const sm::mat& transform) const { sm::vec, 3> trivert; - if (tri_indices[0] < this->vertex.size()) { trivert[0] = (transform * this->vertex[tri_indices[0]].p).less_one_dim(); } - if (tri_indices[1] < this->vertex.size()) { trivert[1] = (transform * this->vertex[tri_indices[1]].p).less_one_dim(); } - if (tri_indices[2] < this->vertex.size()) { trivert[2] = (transform * this->vertex[tri_indices[2]].p).less_one_dim(); } + if (tri_indices.i[0] < this->vertex.size()) { trivert[0] = (transform * this->vertex[tri_indices.i[0]].p).less_one_dim(); } + if (tri_indices.i[1] < this->vertex.size()) { trivert[1] = (transform * this->vertex[tri_indices.i[1]].p).less_one_dim(); } + if (tri_indices.i[2] < this->vertex.size()) { trivert[2] = (transform * this->vertex[tri_indices.i[2]].p).less_one_dim(); } return trivert; } @@ -239,160 +244,40 @@ namespace mplot return rtn; } -#if 0 // unused functions - - // Determine if ti0 is on the edge of the model (with < 3 edge neighbours), If so, place 1 - // in its final element. Also mark as on edge any nighbours sharing one of its vertices - uint32_t mark_if_on_edge (std::array& _ti0) - { - constexpr bool debug_met = false; - uint32_t n2 = 0; // Neighbours sharing 2 vertices (up to 3) - - std::vector*> neighb_edge_tris; - - for (auto& t: this->triangles) { - auto [ti, tn, tnc, tnd] = t; - auto a0 = _ti0[0]; - auto b0 = _ti0[1]; - auto c0 = _ti0[2]; - auto a = ti[0]; - auto b = ti[1]; - auto c = ti[2]; - if (( a == a0 && ((b == b0 && c != c0) || (c == b0 && b != c0))) - || (b == a0 && ((a == b0 && c != c0) || (c == b0 && a != c0))) - || (c == a0 && ((a == b0 && b != c0) || (b == b0 && a != c0)))) { - ++n2; - neighb_edge_tris.push_back (&ti); - } - else if (( a == b0 && ((b == c0 && c != a0) || (c == c0 && b != a0))) - || (b == b0 && ((a == c0 && c != a0) || (c == c0 && a != a0))) - || (c == b0 && ((a == c0 && b != a0) || (b == c0 && a != a0)))) { - ++n2; - neighb_edge_tris.push_back (&ti); - } - else if (( a == c0 && ((b == a0 && c != b0) || (c == a0 && b != b0))) - || (b == c0 && ((a == a0 && c != b0) || (c == a0 && a != b0))) - || (c == c0 && ((a == a0 && b != b0) || (b == a0 && a != b0)))) { - ++n2; - neighb_edge_tris.push_back (&ti); - } - } - - if (n2 < 3) { - if constexpr (debug_met) { - std::cout << _ti0[0] << "-" << _ti0[1] << "-" << _ti0[2] << " is on the edge"; - } - _ti0[3] = 1; - for (auto& net : neighb_edge_tris) { - if constexpr (debug_met) { std::cout << " mark vtx neighbour "; } - (*net)[3] = 1; - } - if constexpr (debug_met) { std::cout << std::endl; } - - return neighb_edge_tris.size() + 1; - } // Meaning that the triangle is 'on the edge' of the model - return 0; - } - - // Go through all triangles, marking if they're an 'edge' triangle. A triangle is ALSO on - // the edge if on of its neighbours has < 3 edge neighbours. - void mark_edge_triangles() - { - constexpr bool debug_met = false; - uint32_t ec = 0; - for (auto& t: this->triangles) { - auto& [ti, tn, tnc, tnd] = t; - ec += mark_if_on_edge (ti); // ALSO loops through triangles - } - if constexpr (debug_met) { - std::cout << ec << " / " << this->triangles.size() << " triangles are on edge\n"; - } - } - - // Count 2-vertex (i.e. edge) neighbours and also 1-vertex neighbours for triangle _ti0 - std::tuple count_neighbour_triangles (const std::array& _ti0) const - { - // Count neighbour triangles - uint32_t n1 = 0; // Neighbour sharing 1 vertex (any number) - uint32_t n2 = 0; // Neighbours sharing 2 vertices (up to 3) - for (auto t: this->triangles) { - auto [ti, tn, tnc, tnd] = t; - auto a0 = _ti0[0]; - auto b0 = _ti0[1]; - auto c0 = _ti0[2]; - auto a = ti[0]; - auto b = ti[1]; - auto c = ti[2]; - - if (( a == a0 && ((b == b0 && c != c0) || (c == b0 && b != c0))) - || (b == a0 && ((a == b0 && c != c0) || (c == b0 && a != c0))) - || (c == a0 && ((a == b0 && b != c0) || (b == b0 && a != c0)))) { ++n2; } - - else if (( a == b0 && ((b == c0 && c != a0) || (c == c0 && b != a0))) - || (b == b0 && ((a == c0 && c != a0) || (c == c0 && a != a0))) - || (c == b0 && ((a == c0 && b != a0) || (b == c0 && a != a0)))) { ++n2; } - - else if (( a == c0 && ((b == a0 && c != b0) || (c == a0 && b != b0))) - || (b == c0 && ((a == a0 && c != b0) || (c == a0 && a != b0))) - || (c == c0 && ((a == a0 && b != b0) || (b == a0 && a != b0)))) { ++n2; } - - else if (( a == a0 && b != b0 && b != c0 && c != b0 && c != c0) - || (b == a0 && c != b0 && c != c0 && a != b0 && a != c0) - || (c == a0 && a != b0 && a != c0 && b != b0 && b != c0)) { ++n1; } - } - - return {n2, n1}; - } - - sm::vvec> neighbour_triangles (const uint32_t _idx) const - { - sm::vvec> rtn; - for (auto t: this->triangles) { - auto [ti, tn, tnc, tnd] = t; - // If it includes _idx, add it to rtn - if (ti[0] == _idx || ti[1] == _idx || ti[2] == _idx) { - rtn.push_back (ti); - } - } - return rtn; - } -#endif // Find all the neighbours of triangle *vertex* index a. // \return tuple containing triangle vertex indices and triangle normal. - std::vector, sm::vec>> + std::vector> find_neighbours (const uint32_t a) const { - std::vector, sm::vec>> rtn = {}; + std::vector> rtn = {}; for (auto tri : triangles) { - auto [ti, tn, tnc, tnd] = tri; - if (ti[0] == a || ti[1] == a || ti[2] == a) { rtn.push_back({ti, tn}); } + if (tri.i[0] == a || tri.i[1] == a || tri.i[2] == a) { rtn.push_back (tri); } } return rtn; } // Find all the one-neighbours of 'of_this' - std::vector, sm::vec>> - find_one_neighbours (const std::array& of_this) const + std::vector> + find_one_neighbours (const mesh::face<>& of_this) const { - std::vector, sm::vec>> rtn = {}; - auto a = of_this[0]; - auto b = of_this[1]; - auto c = of_this[2]; + std::vector> rtn = {}; + auto a = of_this.i[0]; + auto b = of_this.i[1]; + auto c = of_this.i[2]; for (auto tri : triangles) { - auto [ti, tn, tnc, tnd] = tri; - if ((ti[0] == a && ti[1] != b && ti[1] != c && ti[2] != b && ti[2] != c) - || (ti[1] == a && ti[2] != b && ti[2] != c && ti[0] != b && ti[0] != c) - || (ti[2] == a && ti[0] != b && ti[0] != c && ti[1] != b && ti[1] != c) + if ((tri.i[0] == a && tri.i[1] != b && tri.i[1] != c && tri.i[2] != b && tri.i[2] != c) + || (tri.i[1] == a && tri.i[2] != b && tri.i[2] != c && tri.i[0] != b && tri.i[0] != c) + || (tri.i[2] == a && tri.i[0] != b && tri.i[0] != c && tri.i[1] != b && tri.i[1] != c) || - (ti[0] == b && ti[1] != c && ti[1] != a && ti[2] != c && ti[2] != a) - || (ti[1] == b && ti[2] != c && ti[2] != a && ti[0] != c && ti[0] != a) - || (ti[2] == b && ti[0] != c && ti[0] != a && ti[1] != c && ti[1] != a) + (tri.i[0] == b && tri.i[1] != c && tri.i[1] != a && tri.i[2] != c && tri.i[2] != a) + || (tri.i[1] == b && tri.i[2] != c && tri.i[2] != a && tri.i[0] != c && tri.i[0] != a) + || (tri.i[2] == b && tri.i[0] != c && tri.i[0] != a && tri.i[1] != c && tri.i[1] != a) || - (ti[0] == c && ti[1] != a && ti[1] != b && ti[2] != a && ti[2] != b) - || (ti[1] == c && ti[2] != a && ti[2] != b && ti[0] != a && ti[0] != b) - || (ti[2] == c && ti[0] != a && ti[0] != b && ti[1] != a && ti[1] != b)) { + (tri.i[0] == c && tri.i[1] != a && tri.i[1] != b && tri.i[2] != a && tri.i[2] != b) + || (tri.i[1] == c && tri.i[2] != a && tri.i[2] != b && tri.i[0] != a && tri.i[0] != b) + || (tri.i[2] == c && tri.i[0] != a && tri.i[0] != b && tri.i[1] != a && tri.i[1] != b)) { - rtn.push_back ({ti, tn}); + rtn.push_back (tri); } } return rtn; @@ -406,25 +291,17 @@ namespace mplot */ void compute_neighbour_relations() { -#if 0 - for (auto tri : triangles) { - auto [ti, tn, tnc, tnd] = tri; - //find_one_neighbours() // but returning index. This loops through all triangles. - - } -#endif + // Writeme } - std::tuple, sm::vec> - first_triangle_containing (uint32_t _idx) const + mesh::face<> first_triangle_containing (uint32_t _idx) const { - for (auto t: this->triangles) { - auto [ti, tn, tnc, tnd] = t; - if (ti[0] == _idx || ti[1] == _idx || ti[2] == _idx) { - return {ti, tn}; + for (auto tri: this->triangles) { + if (tri.i[0] == _idx || tri.i[1] == _idx || tri.i[2] == _idx) { + return tri; } } - return {}; + return mesh::face<>{}; } /* @@ -436,52 +313,50 @@ namespace mplot * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the * search time. * - * \return a tuple containing crossing location, triangle identity (three indices) and triangle normal vector + * \return a tuple containing crossing location, triangle identity (three indices + triangle normal vector) */ - std::tuple, std::array, sm::vec> + std::tuple, mesh::face<>> find_triangle_crossing (const sm::vec& coord_mf, const sm::vec& vdir, - const std::array ti_ml = {std::numeric_limits::max()}) const + const mesh::face<> ti_ml = mesh::face<>{ {std::numeric_limits::max()} } ) const { - constexpr auto umax = std::numeric_limits::max(); + //constexpr auto umax = std::numeric_limits::max(); constexpr auto fmax = std::numeric_limits::max(); sm::vec vstart = coord_mf - (vdir / 2.0f); // Return objects sm::vec isect_p = { fmax, fmax, fmax }; - std::array isect_ti = { umax, umax, umax, 0 }; - sm::vec isect_tn = { fmax, fmax, fmax }; + mesh::face<> isect_ti; // initializes unset (i[0] == max) auto isect_d = std::numeric_limits::max(); // distance to intersect const auto vdsos = vdir.sos(); // Have we been passed a 'most likely triangle' to test first? If so, test it. - if (ti_ml[0] != std::numeric_limits::max()) { - sm::vec v0 = this->vertex[ti_ml[0]].p; - sm::vec v1 = this->vertex[ti_ml[1]].p; - sm::vec v2 = this->vertex[ti_ml[2]].p; + if (ti_ml.i[0] != std::numeric_limits::max()) { + sm::vec v0 = this->vertex[ti_ml.i[0]].p; + sm::vec v1 = this->vertex[ti_ml.i[1]].p; + sm::vec v2 = this->vertex[ti_ml.i[2]].p; auto [isect, p] = sm::geometry::ray_tri_intersection (v0, v1, v2, vstart, vdir); if (isect) { float d = (p - vstart).sos(); if (d < vdsos) { - sm::vec, 3> tverts = { v0, v1, v2 }; isect_p = p; isect_ti = ti_ml; - isect_tn = this->triangle_normal (tverts); // compute tn + sm::vec, 3> tverts = { v0, v1, v2 }; + isect_ti.n = this->triangle_normal (tverts); // compute tn? isect_d = d; } } } if (isect_d != std::numeric_limits::max()) { // we found it already! - return { isect_p, isect_ti, isect_tn }; + return { isect_p, isect_ti }; } for (auto tri : this->triangles) { - auto [ti, tn, tnc, tnd] = tri; - auto [isect, p] = sm::geometry::ray_tri_intersection (this->vertex[ti[0]].p, - this->vertex[ti[1]].p, - this->vertex[ti[2]].p, vstart, vdir); + auto [isect, p] = sm::geometry::ray_tri_intersection (this->vertex[tri.i[0]].p, + this->vertex[tri.i[1]].p, + this->vertex[tri.i[2]].p, vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use // vdir.sos() to exclude those that are too far and the distance^2 to find the // closest one that isn't. @@ -489,8 +364,7 @@ namespace mplot float d = (p - vstart).sos(); if (d < isect_d && d < vdsos) { isect_p = p; - isect_ti = ti; - isect_tn = tn; + isect_ti = tri; // assumes normal already computed isect_d = d; } } @@ -508,21 +382,21 @@ namespace mplot if (d < isect_d && d < vdir.sos()) { std::cout << "Register vertex triangle_crossing\n"; isect_p = this->vertex[ti].p; - auto [_ti, _tn] = this->first_triangle_containing (ti); + auto _ti = this->first_triangle_containing (ti); isect_ti = _ti; - isect_tn = _tn; + //isect_tn = _tn; isect_d = d; } } } } - return { isect_p, isect_ti, isect_tn }; + return { isect_p, isect_ti }; } // Find the location, and the triangle indices at which a ray between coord (in model frame) // and the model centroid cross - the 'penetration point'. - std::tuple, std::array, sm::vec> + std::tuple, mesh::face<>> find_triangle_crossing (const sm::vec& coord_mf) const { sm::vec vdir = this->bb.mid() - coord_mf; @@ -531,27 +405,19 @@ namespace mplot } // Find a triangle containing indices a and b that isn't 'not_this' and return, along with its normal. - std::tuple, sm::vec> - find_other_triangle_containing (const uint32_t a, const uint32_t b, const std::array& not_this) const + mesh::face<> find_other_triangle_containing (const uint32_t a, const uint32_t b, const mesh::face<>& not_this) const { - constexpr uint32_t umax = std::numeric_limits::max(); - std::array other = {umax, umax, umax, 0}; - constexpr float fmax = std::numeric_limits::max(); - sm::vec other_n = {fmax, fmax, fmax}; + mesh::face<> other; // initialized unset for (auto tri : triangles) { - auto [ti, tn, tnc, tnd] = tri; - if (ti[0] == not_this[0] && ti[1] == not_this[1] && ti[2] == not_this[2]) { - continue; - } - if ((ti[0] == a && (ti[1] == b || ti[2] == b)) - || (ti[1] == a && (ti[0] == b || ti[2] == b)) - || (ti[2] == a && (ti[0] == b || ti[1] == b))) { - other = ti; - other_n = tn; + if (tri.i[0] == not_this.i[0] && tri.i[1] == not_this.i[1] && tri.i[2] == not_this.i[2]) { continue; } + if ((tri.i[0] == a && (tri.i[1] == b || tri.i[2] == b)) + || (tri.i[1] == a && (tri.i[0] == b || tri.i[2] == b)) + || (tri.i[2] == a && (tri.i[0] == b || tri.i[1] == b))) { + other = tri; break; } } - return {other, other_n}; + return other; } sm::vec find_vertex_normal (const uint32_t ti) const @@ -559,23 +425,20 @@ namespace mplot auto neighbs = this->find_neighbours (ti); sm::vec vn = {}; if (neighbs.size() == 0) { return vn; } - for (auto nb : neighbs) { - auto [ti, tn] = nb; - vn += tn; - } + for (auto nb : neighbs) { vn += nb.n; } return (vn / neighbs.size()); } - // Find the common vertex (ignoring a/b[3]) between a and b - uint32_t common_vertex (const std::array& a, const std::array& b) + // Find the common vertex between a and b + uint32_t common_vertex (const mesh::face<>& a, const mesh::face<>& b) { uint32_t cv = std::numeric_limits::max(); - if (a[0] == b[0] || a[1] == b[0] || a[2] == b[0]) { - cv = b[0]; - } else if (a[0] == b[1] || a[1] == b[1] || a[2] == b[1]) { - cv = b[1]; - } else if (a[0] == b[2] || a[1] == b[2] || a[2] == b[2]) { - cv = b[2]; + if (a.i[0] == b.i[0] || a.i[1] == b.i[0] || a.i[2] == b.i[0]) { + cv = b.i[0]; + } else if (a.i[0] == b.i[1] || a.i[1] == b.i[1] || a.i[2] == b.i[1]) { + cv = b.i[1]; + } else if (a.i[0] == b.i[2] || a.i[1] == b.i[2] || a.i[2] == b.i[2]) { + cv = b.i[2]; } return cv; } @@ -758,15 +621,11 @@ namespace mplot * \param mv_s The start of the planned movement * * \param mv_inplane The planned movement - * - * \param t_norm The triangle normal. While this could be computed from t_verts, it has already - * been computed by the program and so I'm passing it in here. */ crossing_data compute_crossing_location (const sm::vec, 3>& t_verts, - const std::array& t_indices, + const mesh::face<>& t_indices, const sm::vec& mv_s, - const sm::vec& mv_inplane, - const sm::vec& t_norm) + const sm::vec& mv_inplane) { constexpr bool debug = false; crossing_data cd; @@ -779,9 +638,10 @@ namespace mplot sm::vec p = mv_s + mv_inplane; sm::vec edge = t1 - t0; sm::vec ptoe = p - t0; - bool inside01 = (t_norm.dot (edge.cross (ptoe)) >= 0); + sm::vec tn = this->triangle_normal (t_verts); + bool inside01 = (tn.dot (edge.cross (ptoe)) >= 0); if (!inside01) { - partial_movement pm = find_edge_crossing (t0, t1, t_norm, mv_s, mv_inplane); + partial_movement pm = find_edge_crossing (t0, t1, tn, mv_s, mv_inplane); if constexpr (debug) { if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: fec returned pm.colinear true for t0t1\n"; @@ -802,15 +662,15 @@ namespace mplot } cd.pm = pm; cd.tri_edge = edge; - cd.edge_idx_a = t_indices[0]; - cd.edge_idx_b = t_indices[1]; + cd.edge_idx_a = t_indices.i[0]; + cd.edge_idx_b = t_indices.i[1]; } } edge = t2 - t1; ptoe = p - t1; - bool inside21 = (t_norm.dot (edge.cross (ptoe)) >= 0); + bool inside21 = (tn.dot (edge.cross (ptoe)) >= 0); if (!inside21) { - partial_movement pm = find_edge_crossing (t1, t2, t_norm, mv_s, mv_inplane); + partial_movement pm = find_edge_crossing (t1, t2, tn, mv_s, mv_inplane); if constexpr (debug) { if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: fec returned pm.colinear true for t1t2\n"; @@ -831,15 +691,15 @@ namespace mplot } cd.pm = pm; cd.tri_edge = edge; - cd.edge_idx_a = t_indices[2]; - cd.edge_idx_b = t_indices[1]; + cd.edge_idx_a = t_indices.i[2]; + cd.edge_idx_b = t_indices.i[1]; } } edge = t0 - t2; ptoe = p - t2; - bool inside02 = (t_norm.dot (edge.cross (ptoe)) >= 0); + bool inside02 = (tn.dot (edge.cross (ptoe)) >= 0); if (!inside02) { - partial_movement pm = find_edge_crossing (t2, t0, t_norm, mv_s, mv_inplane); + partial_movement pm = find_edge_crossing (t2, t0, tn, mv_s, mv_inplane); if constexpr (debug) { if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: fec returned pm.colinear true for t2t0\n"; @@ -860,8 +720,8 @@ namespace mplot } cd.pm = pm; cd.tri_edge = edge; - cd.edge_idx_a = t_indices[0]; - cd.edge_idx_b = t_indices[2]; + cd.edge_idx_a = t_indices.i[0]; + cd.edge_idx_b = t_indices.i[2]; } } @@ -915,21 +775,21 @@ namespace mplot * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the * search time. * - * \return tuple containing: the hit point in scene coordinates; the triangle normal of the - * triangle we hit; and the indices of the triangle we hit. + * \return tuple containing: the hit point in scene coordinates; --the triangle normal of the + * triangle we hit;-- and the indices of the triangle we hit. */ - std::tuple, sm::vec, std::array> + //std::tuple, sm::vec, mesh::face<>> + std::tuple, mesh::face<>> find_triangle_hit (const sm::mat& model_to_scene, const sm::vec& camloc_mf, const sm::vec& vdir, - const std::array ti_ml = {std::numeric_limits::max()}) + const mesh::face<> ti_ml = {std::numeric_limits::max()}) { - this->ti0 = {}; - this->tn0 = {}; + this->ti0 = { std::numeric_limits::max() }; sm::vec hit = {}; // Want to pass 'best tri' to this - std::tie (hit, this->ti0, this->tn0) = this->find_triangle_crossing (camloc_mf, vdir, ti_ml); + std::tie (hit, this->ti0) = this->find_triangle_crossing (camloc_mf, vdir, ti_ml); - if (this->ti0[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit\n"; } + if (this->ti0.i[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit\n"; } sm::vec hp_scene = (model_to_scene * hit).less_one_dim(); @@ -938,9 +798,9 @@ namespace mplot std::cout << "found hit at " << hit << " (model); " << hp_scene << " (scene) in direction " << vdir << "\n"; // Check we'll get a hit when we compute_mesh_movement: sm::vec, 3> tv_mf = this->triangle_vertices (this->ti0); - std::cout << "tn0: " << this->tn0 << ", length " << this->tn0.length() << std::endl; - std::cout << "TEST ray_tri_intersection (hit,-tn0): " << (hit + (this->tn0 / 2.0f)) << "," << -this->tn0 << std::endl; - auto [isect, hov_mf] = sm::geometry::ray_tri_intersection (tv_mf[0], tv_mf[1], tv_mf[2], hit + (this->tn0 / 2.0f), -this->tn0); + std::cout << "ti0.n: " << this->ti0.n << ", length " << this->ti0.n.length() << std::endl; + std::cout << "TEST ray_tri_intersection (hit,-tn0): " << (hit + (this->ti0.n / 2.0f)) << "," << -this->ti0.n << std::endl; + auto [isect, hov_mf] = sm::geometry::ray_tri_intersection (tv_mf[0], tv_mf[1], tv_mf[2], hit + (this->ti0.n / 2.0f), -this->ti0.n); if (isect) { std::cout << "ray_tri_intersection confirms we would hit at " << hov_mf << "\n"; } else { @@ -949,7 +809,7 @@ namespace mplot } } - return { hp_scene, this->tn0, this->ti0 }; + return { hp_scene, this->ti0 }; } /*! @@ -970,10 +830,9 @@ namespace mplot * large, flat, one-sided landscape, we want to make vdir long. search_dist_mult is applied * to vdir. * - * \return tuple containing: the hit point in scene coordinates; the triangle normal of the - * triangle we hit; and the indices of the triangle we hit. + * \return tuple containing: the hit point in scene coordinates and the mesh::face we hit. */ - std::tuple, sm::vec, std::array> + std::tuple, mesh::face<>> find_triangle_hit (const sm::mat& camspace, const sm::mat& model_to_scene, const float search_dist_mult = 1.0f) { @@ -999,7 +858,7 @@ namespace mplot sm::mat cam_mv_y; cam_mv_y.translate (sm::vec{0, hoverheight, 0}); // The basis _x, tn0, _z, where these are vectors in the model frame that define a camera frame - sm::mat cam_to_model_rotn = sm::mat::frombasis (_x, this->tn0, _z); + sm::mat cam_to_model_rotn = sm::mat::frombasis (_x, this->ti0.n, _z); // Get the rotation from scene frame to model sm::mat m_to_sc_rotn = model_to_scene.rotation_mat44(); sm::mat hp_m; @@ -1021,7 +880,7 @@ namespace mplot const float hoverheight) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->tn0[0] == std::numeric_limits::max()) { + if (this->ti0.n[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } @@ -1031,9 +890,10 @@ namespace mplot // and then set z from this random x and the triangle norm (y). sm::vec rand_vec; rand_vec.randomize(); - sm::vec _x = rand_vec.cross (this->tn0); + sm::vec tn0 = (model_to_scene * this->ti0.n).less_one_dim(); + sm::vec _x = rand_vec.cross (tn0); _x.renormalize(); - sm::vec _z = _x.cross (this->tn0); + sm::vec _z = _x.cross (tn0); return this->position_camera (hp_scene, model_to_scene, _x, _z, hoverheight); } @@ -1046,15 +906,16 @@ namespace mplot const float hoverheight, const sm::vec& fwds) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->tn0[0] == std::numeric_limits::max()) { + if (this->ti0.n[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } // Project fwds onto the plane tn0 + sm::vec tn0 = (model_to_scene * this->ti0.n).less_one_dim(); sm::vec _z = sm::geometry::vector_plane_projection (tn0, fwds); _z.renormalize(); - sm::vec _x = -_z.cross (this->tn0); + sm::vec _x = -_z.cross (tn0); _x.renormalize(); return this->position_camera (hp_scene, model_to_scene, _x, _z, hoverheight); @@ -1094,12 +955,12 @@ namespace mplot // Convert indices to vertices for triangle ti0, converting to the scene frame sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); // Compute the triangle normal in the scene frame - this->tn0 = this->triangle_normal (tv_sf); + sm::vec tn0 = this->triangle_normal (tv_sf); if constexpr (debug_move) { std::cout << "\n# compute_mesh_movement:\n" - << "\nti0: " << this->ti0[0] << "," << this->ti0[1] << "," << this->ti0[2] - << "\nti0 (sf): " << tv_sf << "\nnormal " << this->tn0 + << "\nti0: " << this->ti0.i + << "\nti0 (sf): " << tv_sf << "\nnormal " << tn0 << "\nmovement (camframe): " << mv_camframe << "\nInitial camera location (camloc_sf): " << camloc_sf << "\n\n"; } @@ -1114,11 +975,12 @@ namespace mplot // boundaries and so requires that the start point is *within* the boundary. // if constexpr (debug_move) { - std::cout << "First ray_tri_intersection (raystart,-tn0): " << (camloc_sf + (this->tn0 / 2.0f)) << "," << -this->tn0 << std::endl; + std::cout << "First ray_tri_intersection (raystart,-tn0): " + << (camloc_sf + (tn0 / 2.0f)) << "," << -tn0 << std::endl; } bool isect = false; sm::vec hov_sf = {}; - std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (this->tn0 / 2.0f), -this->tn0); + std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (tn0 / 2.0f), -tn0); // Use the detected location, hov_sf to compute the surface location of the camera - its 'hover location' sm::mat cam_to_surface = cam_to_scene; @@ -1126,7 +988,7 @@ namespace mplot // Try double precision if (isect == false) { - std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (this->tn0 / 2.0f), -this->tn0); + std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (tn0 / 2.0f), -tn0); if constexpr (debug_move) { if (isect == false) { std::cout << "No isect at start with ti0 using float OR double internally" << std::endl; @@ -1143,10 +1005,10 @@ namespace mplot for (uint32_t i = 0u; i < 3u; i++) { // We need to use the *vertex* normal for this test - the average of all the adjacent triangle normals! - sm::vec vertex_n = this->find_vertex_normal (this->ti0[i]); + sm::vec vertex_n = this->find_vertex_normal (this->ti0.i[i]); vertex_n.renormalize(); if constexpr (debug_move) { - std::cout << "Vertex normal for triangle index " << ti0[i] << " is " << vertex_n << std::endl; + std::cout << "Vertex normal for triangle index " << ti0.i[i] << " is " << vertex_n << std::endl; } if (sm::geometry::ray_point_intersection (tv_sf[i], camloc_sf + (vertex_n / 2.0f), -vertex_n)) { @@ -1161,7 +1023,7 @@ namespace mplot } } - std::vector> trisearched; // the other triangles we search. To place in exception + std::vector> trisearched; // the other triangles we search. To place in exception if (isect == false) { if constexpr (debug_move2) { @@ -1174,24 +1036,24 @@ namespace mplot for (uint32_t i = 0u; i < 3u; i++) { uint32_t i1 = i; uint32_t i2 = (i + 1) % 3u; - auto [_ti, _tn] = this->find_other_triangle_containing (this->ti0[i1], this->ti0[i2], this->ti0); - if (_ti[0] != std::numeric_limits::max()) { + auto _ti = this->find_other_triangle_containing (this->ti0.i[i1], this->ti0.i[i2], this->ti0); + if (_ti.i[0] != std::numeric_limits::max()) { trisearched.push_back (_ti); // Test to see if start location was inside a neighbour sm::vec, 3> tv_lf = this->triangle_vertices (_ti, model_to_scene); // _tn was returned in model frame coordinates, so recompute in scene frame - _tn = this->triangle_normal (tv_lf); + auto _tn = this->triangle_normal (tv_lf); auto [is, h] = sm::geometry::ray_tri_intersection (tv_lf[0], tv_lf[1], tv_lf[2], camloc_sf + (_tn / 2.0f), -_tn); if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in " << _ti[0] << "," << _ti[1] << "," << _ti[2] << std::endl; + std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in " << _ti.i << std::endl; } if (is) { - if constexpr (debug_move) { std::cout << "CORRECT ti0 to " << _ti[0] << "," << _ti[1] << "," << _ti[2] << std::endl; } + if constexpr (debug_move) { std::cout << "CORRECT ti0 to " << _ti.i << std::endl; } // We're in this neighbour, so update ti0/tn0 and mark isect true this->ti0 = _ti; tv_sf = tv_lf; - this->tn0 = _tn; + tn0 = _tn; isect = true; // This requires a number of matrix recomputations: hov_sf = h; @@ -1208,9 +1070,9 @@ namespace mplot } // Final test to see if we're on boundary? - float closest_edge_d = sm::geometry::dist_to_tri_edge (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf - (this->tn0 * hoverheight)); + float closest_edge_d = sm::geometry::dist_to_tri_edge (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf - (tn0 * hoverheight)); if constexpr (debug_move2) { - std::cout << "Closest distance from " << (camloc_sf - (this->tn0 * hoverheight)) + std::cout << "Closest distance from " << (camloc_sf - (tn0 * hoverheight)) << " to ti0 edge: " << closest_edge_d << std::endl; } constexpr float ced_thresh = std::numeric_limits::epsilon() * 50; @@ -1225,7 +1087,7 @@ namespace mplot } else { if constexpr (debug_move2) { std::cout << "Found intersection (at start) with (2-)neighbour triangle " - << this->ti0[0] << "," << this->ti0[1] << "," << this->ti0[2] << std::endl; + << this->ti0.i[0] << "," << this->ti0.i[1] << "," << this->ti0.i[2] << std::endl; } } @@ -1239,7 +1101,7 @@ namespace mplot // Find component of movement that is in the current triangle plane (in the scene frame of reference) sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - camloc_sf; - sm::vec mv_orthog = this->tn0 * (mv_sf.dot (this->tn0) / (this->tn0.dot (this->tn0))); + sm::vec mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); sm::vec mv_inplane = mv_sf - mv_orthog; // scene frame, a relative movement if (mv_inplane.length() == 0.0f) { @@ -1250,22 +1112,21 @@ namespace mplot // New section to handle the case that we started right on a vertex if (isect == true && int_vertex != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. - auto onens = this->find_neighbours (this->ti0[int_vertex]); - for (auto onen : onens) { - auto [_ti, _tn] = onen; - sm::vec _mv_orthog = _tn * (mv_sf.dot (_tn) / (_tn.dot (_tn))); - sm::vec _mv_inplane = mv_sf - _mv_orthog; // scene frame, a relative movement + auto onens = this->find_neighbours (this->ti0.i[int_vertex]); + for (auto _ti : onens) { sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - // _tn = this->triangle_normal (tv_nb); // shouldn't need to recompute - crossing_data cd = this->compute_crossing_location (tv_nb, _ti, hov_sf, _mv_inplane, _tn); + auto _tn = this->triangle_normal (tv_nb); // gets normal in *scene frame* + sm::vec _mv_orthog = _tn * (mv_sf.dot (_tn) / (_tn.dot (_tn))); // This tn needs to be in scene frame + sm::vec _mv_inplane = mv_sf - _mv_orthog; // scene frame, a relative movement + crossing_data cd = this->compute_crossing_location (tv_nb, _ti, hov_sf, _mv_inplane); if (cd.pm.flags.test (pm_fl::no_cross_point) == false) { this->ti0 = _ti; - this->tn0 = _tn; + tn0 = _tn; tv_sf = tv_nb; mv_orthog = _mv_orthog; mv_inplane = _mv_inplane; if constexpr (debug_move) { - std::cout << "Break on cross point with triangle (" << _ti[0] << "," << _ti[1] << "," << _ti[2] << ")\n"; + std::cout << "Break on cross point with triangle (" << _ti.i << ")\n"; } break; } else { @@ -1273,12 +1134,12 @@ namespace mplot auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + _mv_inplane + (_tn / 2.0f), -_tn); if (is) { // then we DID land in this neighbour tri this->ti0 = _ti; - this->tn0 = _tn; + tn0 = _tn; tv_sf = tv_nb; mv_orthog = _mv_orthog; mv_inplane = _mv_inplane; if constexpr (debug_move) { - std::cout << "Break as we landed in triangle (" << _ti[0] << "," << _ti[1] << "," << _ti[2] << ")\n"; + std::cout << "Break as we landed in triangle (" << _ti.i << ")\n"; } break; } @@ -1293,16 +1154,16 @@ namespace mplot // failed. sm::vec detected_edge = {}; sm::vec detected_edgevec = {}; - std::array detected_newtri = {}; // new triangle detected as part of a vertex crossing + mesh::face<> detected_newtri = {}; // new triangle detected as part of a vertex crossing // Now loop while our path may traverse one or more triangles while (!flags.test (cmm_fl::done)) { if constexpr (debug_move) { std::cout << "\nWHILE LOOP\n" - << "ti0 = (" << this->ti0[0] << "," << this->ti0[1] << "," << this->ti0[2] << ")\n" + << "ti0 = (" << this->ti0.i << ")\n" << "mv_inplane: " << hov_sf << "," << mv_inplane << "\n" - << "tn0 = " << this->tn0 << ")\n"; + << "tn0 = " << tn0 << ")\n"; } if (mv_inplane.length() == 0) { @@ -1315,7 +1176,7 @@ namespace mplot } // Apply the edge crossing algorithm - crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane, this->tn0); + crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane); if (cd.pm.flags.test (pm_fl::no_cross_point) == false || flags.test (cmm_fl::detected_crossing) || flags.test (cmm_fl::vertex_crossing)) { // Then an edge (or vertex)crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing') @@ -1339,11 +1200,10 @@ namespace mplot // _ti, _tn are the new triangle sm::vec _tn = {}; - std::array _ti = {}; + mesh::face<> _ti = {}; if (flags.test (cmm_fl::vertex_crossing)) { if constexpr (debug_move) { - std::cout << "Setting _ti to over-the-vertex tri " - << detected_newtri[0] << "-" << detected_newtri[0] << "-" << detected_newtri[0] << std::endl; + std::cout << "Setting _ti to over-the-vertex tri " << detected_newtri.i << std::endl; } _ti = detected_newtri; } else { @@ -1351,29 +1211,29 @@ namespace mplot if constexpr (debug_move) { std::cout << "find triangle across edge: find_other_triangle_containing (" << cd.edge_idx_a << ", " << cd.edge_idx_b - << ", [" << this->ti0[0] << "," << this->ti0[1] << "," << this->ti0[2] << "])" << std::endl; + << ", [" << this->ti0.i << "])" << std::endl; } - std::tie (_ti, _tn) = this->find_other_triangle_containing (cd.edge_idx_a, cd.edge_idx_b, this->ti0); + _ti = this->find_other_triangle_containing (cd.edge_idx_a, cd.edge_idx_b, this->ti0); } - if (_ti[0] != std::numeric_limits::max()) { + if (_ti.i[0] != std::numeric_limits::max()) { // Re-orient onto the new triangle sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); _tn = this->triangle_normal (newtv_sf); if constexpr (debug_move) { - std::cout << "RE-ORIENT to _ti: " << _ti[0] << "," << _ti[1] << "," << _ti[2] + std::cout << "RE-ORIENT to _ti: " << _ti.i << "\n " << newtv_sf << "\n normal " << _tn << "\n"; } // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals - if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = this->tn0.cross (_tn); } + if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); } // Compute the reorientation due to the requested movement. float rotn_angle = 0.0f; // Rotate by the angle between the normals (if stabilised is false). I think this is constrained to be <= pi - if (stabilised == false) { rotn_angle = this->tn0.angle (_tn, cd.tri_edge); } + if (stabilised == false) { rotn_angle = tn0.angle (_tn, cd.tri_edge); } // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } sm::mat reorient_model; // reorientation transformation in sf @@ -1422,7 +1282,7 @@ namespace mplot this->ti0 = _ti; ne.tris.push_back (this->ti0); - this->tn0 = _tn; + tn0 = _tn; } else { // other triangle not found?! We probably went off the edge of our navigation model mesh @@ -1448,10 +1308,10 @@ namespace mplot // Test if it was movement-within; the simplest case if constexpr (debug_move) { std::cout << "No cross point and not colinear.\n Testing if " - << (hov_sf + mv_inplane + (this->tn0 / 2.0f)) << "," << -this->tn0 + << (hov_sf + mv_inplane + (tn0 / 2.0f)) << "," << -tn0 << " intersects tv_sf (" << tv_sf << "\n"; } - auto [single_mv, he] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], hov_sf + mv_inplane + (this->tn0 / 2.0f), -this->tn0); + auto [single_mv, he] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], hov_sf + mv_inplane + (tn0 / 2.0f), -tn0); flags.set (cmm_fl::single_movement, single_mv); } @@ -1463,22 +1323,21 @@ namespace mplot } else { if constexpr (debug_move) { - std::cout << "End of movement is NOT in ti0 " << this->ti0[0] << "," << this->ti0[1] << "," << this->ti0[2] << ". Look for start neighbours\n"; + std::cout << "End of movement is NOT in ti0 " << this->ti0.i << ". Look for start neighbours\n"; } // Test 3 neighbours across the edges to find any for which the start location is also within-boundary flags.set (cmm_fl::detected_crossing, false); flags.set (cmm_fl::vertex_crossing, false); - std::array _ti_2n = { std::numeric_limits::max() }; - sm::vec_tn_2n = {}; + mesh::face<> _ti_2n = { std::numeric_limits::max() }; for (uint32_t i = 0u; i < 3u; i++) { uint32_t i1 = i; uint32_t i2 = (i + 1) % 3u; - auto [_ti, _tn] = this->find_other_triangle_containing (this->ti0[i1], this->ti0[i2], this->ti0); - if (_ti[0] != std::numeric_limits::max()) { + auto _ti = this->find_other_triangle_containing (this->ti0.i[i1], this->ti0.i[i2], this->ti0); + if (_ti.i[0] != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - _tn = this->triangle_normal (tv_nb); + auto _tn = this->triangle_normal (tv_nb); auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); @@ -1489,7 +1348,7 @@ namespace mplot auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); if constexpr (debug_move) { std::cout << "Start of move " << (is ? "IS" : "is NOT") - << " in " << _ti[0] << "," << _ti[1] << "," << _ti[2] << " / " << tv_nb << std::endl; + << " in " << _ti.i << " / " << tv_nb << std::endl; std::cout << "End of move " << (endis ? "IS" : "is NOT") << " in that triangle" << std::endl; } @@ -1501,13 +1360,13 @@ namespace mplot // End is in neighbour so this is a detected crossing if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } flags.set (cmm_fl::detected_crossing, true); - detected_edge = { this->ti0[i1], this->ti0[i2] }; + detected_edge = { this->ti0.i[i1], this->ti0.i[i2] }; detected_edgevec = tv_nb[i2] - tv_nb[i1]; break; // out of for } else { // end not in neighbour if (is) { // start is in neighbour tri (will re-orient to this and re-loop) _ti_2n = _ti; - _tn_2n = _tn; + _ti_2n.n = _tn; break; // out of for } // else end is not in neighbour, and neither is start. This // occurs if the end is ON the boundary, but precision errors @@ -1518,14 +1377,14 @@ namespace mplot } // Test one-neighbours here if necessary (that is, if the two neighbour test above failed) + sm::vec _tn = {}; if (flags.test (cmm_fl::detected_crossing) == false && - _ti_2n[0] == std::numeric_limits::max()) { + _ti_2n.i[0] == std::numeric_limits::max()) { auto onens = this->find_one_neighbours (this->ti0); - for (auto onen : onens) { + for (auto _ti : onens) { // Are we in this one? - auto [_ti, _tn] = onen; sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - _tn = this->triangle_normal (tv_nb); + _tn = this->triangle_normal (tv_nb); // scene frame auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; @@ -1535,7 +1394,7 @@ namespace mplot auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); if constexpr (debug_move) { std::cout << "Start of move " << (is ? "IS" : "is NOT") - << " in ONE-neighbour " << _ti[0] << "," << _ti[1] << "," << _ti[2] << " / " << tv_nb << std::endl; + << " in ONE-neighbour " << _ti.i << " / " << tv_nb << std::endl; std::cout << "And End of move " << (endis ? "IS" : "is NOT") << " in that ONE-neighbour " << std::endl; } @@ -1551,20 +1410,20 @@ namespace mplot } else { // end not in one-neighbour if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) _ti_2n = _ti; - _tn_2n = _tn; + //_ti_2n.n = _tn; break; // out of for } // else end is not in one-neighbour, and neither is start. } } } - if (_ti_2n[0] != std::numeric_limits::max()) { + if (_ti_2n.i[0] != std::numeric_limits::max()) { // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop this->ti0 = _ti_2n; ne.tris.push_back (this->ti0); - this->tn0 = _tn_2n; + tn0 = _tn; // recompute mv_inplane for this neighbour triangle - mv_orthog = this->tn0 * (mv_sf.dot (this->tn0) / (this->tn0.dot (this->tn0))); + mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); mv_inplane = mv_sf - mv_orthog; // sf } else if (flags.test (cmm_fl::detected_crossing)) { // We didn't find an alternative start triangle, but we did detect an edge crossing by intersection, so continue. @@ -1584,7 +1443,7 @@ namespace mplot } // triangle traversing while loop // Raise cam_to_surface up by hoverheight and then return - cam_to_surface.pretranslate (hoverheight * this->tn0); + cam_to_surface.pretranslate (hoverheight * tn0); if constexpr (debug_move) { std::cout << "looping mv_inplanes completed. Final camloc_sf: " << cam_to_surface.translation() << std::endl; } diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index dfeb1a48..ea09ea0b 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -61,10 +61,16 @@ namespace mplot sm::vec nvd = {}; sm::vec pos = {}; - for (auto t : mymodel->navmesh->triangles) { - std::tie(ti, nv, nvc, nvd) = t; + for (auto ti : mymodel->navmesh->triangles) { + + const sm::vec& v0 = mymodel->navmesh->vertex.p[ti[0]]; + const sm::vec& v1 = mymodel->navmesh->vertex.p[ti[1]]; + const sm::vec& v2 = mymodel->navmesh->vertex.p[ti[2]]; + + nv = mymodel->navmesh->triangle_normal ({v0, v1, v2}); + // Plot tn at mean location of ti - pos = (mymodel->navmesh->vertex[ti[0]] + mymodel->navmesh->vertex[ti[1]] + mymodel->navmesh->vertex[ti[2]]) / 3.0f; + pos = (v0 + v1 + v2) / 3.0f; // Mesh triangle normals this->computeArrow (pos, (pos + nv * this->scale_factor), clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 23e539a7..30bc1fe7 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -349,35 +349,35 @@ namespace mplot navmesh->edges.insert (e); // Direct population of triangles. Three indices and a 4th number to hold flags (with bit0 meaning edge-triangle) - std::array t = { navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]], 0 }; + mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]}, {} }; // The normal vector for this triangle could be obtained from the mesh normals, but // we can't trust them (though they're easy to get, as we're dealing with indices // already). However, use this to ensure that our triangle indices order is in // agreement with mesh normal as far as direction goes. - sm::vec trinorm = this->get_normal (indices[i]) + this->get_normal (indices[i+1]) + this->get_normal (indices[i+2]) ; - trinorm.renormalize(); + t.n = this->get_normal (indices[i]) + this->get_normal (indices[i+1]) + this->get_normal (indices[i+2]) ; + t.n.renormalize(); // Compute trinorm as well and compare with the one from the mesh - perhaps it's // different? We really want the right normal. - const sm::vec& tv0 = navmesh->vertex[t[0]].p; - const sm::vec& tv1 = navmesh->vertex[t[1]].p; - const sm::vec& tv2 = navmesh->vertex[t[2]].p; + const sm::vec& tv0 = navmesh->vertex[t.i[0]].p; + const sm::vec& tv1 = navmesh->vertex[t.i[1]].p; + const sm::vec& tv2 = navmesh->vertex[t.i[2]].p; sm::vec nx = (tv1 - tv0); sm::vec ny = (tv2 - tv0); sm::vec n = nx.cross (ny); n.renormalize(); // Check rotational sense of triangles? - if (n.dot (trinorm) < 0.0f) { + if (n.dot (t.n) < 0.0f) { // need to swap order in t: - uint32_t ti = t[2]; - t[2] = t[1]; - t[1] = ti; - n = -n; // Also reverse n + uint32_t ti = t.i[2]; + t.i[2] = t.i[1]; + t.i[1] = ti; + t.n = -t.n; // Also reverse n } - navmesh->triangles.push_back ({t, n, nx, ny}); // n is computed normal, nx, ny never used? + navmesh->triangles.push_back (t); } if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles" << std::endl; } From 038bad3398fe187310cf311f9be94f00b2b0f20e Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 1 Feb 2026 13:22:27 +0000 Subject: [PATCH 05/82] compute nvc, nvd --- mplot/NormalsVisual.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index ea09ea0b..b5eb0fa8 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -55,7 +55,6 @@ namespace mplot // If we also have the navmesh, then use its triangles to show face normals if (mymodel->navmesh) { - std::array ti = {}; sm::vec nv = {}; sm::vec nvc = {}; sm::vec nvd = {}; @@ -63,9 +62,12 @@ namespace mplot for (auto ti : mymodel->navmesh->triangles) { - const sm::vec& v0 = mymodel->navmesh->vertex.p[ti[0]]; - const sm::vec& v1 = mymodel->navmesh->vertex.p[ti[1]]; - const sm::vec& v2 = mymodel->navmesh->vertex.p[ti[2]]; + const sm::vec& v0 = mymodel->navmesh->vertex[ti.i[0]].p; + const sm::vec& v1 = mymodel->navmesh->vertex[ti.i[1]].p; + const sm::vec& v2 = mymodel->navmesh->vertex[ti.i[2]].p; + + nvc = v1 - v0; + nvd = v2 - v0; nv = mymodel->navmesh->triangle_normal ({v0, v1, v2}); From 5edf3cca351f74226523b41eec269049eaa2c693 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 1 Feb 2026 13:37:44 +0000 Subject: [PATCH 06/82] A couple of example programs that have to change along with updates to NavMesh --- examples/draw_triangles_intersections.cpp | 18 +++++++++--------- examples/triangle_intersect.cpp | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/draw_triangles_intersections.cpp b/examples/draw_triangles_intersections.cpp index e5e19755..59529a81 100644 --- a/examples/draw_triangles_intersections.cpp +++ b/examples/draw_triangles_intersections.cpp @@ -332,11 +332,11 @@ int main() auto start_wr = (vmi * start).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; - auto [hit, ti, tn] = tvp->navmesh->find_triangle_crossing (start_wr, dirn); - if (ti[0] == std::numeric_limits::max()) { + auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn); + if (ti.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti[0] << "," << ti[1] << "," << ti[2] << std::endl; + std::cout << "Indices: " << ti.i << std::endl; std::cout << "Contains hit " << hit << std::endl; sv = std::make_unique>(hit, 0.07, mplot::colour::springgreen2); @@ -346,11 +346,11 @@ int main() } auto start_wr_fr2 = (vmi * start_fr2).less_one_dim(); // wr to tvp - auto [hit_fr2, ti_fr2, tn_fr2] = tvp->navmesh->find_triangle_crossing (start_wr_fr2, dirn_fr2); - if (ti[0] == std::numeric_limits::max()) { + auto [hit_fr2, ti_fr2] = tvp->navmesh->find_triangle_crossing (start_wr_fr2, dirn_fr2); + if (ti_fr2.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti_fr2[0] << "," << ti_fr2[1] << "," << ti_fr2[2] << std::endl; + std::cout << "Indices: " << ti_fr2.i << std::endl; std::cout << "Contains hit " << hit_fr2 << std::endl; sv = std::make_unique>(hit_fr2, 0.07, mplot::colour::springgreen2); @@ -361,11 +361,11 @@ int main() auto start_wr_bh = (vmi * start_bh).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; - auto [hit_bh, ti_bh, tn_bh] = tvp->navmesh->find_triangle_crossing (start_wr_bh, dirn_bh); - if (ti_bh[0] == std::numeric_limits::max()) { + auto [hit_bh, ti_bh] = tvp->navmesh->find_triangle_crossing (start_wr_bh, dirn_bh); + if (ti_bh.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti_bh[0] << "," << ti_bh[1] << "," << ti_bh[2] << std::endl; + std::cout << "Indices: " << ti_bh.i << std::endl; std::cout << "Contains hit " << hit_bh << std::endl; sv = std::make_unique>(hit_bh, 0.07, mplot::colour::springgreen2); diff --git a/examples/triangle_intersect.cpp b/examples/triangle_intersect.cpp index f99165c5..fc74490b 100644 --- a/examples/triangle_intersect.cpp +++ b/examples/triangle_intersect.cpp @@ -71,11 +71,11 @@ int main (int argc, char** argv) auto start_wr = (vmi * start).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; - auto [hit, ti, tn] = tvp->navmesh->find_triangle_crossing (start_wr, dirn); - if (ti[0] == std::numeric_limits::max()) { + auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn); + if (ti.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti[0] << "," << ti[1] << "," << ti[2] << std::endl; + std::cout << "Indices: " << ti.i << std::endl; std::cout << "Contains hit " << hit << std::endl; sv = std::make_unique>(hit, start_sphr * 1.1f, mplot::colour::springgreen2); From 2d41d5301078e09843887a9a725045888a47b855 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 1 Feb 2026 16:07:39 +0000 Subject: [PATCH 07/82] Removes normal from mesh::face<>, but now programs crash (see model_crawler) --- mplot/NavMesh.h | 75 ++++++++++++++++++----------------------- mplot/VisualModelBase.h | 9 +++-- 2 files changed, 36 insertions(+), 48 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index cb647abb..8107f96a 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -60,7 +59,7 @@ namespace mplot // for N>3 though). i[0] == max means unset. sm::vec i = { max }; // store normal vector in here? - sm::vec n = {}; + //sm::vec n = {}; }; } @@ -138,18 +137,6 @@ namespace mplot //sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; std::vector> triangles; - /*! - * For triangles[i], one_neighbours[i] should contain the indices of the triangles that are - * its one-vertex-shared-neighbours - */ - std::unordered_map> one_neighbours; - - /*! - * For triangles[i], two_neighbours[i] should contain the indices of the triangles that are - * its two-vertices-shared-neighbours. Could be sm::vec as can never be >3? - */ - std::unordered_map> two_neighbours; - /*! * Maps index in vertex to the original parent->indices index. populated by * VisualModel::make_navmesh() @@ -342,8 +329,6 @@ namespace mplot if (d < vdsos) { isect_p = p; isect_ti = ti_ml; - sm::vec, 3> tverts = { v0, v1, v2 }; - isect_ti.n = this->triangle_normal (tverts); // compute tn? isect_d = d; } } @@ -369,7 +354,7 @@ namespace mplot } } } - +#if 0 if (isect_p[0] == fmax && this->vertex.size() < 10000) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. // This can be computationally expensive, hence the hacky check, above. @@ -390,7 +375,7 @@ namespace mplot } } } - +#endif return { isect_p, isect_ti }; } @@ -419,16 +404,19 @@ namespace mplot } return other; } - +#if 0 + // FIXME: This next sm::vec find_vertex_normal (const uint32_t ti) const { auto neighbs = this->find_neighbours (ti); sm::vec vn = {}; if (neighbs.size() == 0) { return vn; } - for (auto nb : neighbs) { vn += nb.n; } + for (auto nb : neighbs) { + vn += nb.n; // Fixme: will have to look up vertices and compute. And in what frame? + } return (vn / neighbs.size()); } - +#endif // Find the common vertex between a and b uint32_t common_vertex (const mesh::face<>& a, const mesh::face<>& b) { @@ -798,14 +786,14 @@ namespace mplot std::cout << "found hit at " << hit << " (model); " << hp_scene << " (scene) in direction " << vdir << "\n"; // Check we'll get a hit when we compute_mesh_movement: sm::vec, 3> tv_mf = this->triangle_vertices (this->ti0); - std::cout << "ti0.n: " << this->ti0.n << ", length " << this->ti0.n.length() << std::endl; - std::cout << "TEST ray_tri_intersection (hit,-tn0): " << (hit + (this->ti0.n / 2.0f)) << "," << -this->ti0.n << std::endl; - auto [isect, hov_mf] = sm::geometry::ray_tri_intersection (tv_mf[0], tv_mf[1], tv_mf[2], hit + (this->ti0.n / 2.0f), -this->ti0.n); + auto tn = this->triangle_normal (tv_mf); + std::cout << "tn: " << tn << ", length " << tn.length() << std::endl; + std::cout << "TEST ray_tri_intersection (hit,-tn0): " << (hit + (tn / 2.0f)) << "," << -tn << std::endl; + auto [isect, hov_mf] = sm::geometry::ray_tri_intersection (tv_mf[0], tv_mf[1], tv_mf[2], hit + (tn / 2.0f), -tn); if (isect) { std::cout << "ray_tri_intersection confirms we would hit at " << hov_mf << "\n"; } else { std::cout << "ray_tri_intersection DOES NOT get a hit\n"; - //throw std::runtime_error ("ray_tri_intersection DOES NOT get a hit!"); } } @@ -850,15 +838,16 @@ namespace mplot * _z and its 'x' axis in direction _x. */ sm::mat position_camera (const sm::vec& hp_scene, const sm::mat& model_to_scene, - const sm::vec& _x, const sm::vec& _z, + const sm::vec& _x, const sm::vec& _y, const sm::vec& _z, const float hoverheight) { // I think this positions correctly now (which is all it has to do). It ignores scaling // in model_to_scene. Can be reduced to use fewer mat<>s. sm::mat cam_mv_y; cam_mv_y.translate (sm::vec{0, hoverheight, 0}); + // The basis _x, tn0, _z, where these are vectors in the model frame that define a camera frame - sm::mat cam_to_model_rotn = sm::mat::frombasis (_x, this->ti0.n, _z); + sm::mat cam_to_model_rotn = sm::mat::frombasis (_x, _y, _z); // Get the rotation from scene frame to model sm::mat m_to_sc_rotn = model_to_scene.rotation_mat44(); sm::mat hp_m; @@ -880,7 +869,7 @@ namespace mplot const float hoverheight) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->ti0.n[0] == std::numeric_limits::max()) { + if (this->ti0.i[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } @@ -890,12 +879,13 @@ namespace mplot // and then set z from this random x and the triangle norm (y). sm::vec rand_vec; rand_vec.randomize(); - sm::vec tn0 = (model_to_scene * this->ti0.n).less_one_dim(); - sm::vec _x = rand_vec.cross (tn0); + sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); + sm::vec tn = this->triangle_normal (tv_sf); + sm::vec _x = rand_vec.cross (tn); _x.renormalize(); - sm::vec _z = _x.cross (tn0); + sm::vec _z = _x.cross (tn); - return this->position_camera (hp_scene, model_to_scene, _x, _z, hoverheight); + return this->position_camera (hp_scene, model_to_scene, _x, tn, _z, hoverheight); } /*! @@ -906,19 +896,20 @@ namespace mplot const float hoverheight, const sm::vec& fwds) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->ti0.n[0] == std::numeric_limits::max()) { + if (this->ti0.i[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } // Project fwds onto the plane tn0 - sm::vec tn0 = (model_to_scene * this->ti0.n).less_one_dim(); - sm::vec _z = sm::geometry::vector_plane_projection (tn0, fwds); + sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); + sm::vec tn = this->triangle_normal (tv_sf); + sm::vec _z = sm::geometry::vector_plane_projection (tn, fwds); _z.renormalize(); - sm::vec _x = -_z.cross (tn0); + sm::vec _x = -_z.cross (tn); _x.renormalize(); - return this->position_camera (hp_scene, model_to_scene, _x, _z, hoverheight); + return this->position_camera (hp_scene, model_to_scene, _x, tn, _z, hoverheight); } /*! @@ -997,7 +988,7 @@ namespace mplot } } } - +#if 0 // If that didn't work, try the triangle *vertices* uint32_t int_vertex = std::numeric_limits::max(); // intersection vertex if (isect == false) { @@ -1022,7 +1013,7 @@ namespace mplot } } } - +#endif std::vector> trisearched; // the other triangles we search. To place in exception if (isect == false) { @@ -1108,7 +1099,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "No movement, so return unchanged camera viewmatrix\n"; } return cam_to_scene; } - +#if 0 // New section to handle the case that we started right on a vertex if (isect == true && int_vertex != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. @@ -1146,7 +1137,7 @@ namespace mplot } } } // Now carry on with corrected mv_inplane, tn0 and ti0 - +#endif // A 'detected crossing' is one where we had to use a secondary method (comparing the // triangle containing the start and the triangle containing the end) to determine that // a triangle edge had been crossed, because the original method @@ -1366,7 +1357,6 @@ namespace mplot } else { // end not in neighbour if (is) { // start is in neighbour tri (will re-orient to this and re-loop) _ti_2n = _ti; - _ti_2n.n = _tn; break; // out of for } // else end is not in neighbour, and neither is start. This // occurs if the end is ON the boundary, but precision errors @@ -1410,7 +1400,6 @@ namespace mplot } else { // end not in one-neighbour if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) _ti_2n = _ti; - //_ti_2n.n = _tn; break; // out of for } // else end is not in one-neighbour, and neither is start. } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 30bc1fe7..dc9c2e2c 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -349,14 +349,14 @@ namespace mplot navmesh->edges.insert (e); // Direct population of triangles. Three indices and a 4th number to hold flags (with bit0 meaning edge-triangle) - mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]}, {} }; + mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]} }; // The normal vector for this triangle could be obtained from the mesh normals, but // we can't trust them (though they're easy to get, as we're dealing with indices // already). However, use this to ensure that our triangle indices order is in // agreement with mesh normal as far as direction goes. - t.n = this->get_normal (indices[i]) + this->get_normal (indices[i+1]) + this->get_normal (indices[i+2]) ; - t.n.renormalize(); + sm::vec tn = this->get_normal (indices[i]) + this->get_normal (indices[i+1]) + this->get_normal (indices[i+2]) ; + tn.renormalize(); // Compute trinorm as well and compare with the one from the mesh - perhaps it's // different? We really want the right normal. @@ -369,12 +369,11 @@ namespace mplot n.renormalize(); // Check rotational sense of triangles? - if (n.dot (t.n) < 0.0f) { + if (n.dot (tn) < 0.0f) { // need to swap order in t: uint32_t ti = t.i[2]; t.i[2] = t.i[1]; t.i[1] = ti; - t.n = -t.n; // Also reverse n } navmesh->triangles.push_back (t); From f78370aebb46ca43d41f1edaf2b7184394a11170 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 1 Feb 2026 21:38:24 +0000 Subject: [PATCH 08/82] These ifdeffed out sections are important --- examples/model_crawler.cpp | 2 +- mplot/NavMesh.h | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 51aac4fa..22907449 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -94,7 +94,7 @@ int main (int argc, char** argv) // mplot::NavMesh::find_triangle_hit. This updates internal state in NavMesh. It could be // executed automatically in compute_mesh_movement auto[hp_scene, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); - std::cout << "Find hit finds hit point " << hp_scene << std::endl; + std::cout << "Find hit finds hit point " << hp_scene << " with ti0: " << ti0.i << std::endl; int move_counter = 0; constexpr int move_max = 1000; diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 8107f96a..1a7cd62b 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -930,7 +930,7 @@ namespace mplot const sm::mat& model_to_scene, const float hoverheight) { - constexpr bool debug_move = false; + constexpr bool debug_move = true; constexpr bool debug_move2 = true; // A data-containing exception to throw @@ -988,7 +988,11 @@ namespace mplot } } } -#if 0 +#if 1 + if (isect == false) { + if constexpr (debug_move) { std::cout << "Key for model_crawler...\n"; } + } +#else // If that didn't work, try the triangle *vertices* uint32_t int_vertex = std::numeric_limits::max(); // intersection vertex if (isect == false) { From c499b341b4b7504e2decde8be31a3e22143584ed Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 09:32:31 +0000 Subject: [PATCH 09/82] Semantics for the ifdeffed out bits --- mplot/NavMesh.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 1a7cd62b..854d2de7 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -51,6 +51,7 @@ namespace mplot I he = max; // A halfedge emanating from this he_vertex }; + // mesh::face could just be sm::vec if it has no other information. template requires std::is_integral_v struct face { @@ -354,7 +355,7 @@ namespace mplot } } } -#if 0 +#ifdef VERTEX_HANDLING if (isect_p[0] == fmax && this->vertex.size() < 10000) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. // This can be computationally expensive, hence the hacky check, above. @@ -404,7 +405,7 @@ namespace mplot } return other; } -#if 0 +#ifdef VERTEX_HANDLING // FIXME: This next sm::vec find_vertex_normal (const uint32_t ti) const { @@ -988,7 +989,7 @@ namespace mplot } } } -#if 1 +#ifndef VERTEX_HANDLING if (isect == false) { if constexpr (debug_move) { std::cout << "Key for model_crawler...\n"; } } @@ -1103,7 +1104,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "No movement, so return unchanged camera viewmatrix\n"; } return cam_to_scene; } -#if 0 +#ifdef VERTEX_HANDLING // New section to handle the case that we started right on a vertex if (isect == true && int_vertex != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. From b442b32ad4b98db6fbab155ca0f436a59e0b7bfd Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 09:58:25 +0000 Subject: [PATCH 10/82] Completes the removal of normal as a member of mesh::face. --- mplot/NavMesh.h | 53 +++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 854d2de7..2d14ff87 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -51,8 +51,8 @@ namespace mplot I he = max; // A halfedge emanating from this he_vertex }; - // mesh::face could just be sm::vec if it has no other information. - template requires std::is_integral_v + // Stores all three indices of a triangle for now, but will in future have a half edge that's part of the triangle. + template requires std::is_integral_v struct face { static constexpr I max = std::numeric_limits::max(); @@ -60,7 +60,6 @@ namespace mplot // for N>3 though). i[0] == max means unset. sm::vec i = { max }; // store normal vector in here? - //sm::vec n = {}; }; } @@ -298,6 +297,8 @@ namespace mplot * model. The length of vdir is used to avoid finding the intersection at the 'back' of the * model. * + * \param model_to_scene Transform that is only passed to find_vertex_normal. May in future be unnecessary. + * * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the * search time. * @@ -305,6 +306,7 @@ namespace mplot */ std::tuple, mesh::face<>> find_triangle_crossing (const sm::vec& coord_mf, const sm::vec& vdir, + const sm::mat& model_to_scene, const mesh::face<> ti_ml = mesh::face<>{ {std::numeric_limits::max()} } ) const { //constexpr auto umax = std::numeric_limits::max(); @@ -355,12 +357,13 @@ namespace mplot } } } -#ifdef VERTEX_HANDLING + + // VERTEX_HANDLING if (isect_p[0] == fmax && this->vertex.size() < 10000) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. // This can be computationally expensive, hence the hacky check, above. for (uint32_t ti = 0; ti < this->vertex.size(); ++ti) { - sm::vec vertex_n = this->find_vertex_normal (ti); // also loops + sm::vec vertex_n = this->find_vertex_normal (ti, model_to_scene); // also loops vertex_n.renormalize(); vstart = coord_mf + (vertex_n / 2.0f); if (sm::geometry::ray_point_intersection (this->vertex[ti].p, vstart, -vertex_n)) { @@ -376,18 +379,18 @@ namespace mplot } } } -#endif + return { isect_p, isect_ti }; } // Find the location, and the triangle indices at which a ray between coord (in model frame) // and the model centroid cross - the 'penetration point'. std::tuple, mesh::face<>> - find_triangle_crossing (const sm::vec& coord_mf) const + find_triangle_crossing (const sm::vec& coord_mf, const sm::mat& model_to_scene) const { sm::vec vdir = this->bb.mid() - coord_mf; vdir.renormalize(); - return this->find_triangle_crossing (coord_mf, vdir); + return this->find_triangle_crossing (coord_mf, vdir, model_to_scene); } // Find a triangle containing indices a and b that isn't 'not_this' and return, along with its normal. @@ -405,19 +408,19 @@ namespace mplot } return other; } -#ifdef VERTEX_HANDLING - // FIXME: This next - sm::vec find_vertex_normal (const uint32_t ti) const + + // VERTEX_HANDLING + sm::vec find_vertex_normal (const uint32_t ti, const sm::mat& transform) const { auto neighbs = this->find_neighbours (ti); sm::vec vn = {}; if (neighbs.size() == 0) { return vn; } for (auto nb : neighbs) { - vn += nb.n; // Fixme: will have to look up vertices and compute. And in what frame? + vn += this->triangle_normal (this->triangle_vertices (nb, transform)); } return (vn / neighbs.size()); } -#endif + // Find the common vertex between a and b uint32_t common_vertex (const mesh::face<>& a, const mesh::face<>& b) { @@ -776,7 +779,7 @@ namespace mplot this->ti0 = { std::numeric_limits::max() }; sm::vec hit = {}; // Want to pass 'best tri' to this - std::tie (hit, this->ti0) = this->find_triangle_crossing (camloc_mf, vdir, ti_ml); + std::tie (hit, this->ti0) = this->find_triangle_crossing (camloc_mf, vdir, model_to_scene, ti_ml); if (this->ti0.i[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit\n"; } @@ -789,7 +792,7 @@ namespace mplot sm::vec, 3> tv_mf = this->triangle_vertices (this->ti0); auto tn = this->triangle_normal (tv_mf); std::cout << "tn: " << tn << ", length " << tn.length() << std::endl; - std::cout << "TEST ray_tri_intersection (hit,-tn0): " << (hit + (tn / 2.0f)) << "," << -tn << std::endl; + std::cout << "TEST ray_tri_intersection (hit,-tn): " << (hit + (tn / 2.0f)) << "," << -tn << std::endl; auto [isect, hov_mf] = sm::geometry::ray_tri_intersection (tv_mf[0], tv_mf[1], tv_mf[2], hit + (tn / 2.0f), -tn); if (isect) { std::cout << "ray_tri_intersection confirms we would hit at " << hov_mf << "\n"; @@ -847,7 +850,7 @@ namespace mplot sm::mat cam_mv_y; cam_mv_y.translate (sm::vec{0, hoverheight, 0}); - // The basis _x, tn0, _z, where these are vectors in the model frame that define a camera frame + // The basis _x, _y, _z, where these are vectors in the model frame that define a camera frame sm::mat cam_to_model_rotn = sm::mat::frombasis (_x, _y, _z); // Get the rotation from scene frame to model sm::mat m_to_sc_rotn = model_to_scene.rotation_mat44(); @@ -902,7 +905,7 @@ namespace mplot return sm::mat{}; } - // Project fwds onto the plane tn0 + // Project fwds onto the plane tn sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); sm::vec tn = this->triangle_normal (tv_sf); sm::vec _z = sm::geometry::vector_plane_projection (tn, fwds); @@ -989,11 +992,8 @@ namespace mplot } } } -#ifndef VERTEX_HANDLING - if (isect == false) { - if constexpr (debug_move) { std::cout << "Key for model_crawler...\n"; } - } -#else + + // VERTEX_HANDLING // If that didn't work, try the triangle *vertices* uint32_t int_vertex = std::numeric_limits::max(); // intersection vertex if (isect == false) { @@ -1001,7 +1001,7 @@ namespace mplot for (uint32_t i = 0u; i < 3u; i++) { // We need to use the *vertex* normal for this test - the average of all the adjacent triangle normals! - sm::vec vertex_n = this->find_vertex_normal (this->ti0.i[i]); + sm::vec vertex_n = this->find_vertex_normal (this->ti0.i[i], model_to_scene); vertex_n.renormalize(); if constexpr (debug_move) { std::cout << "Vertex normal for triangle index " << ti0.i[i] << " is " << vertex_n << std::endl; @@ -1018,7 +1018,7 @@ namespace mplot } } } -#endif + std::vector> trisearched; // the other triangles we search. To place in exception if (isect == false) { @@ -1104,7 +1104,8 @@ namespace mplot if constexpr (debug_move) { std::cout << "No movement, so return unchanged camera viewmatrix\n"; } return cam_to_scene; } -#ifdef VERTEX_HANDLING + + // VERTEX_HANDLING // New section to handle the case that we started right on a vertex if (isect == true && int_vertex != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. @@ -1142,7 +1143,7 @@ namespace mplot } } } // Now carry on with corrected mv_inplane, tn0 and ti0 -#endif + // A 'detected crossing' is one where we had to use a secondary method (comparing the // triangle containing the start and the triangle containing the end) to determine that // a triangle edge had been crossed, because the original method From 5e36d8cd624b9c707bd17310ea004de1fc18a512 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 10:23:01 +0000 Subject: [PATCH 11/82] Minor changes to ensure compilation. Need to check draw_triangles_intersections though... --- examples/draw_triangles_intersections.cpp | 6 +++--- examples/triangle_intersect.cpp | 2 +- mplot/NavMesh.h | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/draw_triangles_intersections.cpp b/examples/draw_triangles_intersections.cpp index 59529a81..75e0488b 100644 --- a/examples/draw_triangles_intersections.cpp +++ b/examples/draw_triangles_intersections.cpp @@ -332,7 +332,7 @@ int main() auto start_wr = (vmi * start).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; - auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn); + auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn, vm); if (ti.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { @@ -346,7 +346,7 @@ int main() } auto start_wr_fr2 = (vmi * start_fr2).less_one_dim(); // wr to tvp - auto [hit_fr2, ti_fr2] = tvp->navmesh->find_triangle_crossing (start_wr_fr2, dirn_fr2); + auto [hit_fr2, ti_fr2] = tvp->navmesh->find_triangle_crossing (start_wr_fr2, dirn_fr2, vm); if (ti_fr2.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { @@ -361,7 +361,7 @@ int main() auto start_wr_bh = (vmi * start_bh).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; - auto [hit_bh, ti_bh] = tvp->navmesh->find_triangle_crossing (start_wr_bh, dirn_bh); + auto [hit_bh, ti_bh] = tvp->navmesh->find_triangle_crossing (start_wr_bh, dirn_bh, vm); if (ti_bh.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { diff --git a/examples/triangle_intersect.cpp b/examples/triangle_intersect.cpp index fc74490b..1a229e61 100644 --- a/examples/triangle_intersect.cpp +++ b/examples/triangle_intersect.cpp @@ -71,7 +71,7 @@ int main (int argc, char** argv) auto start_wr = (vmi * start).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; - auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn); + auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn, vm); if (ti.i[0] == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 2d14ff87..cde32acc 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -415,9 +415,7 @@ namespace mplot auto neighbs = this->find_neighbours (ti); sm::vec vn = {}; if (neighbs.size() == 0) { return vn; } - for (auto nb : neighbs) { - vn += this->triangle_normal (this->triangle_vertices (nb, transform)); - } + for (auto nb : neighbs) { vn += this->triangle_normal (this->triangle_vertices (nb, transform)); } return (vn / neighbs.size()); } @@ -934,7 +932,7 @@ namespace mplot const sm::mat& model_to_scene, const float hoverheight) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; constexpr bool debug_move2 = true; // A data-containing exception to throw From 850d98eadfa52c722850a9f4cb2d3836b03964f1 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 14:51:33 +0000 Subject: [PATCH 12/82] WIP. Lots of changes to convert to using halfedges --- examples/model_crawler.cpp | 7 +- mplot/NavMesh.h | 311 ++++++++++++++++++++++--------------- mplot/VisualModelBase.h | 43 +++-- 3 files changed, 217 insertions(+), 144 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 22907449..f1cff16e 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -84,7 +84,7 @@ int main (int argc, char** argv) // We're going to move the coordinate arrows forwards (along its z-axis), so that it 'orbits' float move_step = 0.1f; // 0.075 <= move_step and iterations 6 to fail - sm::vec mv_ca = sm::vec::uz() * move_step; + [[maybe_unused]] sm::vec mv_ca = sm::vec::uz() * move_step; // The viewmatrices have to be passed to mplot::NavMesh::compute_mesh_movement sm::mat ca_view = cap->getViewMatrix(); @@ -94,8 +94,9 @@ int main (int argc, char** argv) // mplot::NavMesh::find_triangle_hit. This updates internal state in NavMesh. It could be // executed automatically in compute_mesh_movement auto[hp_scene, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); - std::cout << "Find hit finds hit point " << hp_scene << " with ti0: " << ti0.i << std::endl; + std::cout << "Find hit finds hit point " << hp_scene << " with ti0 vertices: " << ti0->vi << std::endl; +#if 0 int move_counter = 0; constexpr int move_max = 1000; while (!v.readyToFinish()) { @@ -129,7 +130,7 @@ int main (int argc, char** argv) // Re-render the scene v.render(); } - +#endif v.keepOpen(); return rtn; diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index cde32acc..36aa6c9a 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -36,30 +36,25 @@ namespace mplot template requires std::is_integral_v struct halfedge { - static constexpr I max = std::numeric_limits::max(); - sm::vec i = {max, max}; // two he_vertex indices for start and end of this halfedge - I twin = max; // Index of twin half edge - I next = max; // Index of next half edge in face (or hole) - I prev = max; // Index of prev half edge in face (or hole) + // two vertex indices for start and end of this halfedge + sm::vec vi = { std::numeric_limits::max(), std::numeric_limits::max() }; + const halfedge* twin = nullptr; // twin half edge + const halfedge* next = nullptr; // next half edge in face (or hole) + const halfedge* prev = nullptr; // prev half edge in face (or hole) }; template requires std::is_integral_v struct vertex { - static constexpr I max = std::numeric_limits::max(); sm::vec p = {}; // Coordinate position of vertex - I he = max; // A halfedge emanating from this he_vertex + const halfedge* he = nullptr; // A halfedge emanating from this he_vertex }; - // Stores all three indices of a triangle for now, but will in future have a half edge that's part of the triangle. - template requires std::is_integral_v + template requires std::is_integral_v struct face { - static constexpr I max = std::numeric_limits::max(); - // Indices of vertices of a simplex in the mesh in ccw order (dunno what ccw would be - // for N>3 though). i[0] == max means unset. - sm::vec i = { max }; - // store normal vector in here? + // The index of the starting halfedge + const halfedge* he = nullptr; }; } @@ -118,9 +113,6 @@ namespace mplot */ std::vector> vertex; - // The vector of half edges in the mesh - std::vector> halfedges; - /*! * The edges that make up the same triangles as are shown with the parent VisualModel's * indices & vertexPositions, but in terms of this->vertex. Each edge must be two indices @@ -128,13 +120,12 @@ namespace mplot */ std::set> edges; // This is edges, not half edges + // The vector of half edges in the mesh + std::vector> halfedges; + /*! - * Triangles too. Might be more useful than edges. Triangle given as indices into - * this->vertex. populated by VisualModel::make_navmesh() - * - * Tuple contains: triangle vertices (+flags), triangle normal, triangle 'x' edge vector, triangle 'y' edge vector. + * Triangle mesh faces. populated by VisualModel::make_navmesh() */ - //sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; std::vector> triangles; /*! @@ -147,13 +138,8 @@ namespace mplot sm::range> bb; //! When navigating, this is the 'current triangle' that you're located over/near - mesh::face<> ti0; - - /*! - * The normal of ti0. This is the current triangle normal (in our mesh's frame of - * reference) that our agent/camera is 'next to' - */ - //sm::vec tn0 = {}; // now in mesh::face + //mesh::face<> ti0; // or could be a halfedge pointer? + const mesh::halfedge<>* ti0 = nullptr; /*! * Stabilisation flag: if true, no rotation is applied when moving over a triangle boundary @@ -187,22 +173,47 @@ namespace mplot } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex - sm::vec, 3> triangle_vertices (const mesh::face<>& tri_indices) const + sm::vec, 3> triangle_vertices (const mesh::halfedge<>* tri_he) const { - sm::vec, 3> trivert; - if (tri_indices.i[0] < this->vertex.size()) { trivert[0] = this->vertex[tri_indices.i[0]].p; } - if (tri_indices.i[1] < this->vertex.size()) { trivert[1] = this->vertex[tri_indices.i[1]].p; } - if (tri_indices.i[2] < this->vertex.size()) { trivert[2] = this->vertex[tri_indices.i[2]].p; } + sm::vec, 3> trivert = {}; + if (!tri_he) { + std::cout << "tri_he is null?\n"; + return trivert; + } + + uint32_t i = 0; + const mesh::halfedge<>* he = tri_he; + if (!he) { + std::cout << "he is null?\n"; + return trivert; + } + do { + std::cout << "he: " << he << std::endl; + std::cout << "he->vi[0]: " << he->vi[0] << std::endl; + std::cout << "vertex.size(): " << this->vertex.size() << std::endl; + if (he->vi[0] < this->vertex.size()) { + std::cout << "Getting vertex[" << he->vi[0] << "] from vertex size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; + trivert[i] = this->vertex[he->vi[0]].p; + } else { + std::cout << "Not getting vertex[" << he->vi[0] << "] from vertex as it is of size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; + } + ++i; + he = he->next; + } while (he != tri_he); return trivert; } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex transformed by transform - sm::vec, 3> triangle_vertices (const mesh::face<>& tri_indices, const sm::mat& transform) const + sm::vec, 3> triangle_vertices (const mesh::halfedge<>* tri_he, const sm::mat& transform) const { sm::vec, 3> trivert; - if (tri_indices.i[0] < this->vertex.size()) { trivert[0] = (transform * this->vertex[tri_indices.i[0]].p).less_one_dim(); } - if (tri_indices.i[1] < this->vertex.size()) { trivert[1] = (transform * this->vertex[tri_indices.i[1]].p).less_one_dim(); } - if (tri_indices.i[2] < this->vertex.size()) { trivert[2] = (transform * this->vertex[tri_indices.i[2]].p).less_one_dim(); } + uint32_t i = 0; + const mesh::halfedge<>* he = tri_he; + do { + if (he->vi[0] < this->vertex.size()) { trivert[i] = (transform * this->vertex[he->vi[0]].p).less_one_dim(); } + ++i; + he = he->next; + } while (he != tri_he); return trivert; } @@ -214,6 +225,7 @@ namespace mplot return n; } +#if 0 // unused? sm::vvec neighbours (const uint32_t _idx) const { sm::vvec rtn; @@ -230,103 +242,75 @@ namespace mplot } return rtn; } - +#endif // Find all the neighbours of triangle *vertex* index a. - // \return tuple containing triangle vertex indices and triangle normal. - std::vector> - find_neighbours (const uint32_t a) const + // \return vector of halfedges indices + std::vector*> + find_neighbours (const mesh::halfedge<>* a) const { - std::vector> rtn = {}; - for (auto tri : triangles) { - if (tri.i[0] == a || tri.i[1] == a || tri.i[2] == a) { rtn.push_back (tri); } - } + const mesh::halfedge<>* he = a; + std::vector*> rtn = {}; + do { + // he emanates from the vertex, so return it. + rtn.push_back (he); + he = he->next->twin; + } while (he != a); return rtn; } - - // Find all the one-neighbours of 'of_this' - std::vector> - find_one_neighbours (const mesh::face<>& of_this) const +#if 0 + std::vector*> + find_neighbours (const uint32_t a) const { - std::vector> rtn = {}; - auto a = of_this.i[0]; - auto b = of_this.i[1]; - auto c = of_this.i[2]; - for (auto tri : triangles) { - if ((tri.i[0] == a && tri.i[1] != b && tri.i[1] != c && tri.i[2] != b && tri.i[2] != c) - || (tri.i[1] == a && tri.i[2] != b && tri.i[2] != c && tri.i[0] != b && tri.i[0] != c) - || (tri.i[2] == a && tri.i[0] != b && tri.i[0] != c && tri.i[1] != b && tri.i[1] != c) - || - (tri.i[0] == b && tri.i[1] != c && tri.i[1] != a && tri.i[2] != c && tri.i[2] != a) - || (tri.i[1] == b && tri.i[2] != c && tri.i[2] != a && tri.i[0] != c && tri.i[0] != a) - || (tri.i[2] == b && tri.i[0] != c && tri.i[0] != a && tri.i[1] != c && tri.i[1] != a) - || - (tri.i[0] == c && tri.i[1] != a && tri.i[1] != b && tri.i[2] != a && tri.i[2] != b) - || (tri.i[1] == c && tri.i[2] != a && tri.i[2] != b && tri.i[0] != a && tri.i[0] != b) - || (tri.i[2] == c && tri.i[0] != a && tri.i[0] != b && tri.i[1] != a && tri.i[1] != b)) { - - rtn.push_back (tri); - } - } - return rtn; + // a is an index into this->vertex + const mesh::halfedge<>* he = this->vertex[a].he; // The emanating half edge from this vertex + return find_neighbours (he); } - +#endif /* - * Populate containers of neighbour relations between the triangles. That's - * this->one_neighbours and this->two_neighbours. Can I do this in a non-n^2 way? + * Determine neighbour relations. That means populating a halfedge data structure. Don't + * think there's any way around the at-worst O(N^2) computation, so save results into an h5 + * file that can be loaded at startup. + * * The key is the half-edge data structure. * See: https://jerryyin.info/geometry-processing-algorithms/half-edge/ */ void compute_neighbour_relations() { - // Writeme - } - - mesh::face<> first_triangle_containing (uint32_t _idx) const - { - for (auto tri: this->triangles) { - if (tri.i[0] == _idx || tri.i[1] == _idx || tri.i[2] == _idx) { - return tri; - } - } - return mesh::face<>{}; + // Writeme. This fills in the 'twin' field of the halfedges appropriately } /* - * Find the location, and the triangle indices at which a ray starting from coord (scene - * frame) with direction vdir - the 'penetration point' intersects with this NavMesh - * model. The length of vdir is used to avoid finding the intersection at the 'back' of the - * model. + * Find the location, and the triangle indices at which a ray starting from coord with + * direction vdir - the 'penetration point' intersects with this NavMesh model. The length + * of vdir is used to avoid finding the intersection at the 'back' of the model. * * \param model_to_scene Transform that is only passed to find_vertex_normal. May in future be unnecessary. * * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the * search time. * - * \return a tuple containing crossing location, triangle identity (three indices + triangle normal vector) + * \return a tuple containing crossing location, halfedge (which is part of a triangle) */ - std::tuple, mesh::face<>> + std::tuple, const mesh::halfedge<>*> find_triangle_crossing (const sm::vec& coord_mf, const sm::vec& vdir, const sm::mat& model_to_scene, - const mesh::face<> ti_ml = mesh::face<>{ {std::numeric_limits::max()} } ) const + const mesh::halfedge<>* ti_ml = nullptr ) const { - //constexpr auto umax = std::numeric_limits::max(); - constexpr auto fmax = std::numeric_limits::max(); + constexpr float fmax = std::numeric_limits::max(); sm::vec vstart = coord_mf - (vdir / 2.0f); // Return objects sm::vec isect_p = { fmax, fmax, fmax }; - mesh::face<> isect_ti; // initializes unset (i[0] == max) + const mesh::halfedge<>* isect_ti = nullptr; - auto isect_d = std::numeric_limits::max(); // distance to intersect + float isect_d = std::numeric_limits::max(); // distance to intersect - const auto vdsos = vdir.sos(); + const float vdsos = vdir.sos(); // Have we been passed a 'most likely triangle' to test first? If so, test it. - if (ti_ml.i[0] != std::numeric_limits::max()) { - sm::vec v0 = this->vertex[ti_ml.i[0]].p; - sm::vec v1 = this->vertex[ti_ml.i[1]].p; - sm::vec v2 = this->vertex[ti_ml.i[2]].p; - auto [isect, p] = sm::geometry::ray_tri_intersection (v0, v1, v2, vstart, vdir); + if (ti_ml != nullptr) { + sm::vec, 3> v = this->triangle_vertices (ti_ml); + auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { float d = (p - vstart).sos(); if (d < vdsos) { @@ -342,9 +326,8 @@ namespace mplot } for (auto tri : this->triangles) { - auto [isect, p] = sm::geometry::ray_tri_intersection (this->vertex[tri.i[0]].p, - this->vertex[tri.i[1]].p, - this->vertex[tri.i[2]].p, vstart, vdir); + sm::vec, 3> v = this->triangle_vertices (tri.he); + auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use // vdir.sos() to exclude those that are too far and the distance^2 to find the // closest one that isn't. @@ -352,28 +335,26 @@ namespace mplot float d = (p - vstart).sos(); if (d < isect_d && d < vdsos) { isect_p = p; - isect_ti = tri; // assumes normal already computed + isect_ti = tri.he; isect_d = d; } } } // VERTEX_HANDLING - if (isect_p[0] == fmax && this->vertex.size() < 10000) { + if (isect_p[0] == fmax) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. - // This can be computationally expensive, hence the hacky check, above. - for (uint32_t ti = 0; ti < this->vertex.size(); ++ti) { - sm::vec vertex_n = this->find_vertex_normal (ti, model_to_scene); // also loops + for (uint32_t vi = 0; vi < this->vertex.size(); ++vi) { + sm::vec vertex_n = this->find_vertex_normal (this->vertex[vi].he, model_to_scene); vertex_n.renormalize(); vstart = coord_mf + (vertex_n / 2.0f); - if (sm::geometry::ray_point_intersection (this->vertex[ti].p, vstart, -vertex_n)) { - float d = (this->vertex[ti].p - vstart).sos(); + if (sm::geometry::ray_point_intersection (this->vertex[vi].p, vstart, -vertex_n)) { + float d = (this->vertex[vi].p - vstart).sos(); if (d < isect_d && d < vdir.sos()) { std::cout << "Register vertex triangle_crossing\n"; - isect_p = this->vertex[ti].p; - auto _ti = this->first_triangle_containing (ti); - isect_ti = _ti; - //isect_tn = _tn; + isect_p = this->vertex[vi].p; + // now have halfedge specifying a triangle. Could attempt to look up to face, but could just return halfedge? + isect_ti = this->vertex[vi].he; isect_d = d; } } @@ -385,7 +366,7 @@ namespace mplot // Find the location, and the triangle indices at which a ray between coord (in model frame) // and the model centroid cross - the 'penetration point'. - std::tuple, mesh::face<>> + std::tuple, const mesh::halfedge<>*> find_triangle_crossing (const sm::vec& coord_mf, const sm::mat& model_to_scene) const { sm::vec vdir = this->bb.mid() - coord_mf; @@ -393,6 +374,7 @@ namespace mplot return this->find_triangle_crossing (coord_mf, vdir, model_to_scene); } +#ifdef UNDISABLED // Find a triangle containing indices a and b that isn't 'not_this' and return, along with its normal. mesh::face<> find_other_triangle_containing (const uint32_t a, const uint32_t b, const mesh::face<>& not_this) const { @@ -408,17 +390,36 @@ namespace mplot } return other; } +#endif // VERTEX_HANDLING + // Find the normal of the vertex specified by halfedge vhe + sm::vec find_vertex_normal (const mesh::halfedge<>* vhe, const sm::mat& transform) const + { + auto neighbs = this->find_neighbours (vhe); + sm::vec vn = {}; + if (neighbs.size() == 0) { return vn; } + for (auto nb : neighbs) { + // Turn nb, a half edge index, into a triangle? + vn += this->triangle_normal (this->triangle_vertices (nb, transform)); + } + return (vn / neighbs.size()); + } +#ifdef UNDISABLED sm::vec find_vertex_normal (const uint32_t ti, const sm::mat& transform) const { auto neighbs = this->find_neighbours (ti); sm::vec vn = {}; if (neighbs.size() == 0) { return vn; } - for (auto nb : neighbs) { vn += this->triangle_normal (this->triangle_vertices (nb, transform)); } + for (auto nb : neighbs) { + // Turn nb, a half edge index, into a triangle? + vn += this->triangle_normal (this->triangle_vertices (nb, transform)); + } return (vn / neighbs.size()); } +#endif +#ifdef UNDISABLED // Find the common vertex between a and b uint32_t common_vertex (const mesh::face<>& a, const mesh::face<>& b) { @@ -432,7 +433,7 @@ namespace mplot } return cv; } - +#endif // Flags class enum class pm_fl : uint32_t { @@ -593,6 +594,7 @@ namespace mplot partial_movement pm = {}; }; +#ifdef UNDISABLED /* * Find the location at which a movement from mv_s in the direction mv_inplane crosses one of * the edges of the triangle specified by the three vertices in t_verts/t_indices. @@ -740,6 +742,7 @@ namespace mplot return cd; } +#endif /*! * Find the model location, starting from the location of a camera specified in @@ -768,18 +771,17 @@ namespace mplot * \return tuple containing: the hit point in scene coordinates; --the triangle normal of the * triangle we hit;-- and the indices of the triangle we hit. */ - //std::tuple, sm::vec, mesh::face<>> - std::tuple, mesh::face<>> + std::tuple, const mesh::halfedge<>*> find_triangle_hit (const sm::mat& model_to_scene, const sm::vec& camloc_mf, const sm::vec& vdir, - const mesh::face<> ti_ml = {std::numeric_limits::max()}) + const mesh::halfedge<>* ti_ml = nullptr) { - this->ti0 = { std::numeric_limits::max() }; + this->ti0 = nullptr; sm::vec hit = {}; // Want to pass 'best tri' to this std::tie (hit, this->ti0) = this->find_triangle_crossing (camloc_mf, vdir, model_to_scene, ti_ml); - if (this->ti0.i[0] == std::numeric_limits::max()) { std::cout << __func__ << ": No hit\n"; } + if (this->ti0 == nullptr) { std::cout << __func__ << ": No hit\n"; } sm::vec hp_scene = (model_to_scene * hit).less_one_dim(); @@ -822,7 +824,7 @@ namespace mplot * * \return tuple containing: the hit point in scene coordinates and the mesh::face we hit. */ - std::tuple, mesh::face<>> + std::tuple, const mesh::halfedge<>*> find_triangle_hit (const sm::mat& camspace, const sm::mat& model_to_scene, const float search_dist_mult = 1.0f) { @@ -871,7 +873,7 @@ namespace mplot const float hoverheight) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->ti0.i[0] == std::numeric_limits::max()) { + if (this->ti0 == nullptr) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } @@ -898,7 +900,7 @@ namespace mplot const float hoverheight, const sm::vec& fwds) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->ti0.i[0] == std::numeric_limits::max()) { + if (this->ti0 == nullptr) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } @@ -914,6 +916,7 @@ namespace mplot return this->position_camera (hp_scene, model_to_scene, _x, tn, _z, hoverheight); } +#ifdef UNDISABLED /*! * Compute a movement over this navigation mesh. * @@ -999,6 +1002,7 @@ namespace mplot for (uint32_t i = 0u; i < 3u; i++) { // We need to use the *vertex* normal for this test - the average of all the adjacent triangle normals! + // Will be for each vertex in tri: sm::vec vertex_n = this->find_vertex_normal (this->ti0.i[i], model_to_scene); vertex_n.renormalize(); if constexpr (debug_move) { @@ -1320,6 +1324,57 @@ namespace mplot if constexpr (debug_move) { std::cout << "End of movement is NOT in ti0 " << this->ti0.i << ". Look for start neighbours\n"; } +#if 1 + // Test neighbours, new scheme using halfedge data structures + // Test neighbours to find any for which the start location is also within-boundary + flags.set (cmm_fl::detected_crossing, false); + flags.set (cmm_fl::vertex_crossing, false); + + // Was container for 'two neighbours' + mesh::face<> _ti_2n = { std::numeric_limits::max() }; + + // We have this->ti0, which contains a halfedge and can use this to get all neighbours _ti being a halfedge index (uint32_t) + for (each neighbour _ti) { + // Test to see if start location was inside a neighbour + sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + auto _tn = this->triangle_normal (tv_nb); + + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); + sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); + sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; + if constexpr (debug_move) { + std::cout << "endis? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; + } + auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { + std::cout << "Start of move " << (is ? "IS" : "is NOT") + << " in " << _ti.i << " / " << tv_nb << std::endl; + std::cout << "End of move " << (endis ? "IS" : "is NOT") + << " in that triangle" << std::endl; + } + + // Here, start is in original, end may not be in original. This + // is an 'intersection detected crossing' of a triangle edge + // which wasn't picked up with compute_crossing_location + if (endis) { + // End is in neighbour so this is a detected crossing + if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } + flags.set (cmm_fl::detected_crossing, true); + detected_edge = { this->ti0.i[i1], this->ti0.i[i2] }; + detected_edgevec = tv_nb[i2] - tv_nb[i1]; + break; // out of for + } else { // end not in neighbour + if (is) { // start is in neighbour tri (will re-orient to this and re-loop) + _ti_2n = _ti; + break; // out of for + } // else end is not in neighbour, and neither is start. This + // occurs if the end is ON the boundary, but precision errors + // mean this location isn't 'in' either start or neighbour + // (according to ray_tri_intersection) + } + + } +#else // Test 3 neighbours across the edges to find any for which the start location is also within-boundary flags.set (cmm_fl::detected_crossing, false); @@ -1409,6 +1464,7 @@ namespace mplot } } } +#endif if (_ti_2n.i[0] != std::numeric_limits::max()) { // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop @@ -1443,6 +1499,7 @@ namespace mplot return cam_to_surface; } // compute_mesh_movement +#endif // UNDISABLED }; // struct NavMesh diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index dc9c2e2c..0b0e3936 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -318,13 +318,15 @@ namespace mplot // as needed using equiv.first navmesh->vertex.resize (equiv.size(), {0}); i = 0; - for (auto eq : equiv) { navmesh->vertex[i++] = { (*vp)[eq.first], std::numeric_limits::max()}; } + for (auto eq : equiv) { + navmesh->vertex[i++] = { (*vp)[eq.first], nullptr }; + } // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. for (uint32_t i = 0; i < this->indices.size(); i += 3) { // Each three entries in indices is a triangle containing 3 edges. NB: Edges must be listed in ascending order! - std::array e = { navmesh_idx[indices[i]], navmesh_idx[indices[i+1]] }; + std::array e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]] }; if (e[0] > e[1]) { uint32_t t = e[0]; e[0] = e[1]; @@ -332,7 +334,7 @@ namespace mplot } navmesh->edges.insert (e); - e = { navmesh_idx[indices[i]], navmesh_idx[indices[i+2]] }; + e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 2]] }; if (e[0] > e[1]) { uint32_t t = e[0]; e[0] = e[1]; @@ -340,7 +342,7 @@ namespace mplot } navmesh->edges.insert (e); - e = { navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]] }; + e = { navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]] }; if (e[0] > e[1]) { uint32_t t = e[0]; e[0] = e[1]; @@ -348,8 +350,20 @@ namespace mplot } navmesh->edges.insert (e); + // Add three halfedges for the triangle + uint32_t hesz = navmesh->halfedges.size(); + navmesh->halfedges.resize (hesz + 3); + const mesh::halfedge<>* he0 = &navmesh->halfedges[hesz]; + const mesh::halfedge<>* he1 = &navmesh->halfedges[hesz + 1]; + const mesh::halfedge<>* he2 = &navmesh->halfedges[hesz + 2]; + navmesh->halfedges[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, nullptr, he1, he2 }; + navmesh->halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, nullptr, he2, he0 }; + navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, nullptr, he0, he1 }; + // Direct population of triangles. Three indices and a 4th number to hold flags (with bit0 meaning edge-triangle) - mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]} }; + // mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]} }; + + mesh::face<> t = { he0 }; // Face will just be this? The first half edge. // The normal vector for this triangle could be obtained from the mesh normals, but // we can't trust them (though they're easy to get, as we're dealing with indices @@ -360,9 +374,9 @@ namespace mplot // Compute trinorm as well and compare with the one from the mesh - perhaps it's // different? We really want the right normal. - const sm::vec& tv0 = navmesh->vertex[t.i[0]].p; - const sm::vec& tv1 = navmesh->vertex[t.i[1]].p; - const sm::vec& tv2 = navmesh->vertex[t.i[2]].p; + const sm::vec& tv0 = navmesh->vertex[navmesh_idx[indices[i]]].p; + const sm::vec& tv1 = navmesh->vertex[navmesh_idx[indices[i + 1]]].p; + const sm::vec& tv2 = navmesh->vertex[navmesh_idx[indices[i + 2]]].p; sm::vec nx = (tv1 - tv0); sm::vec ny = (tv2 - tv0); sm::vec n = nx.cross (ny); @@ -370,17 +384,18 @@ namespace mplot // Check rotational sense of triangles? if (n.dot (tn) < 0.0f) { - // need to swap order in t: - uint32_t ti = t.i[2]; - t.i[2] = t.i[1]; - t.i[1] = ti; + // need to swap order in t? + throw std::runtime_error ("Need to swap triangle order\n"); + //uint32_t ti = t.i[2]; + //t.i[2] = t.i[1]; + //t.i[1] = ti; } navmesh->triangles.push_back (t); } - if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles" << std::endl; } + if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles (" << navmesh->halfedges.size() << " halfedges)" << std::endl; } - navmesh->compute_neighbour_relations(); + navmesh->compute_neighbour_relations(); // finds the halfedge twins } /** From efa9bf996051e3e557b209649dc3a02ad8402727 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 16:05:43 +0000 Subject: [PATCH 13/82] More debug WIP --- CMakeLists.txt | 2 +- mplot/NavMesh.h | 17 +++++++++-------- mplot/VisualModelBase.h | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8280c82f..c27e78b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ else() endif() endif() set(COMPREHENSIVE_WARNING_FLAGS "-Wall -Wextra -Wpedantic -pedantic-errors -Werror -Wfatal-errors -Wno-psabi") - set(CMAKE_CXX_FLAGS "-g ${COMPREHENSIVE_WARNING_FLAGS} -O3") + set(CMAKE_CXX_FLAGS "-g ${COMPREHENSIVE_WARNING_FLAGS} -O0") if(CMAKE_CXX_COMPILER_ID MATCHES GNU) # Make it possible to compile complex constexpr functions (gcc only) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fconstexpr-ops-limit=5000000000") diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 36aa6c9a..fb1dd84d 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -111,7 +111,7 @@ namespace mplot * Minimum set of vertices to generate a topological mesh. populated by * VisualModel::make_navmesh() */ - std::vector> vertex; + std::vector> vertex = {}; /*! * The edges that make up the same triangles as are shown with the parent VisualModel's @@ -121,12 +121,12 @@ namespace mplot std::set> edges; // This is edges, not half edges // The vector of half edges in the mesh - std::vector> halfedges; + std::vector> halfedges = {}; /*! * Triangle mesh faces. populated by VisualModel::make_navmesh() */ - std::vector> triangles; + std::vector> triangles = {}; /*! * Maps index in vertex to the original parent->indices index. populated by @@ -188,14 +188,13 @@ namespace mplot return trivert; } do { - std::cout << "he: " << he << std::endl; - std::cout << "he->vi[0]: " << he->vi[0] << std::endl; - std::cout << "vertex.size(): " << this->vertex.size() << std::endl; + std::cout << "he: " << he << ", he->vi: " << he->vi << std::endl; + //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; if (he->vi[0] < this->vertex.size()) { - std::cout << "Getting vertex[" << he->vi[0] << "] from vertex size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; + //std::cout << "Getting vertex[" << he->vi[0] << "] from vertex size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; trivert[i] = this->vertex[he->vi[0]].p; } else { - std::cout << "Not getting vertex[" << he->vi[0] << "] from vertex as it is of size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; + //std::cout << "Not getting vertex[" << he->vi[0] << "] from vertex as it is of size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; } ++i; he = he->next; @@ -309,6 +308,7 @@ namespace mplot // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml != nullptr) { + std::cout << "Passing ti_ml to trangle_vertices: with he->vi = " << ti_ml->vi << std::endl; sm::vec, 3> v = this->triangle_vertices (ti_ml); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { @@ -326,6 +326,7 @@ namespace mplot } for (auto tri : this->triangles) { + std::cout << "Passing tri.he " << tri.he << " to triangle_vertices(): with he->vi = " << tri.he->vi << std::endl; sm::vec, 3> v = this->triangle_vertices (tri.he); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 0b0e3936..ec7d14f6 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -324,6 +324,7 @@ namespace mplot // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. + std::cout << "At start, halfedges size is " << navmesh->halfedges.size() << std::endl; for (uint32_t i = 0; i < this->indices.size(); i += 3) { // Each three entries in indices is a triangle containing 3 edges. NB: Edges must be listed in ascending order! std::array e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]] }; @@ -353,13 +354,33 @@ namespace mplot // Add three halfedges for the triangle uint32_t hesz = navmesh->halfedges.size(); navmesh->halfedges.resize (hesz + 3); - const mesh::halfedge<>* he0 = &navmesh->halfedges[hesz]; - const mesh::halfedge<>* he1 = &navmesh->halfedges[hesz + 1]; - const mesh::halfedge<>* he2 = &navmesh->halfedges[hesz + 2]; + const mesh::halfedge<>* he0 = &(navmesh->halfedges[hesz]); + const mesh::halfedge<>* he1 = &(navmesh->halfedges[hesz + 1]); + const mesh::halfedge<>* he2 = &(navmesh->halfedges[hesz + 2]); + if (hesz < 10) { + std::cout << "setting halfedges["<halfedges[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, nullptr, he1, he2 }; navmesh->halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, nullptr, he2, he0 }; navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, nullptr, he0, he1 }; + if (hesz < 10) { + std::cout << "halfedges["<< hesz << "]: " + << navmesh->halfedges[hesz].vi + << ", twin:" << navmesh->halfedges[hesz].twin + << ", next:" << navmesh->halfedges[hesz].next + << ", prev:" << navmesh->halfedges[hesz].prev << std::endl; + } // Direct population of triangles. Three indices and a 4th number to hold flags (with bit0 meaning edge-triangle) // mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]} }; @@ -390,8 +411,13 @@ namespace mplot //t.i[2] = t.i[1]; //t.i[1] = ti; } - + if (hesz < 10) { + std::cout << "Push_back triangle " << t.he << std::endl; + } navmesh->triangles.push_back (t); + if (hesz < 10) { + std::cout << "back triangle is " << navmesh->triangles.back().he << std::endl; + } } if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles (" << navmesh->halfedges.size() << " halfedges)" << std::endl; } From a9163bb0e1c6d2312a11751b848313a61a9331e3 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 16:49:05 +0000 Subject: [PATCH 14/82] Committing, so I can go back and try non-pointer version again. --- examples/model_crawler.cpp | 1 + mplot/NavMesh.h | 8 +++++++- mplot/VisualModelBase.h | 22 ++++++++++++---------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index f1cff16e..fe638afe 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -81,6 +81,7 @@ int main (int argc, char** argv) gvp->reinitColours(); // Make the navmesh for the geodesic, this doesn't occur automatically and has to come after finalize() gvp->make_navmesh(); + std::cout << "\nmake_navmesh() returned\n\n"; // We're going to move the coordinate arrows forwards (along its z-axis), so that it 'orbits' float move_step = 0.1f; // 0.075 <= move_step and iterations 6 to fail diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index fb1dd84d..129670dd 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -326,7 +326,13 @@ namespace mplot } for (auto tri : this->triangles) { - std::cout << "Passing tri.he " << tri.he << " to triangle_vertices(): with he->vi = " << tri.he->vi << std::endl; + std::cout << "this->halfedges["<< 0 << "] " << (&this->halfedges[0]) << " contains: vi:" + << this->halfedges[0].vi + << ", twin:" << this->halfedges[0].twin + << ", next:" << this->halfedges[0].next + << ", prev:" << this->halfedges[0].prev << std::endl; + + std::cout << "CF. Passing tri.he " << tri.he << " to triangle_vertices(): with he->vi = " << tri.he->vi << std::endl; sm::vec, 3> v = this->triangle_vertices (tri.he); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index ec7d14f6..b0a964e9 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -316,7 +316,7 @@ namespace mplot // Can now populate vertex, a vector of coordinates, if required, or simply access (*vp) // as needed using equiv.first - navmesh->vertex.resize (equiv.size(), {0}); + navmesh->vertex.resize (equiv.size(), mesh::vertex<>{}); i = 0; for (auto eq : equiv) { navmesh->vertex[i++] = { (*vp)[eq.first], nullptr }; @@ -325,6 +325,7 @@ namespace mplot // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. std::cout << "At start, halfedges size is " << navmesh->halfedges.size() << std::endl; + //navmesh->halfedges.reserve (this->indices.size() / 3); for (uint32_t i = 0; i < this->indices.size(); i += 3) { // Each three entries in indices is a triangle containing 3 edges. NB: Edges must be listed in ascending order! std::array e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]] }; @@ -351,22 +352,23 @@ namespace mplot } navmesh->edges.insert (e); + constexpr uint32_t mlines = 10000; // Add three halfedges for the triangle uint32_t hesz = navmesh->halfedges.size(); - navmesh->halfedges.resize (hesz + 3); + navmesh->halfedges.resize (hesz + 3, {}); const mesh::halfedge<>* he0 = &(navmesh->halfedges[hesz]); const mesh::halfedge<>* he1 = &(navmesh->halfedges[hesz + 1]); const mesh::halfedge<>* he2 = &(navmesh->halfedges[hesz + 2]); - if (hesz < 10) { - std::cout << "setting halfedges["<halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, nullptr, he2, he0 }; navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, nullptr, he0, he1 }; - if (hesz < 10) { - std::cout << "halfedges["<< hesz << "]: " + if (hesz < mlines) { + std::cout << "halfedges["<< hesz << "] contains: vi:" << navmesh->halfedges[hesz].vi << ", twin:" << navmesh->halfedges[hesz].twin << ", next:" << navmesh->halfedges[hesz].next @@ -411,11 +413,11 @@ namespace mplot //t.i[2] = t.i[1]; //t.i[1] = ti; } - if (hesz < 10) { + if (hesz < mlines) { std::cout << "Push_back triangle " << t.he << std::endl; } navmesh->triangles.push_back (t); - if (hesz < 10) { + if (hesz < mlines) { std::cout << "back triangle is " << navmesh->triangles.back().he << std::endl; } } From 387ff858849083908de82434a33851a9e1fbf6cd Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 2 Feb 2026 17:16:57 +0000 Subject: [PATCH 15/82] Plain indices seem to be working sensibly --- examples/model_crawler.cpp | 2 +- mplot/NavMesh.h | 122 ++++++++++++++++--------------------- mplot/VisualModelBase.h | 21 ++++--- 3 files changed, 67 insertions(+), 78 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index fe638afe..646a0b69 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -95,7 +95,7 @@ int main (int argc, char** argv) // mplot::NavMesh::find_triangle_hit. This updates internal state in NavMesh. It could be // executed automatically in compute_mesh_movement auto[hp_scene, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); - std::cout << "Find hit finds hit point " << hp_scene << " with ti0 vertices: " << ti0->vi << std::endl; + std::cout << "Find hit finds hit point " << hp_scene << " with ti0 halfedge: " << ti0 << std::endl; #if 0 int move_counter = 0; diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 129670dd..db491ddf 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -38,23 +38,23 @@ namespace mplot { // two vertex indices for start and end of this halfedge sm::vec vi = { std::numeric_limits::max(), std::numeric_limits::max() }; - const halfedge* twin = nullptr; // twin half edge - const halfedge* next = nullptr; // next half edge in face (or hole) - const halfedge* prev = nullptr; // prev half edge in face (or hole) + I twin = std::numeric_limits::max(); // twin half edge + I next = std::numeric_limits::max(); // next half edge in face (or hole) + I prev = std::numeric_limits::max(); // prev half edge in face (or hole) }; template requires std::is_integral_v struct vertex { sm::vec p = {}; // Coordinate position of vertex - const halfedge* he = nullptr; // A halfedge emanating from this he_vertex + I he = std::numeric_limits::max(); // A halfedge emanating from this he_vertex }; template requires std::is_integral_v struct face { // The index of the starting halfedge - const halfedge* he = nullptr; + I he = std::numeric_limits::max(); }; } @@ -139,7 +139,8 @@ namespace mplot //! When navigating, this is the 'current triangle' that you're located over/near //mesh::face<> ti0; // or could be a halfedge pointer? - const mesh::halfedge<>* ti0 = nullptr; + //const mesh::halfedge<>* ti0 = nullptr; + uint32_t ti0 = std::numeric_limits::max(); /*! * Stabilisation flag: if true, no rotation is applied when moving over a triangle boundary @@ -173,45 +174,45 @@ namespace mplot } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex - sm::vec, 3> triangle_vertices (const mesh::halfedge<>* tri_he) const + sm::vec, 3> triangle_vertices (uint32_t tri_he) const { sm::vec, 3> trivert = {}; - if (!tri_he) { + if (tri_he == std::numeric_limits::max()) { std::cout << "tri_he is null?\n"; return trivert; } uint32_t i = 0; - const mesh::halfedge<>* he = tri_he; - if (!he) { - std::cout << "he is null?\n"; - return trivert; - } + uint32_t he = tri_he; do { - std::cout << "he: " << he << ", he->vi: " << he->vi << std::endl; //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; - if (he->vi[0] < this->vertex.size()) { - //std::cout << "Getting vertex[" << he->vi[0] << "] from vertex size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; - trivert[i] = this->vertex[he->vi[0]].p; - } else { - //std::cout << "Not getting vertex[" << he->vi[0] << "] from vertex as it is of size " << this->vertex.size() << " into trivert[" << i << "]" << std::endl; + if (this->halfedges[he].vi[0] < this->vertex.size()) { + trivert[i] = this->vertex[this->halfedges[he].vi[0]].p; } ++i; - he = he->next; + he = this->halfedges[he].next; } while (he != tri_he); return trivert; } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex transformed by transform - sm::vec, 3> triangle_vertices (const mesh::halfedge<>* tri_he, const sm::mat& transform) const + sm::vec, 3> triangle_vertices (uint32_t tri_he, const sm::mat& transform) const { - sm::vec, 3> trivert; + sm::vec, 3> trivert = {}; + if (tri_he == std::numeric_limits::max()) { + std::cout << "tri_he is null?\n"; + return trivert; + } + uint32_t i = 0; - const mesh::halfedge<>* he = tri_he; + uint32_t he = tri_he; do { - if (he->vi[0] < this->vertex.size()) { trivert[i] = (transform * this->vertex[he->vi[0]].p).less_one_dim(); } + //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; + if (this->halfedges[he].vi[0] < this->vertex.size()) { + trivert[i] = (transform * this->vertex[this->halfedges[he].vi[0]].p).less_one_dim(); + } ++i; - he = he->next; + he = this->halfedges[he].next; } while (he != tri_he); return trivert; } @@ -244,27 +245,23 @@ namespace mplot #endif // Find all the neighbours of triangle *vertex* index a. // \return vector of halfedges indices - std::vector*> - find_neighbours (const mesh::halfedge<>* a) const + std::vector + find_neighbours (uint32_t a) const { - const mesh::halfedge<>* he = a; - std::vector*> rtn = {}; + uint32_t he = a; + std::vector rtn = {}; do { // he emanates from the vertex, so return it. rtn.push_back (he); - he = he->next->twin; + he = this->halfedges[this->halfedges[he].next].twin; + if (he == std::numeric_limits::max()) { + std::cout << "Warning: twins need to be set up to find_neighbours\n"; + break; + } } while (he != a); return rtn; } -#if 0 - std::vector*> - find_neighbours (const uint32_t a) const - { - // a is an index into this->vertex - const mesh::halfedge<>* he = this->vertex[a].he; // The emanating half edge from this vertex - return find_neighbours (he); - } -#endif + /* * Determine neighbour relations. That means populating a halfedge data structure. Don't * think there's any way around the at-worst O(N^2) computation, so save results into an h5 @@ -288,27 +285,27 @@ namespace mplot * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the * search time. * - * \return a tuple containing crossing location, halfedge (which is part of a triangle) + * \return a tuple containing crossing location, halfedge index (which specifies a triangle) */ - std::tuple, const mesh::halfedge<>*> + std::tuple, uint32_t> find_triangle_crossing (const sm::vec& coord_mf, const sm::vec& vdir, const sm::mat& model_to_scene, - const mesh::halfedge<>* ti_ml = nullptr ) const + const uint32_t ti_ml = std::numeric_limits::max() ) const { constexpr float fmax = std::numeric_limits::max(); sm::vec vstart = coord_mf - (vdir / 2.0f); // Return objects sm::vec isect_p = { fmax, fmax, fmax }; - const mesh::halfedge<>* isect_ti = nullptr; + uint32_t isect_ti = std::numeric_limits::max(); float isect_d = std::numeric_limits::max(); // distance to intersect const float vdsos = vdir.sos(); // Have we been passed a 'most likely triangle' to test first? If so, test it. - if (ti_ml != nullptr) { - std::cout << "Passing ti_ml to trangle_vertices: with he->vi = " << ti_ml->vi << std::endl; + if (ti_ml != std::numeric_limits::max()) { + std::cout << "Passing ti_ml to trangle_vertices: with he->vi = " << this->halfedges[ti_ml].vi << std::endl; sm::vec, 3> v = this->triangle_vertices (ti_ml); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { @@ -332,7 +329,7 @@ namespace mplot << ", next:" << this->halfedges[0].next << ", prev:" << this->halfedges[0].prev << std::endl; - std::cout << "CF. Passing tri.he " << tri.he << " to triangle_vertices(): with he->vi = " << tri.he->vi << std::endl; + std::cout << "CF. Passing tri.he " << tri.he << " to triangle_vertices(): with he->vi = " << this->halfedges[tri.he].vi << std::endl; sm::vec, 3> v = this->triangle_vertices (tri.he); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use @@ -371,9 +368,9 @@ namespace mplot return { isect_p, isect_ti }; } - // Find the location, and the triangle indices at which a ray between coord (in model frame) - // and the model centroid cross - the 'penetration point'. - std::tuple, const mesh::halfedge<>*> + // Find the location, and the triangle indices (by means of a halfedge index) at which a ray + // between coord (in model frame) and the model centroid cross - the 'penetration point'. + std::tuple, uint32_t> find_triangle_crossing (const sm::vec& coord_mf, const sm::mat& model_to_scene) const { sm::vec vdir = this->bb.mid() - coord_mf; @@ -401,18 +398,6 @@ namespace mplot // VERTEX_HANDLING // Find the normal of the vertex specified by halfedge vhe - sm::vec find_vertex_normal (const mesh::halfedge<>* vhe, const sm::mat& transform) const - { - auto neighbs = this->find_neighbours (vhe); - sm::vec vn = {}; - if (neighbs.size() == 0) { return vn; } - for (auto nb : neighbs) { - // Turn nb, a half edge index, into a triangle? - vn += this->triangle_normal (this->triangle_vertices (nb, transform)); - } - return (vn / neighbs.size()); - } -#ifdef UNDISABLED sm::vec find_vertex_normal (const uint32_t ti, const sm::mat& transform) const { auto neighbs = this->find_neighbours (ti); @@ -424,7 +409,6 @@ namespace mplot } return (vn / neighbs.size()); } -#endif #ifdef UNDISABLED // Find the common vertex between a and b @@ -778,17 +762,17 @@ namespace mplot * \return tuple containing: the hit point in scene coordinates; --the triangle normal of the * triangle we hit;-- and the indices of the triangle we hit. */ - std::tuple, const mesh::halfedge<>*> + std::tuple, uint32_t> find_triangle_hit (const sm::mat& model_to_scene, const sm::vec& camloc_mf, const sm::vec& vdir, - const mesh::halfedge<>* ti_ml = nullptr) + uint32_t ti_ml = std::numeric_limits::max()) { - this->ti0 = nullptr; + this->ti0 = std::numeric_limits::max(); sm::vec hit = {}; // Want to pass 'best tri' to this std::tie (hit, this->ti0) = this->find_triangle_crossing (camloc_mf, vdir, model_to_scene, ti_ml); - if (this->ti0 == nullptr) { std::cout << __func__ << ": No hit\n"; } + if (this->ti0 == std::numeric_limits::max()) { std::cout << __func__ << ": No hit\n"; } sm::vec hp_scene = (model_to_scene * hit).less_one_dim(); @@ -831,7 +815,7 @@ namespace mplot * * \return tuple containing: the hit point in scene coordinates and the mesh::face we hit. */ - std::tuple, const mesh::halfedge<>*> + std::tuple, uint32_t> find_triangle_hit (const sm::mat& camspace, const sm::mat& model_to_scene, const float search_dist_mult = 1.0f) { @@ -880,7 +864,7 @@ namespace mplot const float hoverheight) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->ti0 == nullptr) { + if (this->ti0 == std::numeric_limits::max()) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } @@ -907,7 +891,7 @@ namespace mplot const float hoverheight, const sm::vec& fwds) { // Let's 'draw' the camera towards the model and then arrange its normal upwards wrt to the normal of the model. - if (this->ti0 == nullptr) { + if (this->ti0 == std::numeric_limits::max()) { std::cout << __func__ << ": No hit/triangle normal\n"; return sm::mat{}; } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index b0a964e9..1b98f6a0 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -316,10 +316,10 @@ namespace mplot // Can now populate vertex, a vector of coordinates, if required, or simply access (*vp) // as needed using equiv.first - navmesh->vertex.resize (equiv.size(), mesh::vertex<>{}); + navmesh->vertex.resize (equiv.size(), mesh::vertex{}); i = 0; for (auto eq : equiv) { - navmesh->vertex[i++] = { (*vp)[eq.first], nullptr }; + navmesh->vertex[i++] = { (*vp)[eq.first], std::numeric_limits::max() }; } // Lastly, generate edges. For which we require use of indices, which is expressed in @@ -356,9 +356,13 @@ namespace mplot // Add three halfedges for the triangle uint32_t hesz = navmesh->halfedges.size(); navmesh->halfedges.resize (hesz + 3, {}); - const mesh::halfedge<>* he0 = &(navmesh->halfedges[hesz]); - const mesh::halfedge<>* he1 = &(navmesh->halfedges[hesz + 1]); - const mesh::halfedge<>* he2 = &(navmesh->halfedges[hesz + 2]); + //const mesh::halfedge<>* he0 = &(navmesh->halfedges[hesz]); + //const mesh::halfedge<>* he1 = &(navmesh->halfedges[hesz + 1]); + //const mesh::halfedge<>* he2 = &(navmesh->halfedges[hesz + 2]); + uint32_t he0 = hesz; + uint32_t he1 = hesz + 1; + uint32_t he2 = hesz + 2; + if (hesz < mlines) { std::cout << "setting halfedges["<halfedges[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, nullptr, he1, he2 }; - navmesh->halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, nullptr, he2, he0 }; - navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, nullptr, he0, he1 }; + + navmesh->halfedges[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, std::numeric_limits::max(), he1, he2 }; + navmesh->halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, std::numeric_limits::max(), he2, he0 }; + navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, std::numeric_limits::max(), he0, he1 }; if (hesz < mlines) { std::cout << "halfedges["<< hesz << "] contains: vi:" From 3642dab54651385120509674a0f899cd4e27dc70 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 09:52:05 +0000 Subject: [PATCH 16/82] Refactor halfedge index variable name --- mplot/NavMesh.h | 20 +++++++++++--------- mplot/VisualModelBase.h | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index db491ddf..c903639b 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -46,15 +46,17 @@ namespace mplot template requires std::is_integral_v struct vertex { - sm::vec p = {}; // Coordinate position of vertex - I he = std::numeric_limits::max(); // A halfedge emanating from this he_vertex + // Coordinate position of vertex + sm::vec p = {}; + // A halfedge (hi: halfedge index) emanating from this vertex + I hi = std::numeric_limits::max(); }; template requires std::is_integral_v struct face { - // The index of the starting halfedge - I he = std::numeric_limits::max(); + // The index of the starting halfedge that records the existence of this face + I hi = std::numeric_limits::max(); }; } @@ -329,8 +331,8 @@ namespace mplot << ", next:" << this->halfedges[0].next << ", prev:" << this->halfedges[0].prev << std::endl; - std::cout << "CF. Passing tri.he " << tri.he << " to triangle_vertices(): with he->vi = " << this->halfedges[tri.he].vi << std::endl; - sm::vec, 3> v = this->triangle_vertices (tri.he); + std::cout << "CF. Passing tri.he " << tri.hi << " to triangle_vertices(): with he->vi = " << this->halfedges[tri.hi].vi << std::endl; + sm::vec, 3> v = this->triangle_vertices (tri.hi); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use // vdir.sos() to exclude those that are too far and the distance^2 to find the @@ -339,7 +341,7 @@ namespace mplot float d = (p - vstart).sos(); if (d < isect_d && d < vdsos) { isect_p = p; - isect_ti = tri.he; + isect_ti = tri.hi; isect_d = d; } } @@ -349,7 +351,7 @@ namespace mplot if (isect_p[0] == fmax) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. for (uint32_t vi = 0; vi < this->vertex.size(); ++vi) { - sm::vec vertex_n = this->find_vertex_normal (this->vertex[vi].he, model_to_scene); + sm::vec vertex_n = this->find_vertex_normal (this->vertex[vi].hi, model_to_scene); vertex_n.renormalize(); vstart = coord_mf + (vertex_n / 2.0f); if (sm::geometry::ray_point_intersection (this->vertex[vi].p, vstart, -vertex_n)) { @@ -358,7 +360,7 @@ namespace mplot std::cout << "Register vertex triangle_crossing\n"; isect_p = this->vertex[vi].p; // now have halfedge specifying a triangle. Could attempt to look up to face, but could just return halfedge? - isect_ti = this->vertex[vi].he; + isect_ti = this->vertex[vi].hi; isect_d = d; } } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 1b98f6a0..c0116ba1 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -419,11 +419,11 @@ namespace mplot //t.i[1] = ti; } if (hesz < mlines) { - std::cout << "Push_back triangle " << t.he << std::endl; + std::cout << "Push_back triangle " << t.hi << std::endl; } navmesh->triangles.push_back (t); if (hesz < mlines) { - std::cout << "back triangle is " << navmesh->triangles.back().he << std::endl; + std::cout << "back triangle is " << navmesh->triangles.back().hi << std::endl; } } if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles (" << navmesh->halfedges.size() << " halfedges)" << std::endl; } From d2f3aa28fe8434b7a9e321dc1e18715ccfad6042 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 14:00:27 +0000 Subject: [PATCH 17/82] First draft conversion to halfedges. Next up: find the twin halfedges --- examples/model_crawler.cpp | 7 +- mplot/NavMesh.h | 601 ++++++++++++++----------------------- mplot/VisualModelBase.h | 58 ++-- 3 files changed, 253 insertions(+), 413 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 646a0b69..6685fe7f 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -97,14 +97,13 @@ int main (int argc, char** argv) auto[hp_scene, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); std::cout << "Find hit finds hit point " << hp_scene << " with ti0 halfedge: " << ti0 << std::endl; -#if 0 int move_counter = 0; constexpr int move_max = 1000; while (!v.readyToFinish()) { // Wait .018 s and also poll for mouse/keyboard events v.waitevents (0.018); - +#if 1 // Compute a new movement over the landscape mesh (the sphere) try { ca_view = gvp->navmesh->compute_mesh_movement (mv_ca, ca_view, sph_view, hoverheight); @@ -115,7 +114,7 @@ int main (int argc, char** argv) std::cout << "Exception navigating mesh at movement count " << move_counter << ": " << e.what() << std::endl; throw e; } - +#endif // Update the viewmatrix of the coord arrows, setting its position within the scene cap->setViewMatrix (ca_view); @@ -131,7 +130,7 @@ int main (int argc, char** argv) // Re-render the scene v.render(); } -#endif + v.keepOpen(); return rtn; diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index c903639b..230002d5 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -66,7 +66,7 @@ namespace mplot enum class type : uint32_t { generic, no_intersection, zero_mv, mv_to_vertex, undetected_crossing, nan_mv, off_edge }; NavException (const type _type) : m_type(_type) {} - NavException (const type _type, const std::vector>& t) : m_type(_type) { this->tris = t; } + NavException (const type _type, const std::vector& t) : m_type(_type) { this->tris = t; } using std::exception::what; const char* what() @@ -98,8 +98,8 @@ namespace mplot } // Error type determines message generated type m_type = type::generic; - // Triangles of interest (as indices into NavMesh::vertex) - std::vector> tris; + // Triangles of interest (as indices into NavMesh::halfedges) + std::vector tris; }; /*! @@ -122,7 +122,9 @@ namespace mplot */ std::set> edges; // This is edges, not half edges - // The vector of half edges in the mesh + /*! + * The vector of half edges in the mesh + */ std::vector> halfedges = {}; /*! @@ -176,46 +178,46 @@ namespace mplot } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex - sm::vec, 3> triangle_vertices (uint32_t tri_he) const + sm::vec, 3> triangle_vertices (uint32_t tri_hi) const { sm::vec, 3> trivert = {}; - if (tri_he == std::numeric_limits::max()) { - std::cout << "tri_he is null?\n"; + if (tri_hi == std::numeric_limits::max()) { + std::cout << "tri_hi is null?\n"; return trivert; } uint32_t i = 0; - uint32_t he = tri_he; + uint32_t hi = tri_hi; do { //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; - if (this->halfedges[he].vi[0] < this->vertex.size()) { - trivert[i] = this->vertex[this->halfedges[he].vi[0]].p; + if (this->halfedges[hi].vi[0] < this->vertex.size()) { + trivert[i] = this->vertex[this->halfedges[hi].vi[0]].p; } ++i; - he = this->halfedges[he].next; - } while (he != tri_he); + hi = this->halfedges[hi].next; + } while (hi != tri_hi); return trivert; } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex transformed by transform - sm::vec, 3> triangle_vertices (uint32_t tri_he, const sm::mat& transform) const + sm::vec, 3> triangle_vertices (uint32_t tri_hi, const sm::mat& transform) const { sm::vec, 3> trivert = {}; - if (tri_he == std::numeric_limits::max()) { - std::cout << "tri_he is null?\n"; + if (tri_hi == std::numeric_limits::max()) { + std::cout << "tri_hi is null?\n"; return trivert; } uint32_t i = 0; - uint32_t he = tri_he; + uint32_t hi = tri_hi; do { //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; - if (this->halfedges[he].vi[0] < this->vertex.size()) { - trivert[i] = (transform * this->vertex[this->halfedges[he].vi[0]].p).less_one_dim(); + if (this->halfedges[hi].vi[0] < this->vertex.size()) { + trivert[i] = (transform * this->vertex[this->halfedges[hi].vi[0]].p).less_one_dim(); } ++i; - he = this->halfedges[he].next; - } while (he != tri_he); + hi = this->halfedges[hi].next; + } while (hi != tri_hi); return trivert; } @@ -227,6 +229,14 @@ namespace mplot return n; } + // Retrieve the halfedge as a vector, transformed by the given transform + sm::vec edge_vector (uint32_t hi, const sm::mat& transform) const + { + const sm::vec v0 = (transform * this->vertex[this->halfedges[hi].vi[0]].p).less_one_dim(); + const sm::vec v1 = (transform * this->vertex[this->halfedges[hi].vi[1]].p).less_one_dim(); + return v1 - v0; + } + #if 0 // unused? sm::vvec neighbours (const uint32_t _idx) const { @@ -250,17 +260,17 @@ namespace mplot std::vector find_neighbours (uint32_t a) const { - uint32_t he = a; + uint32_t hi = a; std::vector rtn = {}; do { - // he emanates from the vertex, so return it. - rtn.push_back (he); - he = this->halfedges[this->halfedges[he].next].twin; - if (he == std::numeric_limits::max()) { + // hi emanates from the vertex, so return it. + rtn.push_back (hi); + hi = this->halfedges[this->halfedges[hi].next].twin; + if (hi == std::numeric_limits::max()) { std::cout << "Warning: twins need to be set up to find_neighbours\n"; break; } - } while (he != a); + } while (hi != a); return rtn; } @@ -294,6 +304,7 @@ namespace mplot const sm::mat& model_to_scene, const uint32_t ti_ml = std::numeric_limits::max() ) const { + constexpr bool debug_ftc = false; constexpr float fmax = std::numeric_limits::max(); sm::vec vstart = coord_mf - (vdir / 2.0f); @@ -325,13 +336,16 @@ namespace mplot } for (auto tri : this->triangles) { - std::cout << "this->halfedges["<< 0 << "] " << (&this->halfedges[0]) << " contains: vi:" - << this->halfedges[0].vi - << ", twin:" << this->halfedges[0].twin - << ", next:" << this->halfedges[0].next - << ", prev:" << this->halfedges[0].prev << std::endl; - std::cout << "CF. Passing tri.he " << tri.hi << " to triangle_vertices(): with he->vi = " << this->halfedges[tri.hi].vi << std::endl; + if constexpr (debug_ftc) { + std::cout << "this->halfedges["<< 0 << "] " << (&this->halfedges[0]) << " contains: vi:" + << this->halfedges[0].vi + << ", twin:" << this->halfedges[0].twin + << ", next:" << this->halfedges[0].next + << ", prev:" << this->halfedges[0].prev << std::endl; + std::cout << "CF. Passing tri.he " << tri.hi << " to triangle_vertices(): with he->vi = " << this->halfedges[tri.hi].vi << std::endl; + } + sm::vec, 3> v = this->triangle_vertices (tri.hi); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); // What if the triangle is one on the *other side of the model*?? Have to use @@ -380,24 +394,6 @@ namespace mplot return this->find_triangle_crossing (coord_mf, vdir, model_to_scene); } -#ifdef UNDISABLED - // Find a triangle containing indices a and b that isn't 'not_this' and return, along with its normal. - mesh::face<> find_other_triangle_containing (const uint32_t a, const uint32_t b, const mesh::face<>& not_this) const - { - mesh::face<> other; // initialized unset - for (auto tri : triangles) { - if (tri.i[0] == not_this.i[0] && tri.i[1] == not_this.i[1] && tri.i[2] == not_this.i[2]) { continue; } - if ((tri.i[0] == a && (tri.i[1] == b || tri.i[2] == b)) - || (tri.i[1] == a && (tri.i[0] == b || tri.i[2] == b)) - || (tri.i[2] == a && (tri.i[0] == b || tri.i[1] == b))) { - other = tri; - break; - } - } - return other; - } -#endif - // VERTEX_HANDLING // Find the normal of the vertex specified by halfedge vhe sm::vec find_vertex_normal (const uint32_t ti, const sm::mat& transform) const @@ -412,21 +408,6 @@ namespace mplot return (vn / neighbs.size()); } -#ifdef UNDISABLED - // Find the common vertex between a and b - uint32_t common_vertex (const mesh::face<>& a, const mesh::face<>& b) - { - uint32_t cv = std::numeric_limits::max(); - if (a.i[0] == b.i[0] || a.i[1] == b.i[0] || a.i[2] == b.i[0]) { - cv = b.i[0]; - } else if (a.i[0] == b.i[1] || a.i[1] == b.i[1] || a.i[2] == b.i[1]) { - cv = b.i[1]; - } else if (a.i[0] == b.i[2] || a.i[1] == b.i[2] || a.i[2] == b.i[2]) { - cv = b.i[2]; - } - return cv; - } -#endif // Flags class enum class pm_fl : uint32_t { @@ -578,16 +559,14 @@ namespace mplot */ struct crossing_data { - // edge_idx_a/b are the indices of the triangle vertices on the crossed edge - uint32_t edge_idx_a = 0; - uint32_t edge_idx_b = 0; + // The crossed halfedge + uint32_t halfedge = std::numeric_limits::max(); // The crossed edge as a vector sm::vec tri_edge = {}; // The partial movement. pm.mv is the movement, pm.end is the crossing point partial_movement pm = {}; }; -#ifdef UNDISABLED /* * Find the location at which a movement from mv_s in the direction mv_inplane crosses one of * the edges of the triangle specified by the three vertices in t_verts/t_indices. @@ -597,18 +576,20 @@ namespace mplot * * All coordinates are in the frame of the model that contains this triangle. * - * \param t_verts *Ordered* vertices of the triangle. Vertices should be in anti-clockwise order - * when viewed with the triangle normal vector coming 'out of the page' + * \param t_verts *Ordered* vertices of the triangle. Vertices should be in anti-clockwise + * order when viewed with the triangle normal vector coming 'out of the page'. These should + * be the vertices that were generated with the param tri (using function + * triangle_vertices()). Could be obtained within this function, but have already been + * computed, and they may be in a different frame of ref that they have in this->vertex * - * \param t_indices The *Ordered* indices of the vertices in t_verts. Used to return the crossed - * edge specified as two common indices. See t_verts for correct order of triangle vertices. + * \param tri The halfedge that gives the triangle * * \param mv_s The start of the planned movement * * \param mv_inplane The planned movement */ crossing_data compute_crossing_location (const sm::vec, 3>& t_verts, - const mesh::face<>& t_indices, + const uint32_t tri, const sm::vec& mv_s, const sm::vec& mv_inplane) { @@ -616,106 +597,57 @@ namespace mplot crossing_data cd; cd.pm.flags.set (pm_fl::no_cross_point, true); - const sm::vec& t0 = t_verts[0]; - const sm::vec& t1 = t_verts[1]; - const sm::vec& t2 = t_verts[2]; - sm::vec p = mv_s + mv_inplane; - sm::vec edge = t1 - t0; - sm::vec ptoe = p - t0; sm::vec tn = this->triangle_normal (t_verts); - bool inside01 = (tn.dot (edge.cross (ptoe)) >= 0); - if (!inside01) { - partial_movement pm = find_edge_crossing (t0, t1, tn, mv_s, mv_inplane); - if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { - std::cout << "ccl: fec returned pm.colinear true for t0t1\n"; - } - } - if (pm.flags.test (pm_fl::no_cross_point) - && pm.flags.test (pm_fl::colinear) == false) { - inside01 = true; - if constexpr (debug) { - std::cout << "ccl: No intersection for edge t0t1 " << t0 << " -- " << t1 - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; - } - } else { - if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t0t1\n"; } - std::cout << "ccl: Intersection for edge t0t1 " << t0 << " -- " << t1 - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; - } - cd.pm = pm; - cd.tri_edge = edge; - cd.edge_idx_a = t_indices.i[0]; - cd.edge_idx_b = t_indices.i[1]; - } - } - edge = t2 - t1; ptoe = p - t1; - bool inside21 = (tn.dot (edge.cross (ptoe)) >= 0); - if (!inside21) { - partial_movement pm = find_edge_crossing (t1, t2, tn, mv_s, mv_inplane); - if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { - std::cout << "ccl: fec returned pm.colinear true for t1t2\n"; - } - } - if (pm.flags.test (pm_fl::no_cross_point) - && pm.flags.test (pm_fl::colinear) == false) { - inside21 = true; - if constexpr (debug) { - std::cout << "ccl: No intersection for edge t1t2 " << t1 << " -- " << t2 - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; - } - } else { - if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t1t2\n"; } - std::cout << "ccl: Intersection for edge t1t2 " << t1 << " -- " << t2 - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; - } - cd.pm = pm; - cd.tri_edge = edge; - cd.edge_idx_a = t_indices.i[2]; - cd.edge_idx_b = t_indices.i[1]; - } - } + // do-while with tri + uint32_t hi = tri; + uint32_t a = 0; + sm::vec inside = { false, false, false }; + do { + uint32_t b = (a + 1u) % 3u; - edge = t0 - t2; ptoe = p - t2; - bool inside02 = (tn.dot (edge.cross (ptoe)) >= 0); - if (!inside02) { - partial_movement pm = find_edge_crossing (t2, t0, tn, mv_s, mv_inplane); - if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { - std::cout << "ccl: fec returned pm.colinear true for t2t0\n"; - } - } - if (pm.flags.test (pm_fl::no_cross_point) - && pm.flags.test (pm_fl::colinear) == false) { - inside02 = true; + sm::vec edge = t_verts[b] - t_verts[a]; + sm::vec ptoe = p - t_verts[a]; + + inside[a] = (tn.dot (edge.cross (ptoe)) >= 0); + if (!inside[a]) { + partial_movement pm = find_edge_crossing (t_verts[a], t_verts[b], tn, mv_s, mv_inplane); if constexpr (debug) { - std::cout << "ccl: No intersection for edge t2t0 " << t2 << " -- " << t0 - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; + if (pm.flags.test (pm_fl::colinear)) { + std::cout << "ccl: fec returned pm.colinear true for t" << a << "t" << b << "\n"; + } } - } else { - if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t2t0\n"; } - std::cout << "ccl: Intersection for edge t2t0 " << t2 << " -- " << t0 - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; + if (pm.flags.test (pm_fl::no_cross_point) + && pm.flags.test (pm_fl::colinear) == false) { + inside[a] = true; + if constexpr (debug) { + std::cout << "ccl: No intersection for edge t" << a << "t" << b << " " << t_verts[a] << " -- " << t_verts[b] + << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; + } + } else { + if constexpr (debug) { + if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t0t1\n"; } + std::cout << "ccl: Intersection for edge t" << a << "t" << b << " " << t_verts[a] << " -- " << t_verts[b] + << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; + } + cd.pm = pm; + cd.tri_edge = edge; + cd.halfedge = hi; } - cd.pm = pm; - cd.tri_edge = edge; - cd.edge_idx_a = t_indices.i[0]; - cd.edge_idx_b = t_indices.i[2]; } - } + + ++a; + hi = this->halfedges[hi].next; + + } while (hi != tri && a < 3); + // We've now tested edge crossing for three edges in the triangle. - // if constexpr (debug) { if (cd.pm.flags.test (pm_fl::no_cross_point) == false) { - std::cout << "ccl: Crossed over" << (inside01 ? " " : " 0-1") - << (inside21 ? " " : " 2-1") << (inside02 ? " " : " 0-2") << std::endl; + std::cout << "ccl: Crossed over" << (inside[0] ? " " : " 0-1") + << (inside[1] ? " " : " 2-1") << (inside[2] ? " " : " 0-2") << std::endl; // could test pairs of inside01 etc to detect crossing a vertex } else if (cd.pm.flags.test (pm_fl::colinear) == true) { // Movement was colinear. Set Crossed vertex? @@ -728,14 +660,13 @@ namespace mplot // cd.pm.no_cross_point will tell if there's a cross point or not } else { // We have NO crossing, which can occur for a variety of reasons - std::cout << "ccl: No crossings " << (inside01 ? " " : "!!0-1") - << (inside21 ? " " : "!!2-1") << (inside02 ? " " : "!!0-2") << std::endl; + std::cout << "ccl: No crossings " << (inside[0] ? " " : "!!0-1") + << (inside[1] ? " " : "!!2-1") << (inside[2] ? " " : "!!0-2") << std::endl; } } return cd; } -#endif /*! * Find the model location, starting from the location of a camera specified in @@ -909,7 +840,6 @@ namespace mplot return this->position_camera (hp_scene, model_to_scene, _x, tn, _z, hoverheight); } -#ifdef UNDISABLED /*! * Compute a movement over this navigation mesh. * @@ -933,7 +863,6 @@ namespace mplot // A data-containing exception to throw mplot::NavException ne (mplot::NavException::type::generic); - ne.tris.push_back (this->ti0); // Boolean state flags used in this function enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing }; @@ -948,7 +877,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "\n# compute_mesh_movement:\n" - << "\nti0: " << this->ti0.i + << "\nti0: " << this->ti0 << "\nti0 (sf): " << tv_sf << "\nnormal " << tn0 << "\nmovement (camframe): " << mv_camframe << "\nInitial camera location (camloc_sf): " << camloc_sf << "\n\n"; @@ -975,6 +904,7 @@ namespace mplot sm::mat cam_to_surface = cam_to_scene; cam_to_surface.pretranslate (hov_sf - camloc_sf); // This is now our init pose; the camera is now at the surface +//#ifdef NEED_DOUBLE_PRECISION // Try double precision if (isect == false) { std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (tn0 / 2.0f), -tn0); @@ -986,63 +916,60 @@ namespace mplot } } } +//#endif // VERTEX_HANDLING // If that didn't work, try the triangle *vertices* - uint32_t int_vertex = std::numeric_limits::max(); // intersection vertex + uint32_t int_vertex_hi = std::numeric_limits::max(); // intersection vertex if (isect == false) { if constexpr (debug_move) { std::cout << "Try the triangle vertices...\n"; } - for (uint32_t i = 0u; i < 3u; i++) { - + uint32_t hi = this->ti0; + uint32_t i = 0; + do { // We need to use the *vertex* normal for this test - the average of all the adjacent triangle normals! - // Will be for each vertex in tri: - sm::vec vertex_n = this->find_vertex_normal (this->ti0.i[i], model_to_scene); + sm::vec vertex_n = this->find_vertex_normal (hi, model_to_scene); vertex_n.renormalize(); - if constexpr (debug_move) { - std::cout << "Vertex normal for triangle index " << ti0.i[i] << " is " << vertex_n << std::endl; - } - + // How to figure out tv_sf[i]? + // sm::vec v_sf = (model_to_scane * this->vertex[halfedges[hi].vi[0]]).less_one_dim(); + // or, if tv_sf[0] is the start of ti0, then we can do this: if (sm::geometry::ray_point_intersection (tv_sf[i], camloc_sf + (vertex_n / 2.0f), -vertex_n)) { if constexpr (debug_move) { std::cout << "A VERTEX intersection is the start at " << tv_sf[i] << ", compare this with hov_sf = " << hov_sf << "\n"; - // if start is vertex, need to check movement across all the triangle-neighbours of this vertex (see later use of int_vertex) + // if start is vertex, need to check movement across all the triangle-neighbours of this vertex (see later use of int_vertex_hi) } hov_sf = tv_sf[i]; - int_vertex = i; + int_vertex_hi = hi; isect = true; } - } - } + ++i; + hi = this->halfedges[hi].next; - std::vector> trisearched; // the other triangles we search. To place in exception - if (isect == false) { + } while (hi != this->ti0); + } + if (isect == true) { + if constexpr (debug_move) { std::cout << "First ray_tri_intersected. Start of move is IN triangle ti0\n"; } + } else { if constexpr (debug_move2) { - std::cout << "No intersection (at start) with triangle ti0, so correct ti0 and tn0 (if we can)" << std::endl; + std::cout << "No intersection (at start) with triangle ti0, check neighbours (and maybe update ti0)" << std::endl; } // When very close to the boundary, ray_tri_intersection may fail. This triggers a // search for a neighbouring triangle which the camera may instead be hovering over // (this can occur when moving along an edge) - for (uint32_t i = 0u; i < 3u; i++) { - uint32_t i1 = i; - uint32_t i2 = (i + 1) % 3u; - auto _ti = this->find_other_triangle_containing (this->ti0.i[i1], this->ti0.i[i2], this->ti0); - if (_ti.i[0] != std::numeric_limits::max()) { - trisearched.push_back (_ti); - // Test to see if start location was inside a neighbour - sm::vec, 3> tv_lf = this->triangle_vertices (_ti, model_to_scene); - // _tn was returned in model frame coordinates, so recompute in scene frame - auto _tn = this->triangle_normal (tv_lf); - + uint32_t hi = this->ti0; + do { + uint32_t twin = this->halfedges[hi].twin; + if (twin != std::numeric_limits::max()) { + // Test to see if start location was inside this twin + sm::vec, 3> tv_lf = this->triangle_vertices (twin, model_to_scene); + sm::vec _tn = this->triangle_normal (tv_lf); auto [is, h] = sm::geometry::ray_tri_intersection (tv_lf[0], tv_lf[1], tv_lf[2], camloc_sf + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in " << _ti.i << std::endl; - } + if constexpr (debug_move) { std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in twin " << twin << std::endl; } if (is) { - if constexpr (debug_move) { std::cout << "CORRECT ti0 to " << _ti.i << std::endl; } + if constexpr (debug_move) { std::cout << "CORRECT ti0 to the twin " << twin << std::endl; } // We're in this neighbour, so update ti0/tn0 and mark isect true - this->ti0 = _ti; + this->ti0 = twin; tv_sf = tv_lf; tn0 = _tn; isect = true; @@ -1050,21 +977,19 @@ namespace mplot hov_sf = h; cam_to_surface = cam_to_scene; cam_to_surface.pretranslate (hov_sf - camloc_sf); // This is our init pose, placed on the surface - break; + break; // out of do-while } - } // else missing neighbour. Could see if it would land in a neighbour that's just off the edge? - } + } + hi = this->halfedges[hi].next; + } while (hi != this->ti0); if (isect == false) { - if constexpr (debug_move2) { - std::cout << "DBG No intersection (at start) with triangle ti0 OR neighbours" << std::endl; - } + if constexpr (debug_move2) { std::cout << "DBG No intersection (at start) with twins" << std::endl; } // Final test to see if we're on boundary? float closest_edge_d = sm::geometry::dist_to_tri_edge (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf - (tn0 * hoverheight)); if constexpr (debug_move2) { - std::cout << "Closest distance from " << (camloc_sf - (tn0 * hoverheight)) - << " to ti0 edge: " << closest_edge_d << std::endl; + std::cout << "Closest distance from " << (camloc_sf - (tn0 * hoverheight)) << " to ti0 edge: " << closest_edge_d << std::endl; } constexpr float ced_thresh = std::numeric_limits::epsilon() * 50; if (closest_edge_d < ced_thresh) { @@ -1072,20 +997,13 @@ namespace mplot isect = true; // SAY we are, and proceed? <-- this if it works. } else { ne.m_type = NavException::type::no_intersection; - ne.tris.insert (ne.tris.end(), trisearched.begin(), trisearched.end()); throw ne; } } else { if constexpr (debug_move2) { - std::cout << "Found intersection (at start) with (2-)neighbour triangle " - << this->ti0.i[0] << "," << this->ti0.i[1] << "," << this->ti0.i[2] << std::endl; + std::cout << "Found intersection (at start) with twin triangle " << this->ti0 << std::endl; } } - - } else { - if constexpr (debug_move) { - std::cout << "First ray_tri_intersected. Start of move is IN triangle ti0\n"; - } } // rest of function assumes isect was true (exception otherwise) @@ -1102,9 +1020,9 @@ namespace mplot // VERTEX_HANDLING // New section to handle the case that we started right on a vertex - if (isect == true && int_vertex != std::numeric_limits::max()) { + if (isect == true && int_vertex_hi != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. - auto onens = this->find_neighbours (this->ti0.i[int_vertex]); + auto onens = this->find_neighbours (int_vertex_hi); for (auto _ti : onens) { sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); auto _tn = this->triangle_normal (tv_nb); // gets normal in *scene frame* @@ -1117,9 +1035,7 @@ namespace mplot tv_sf = tv_nb; mv_orthog = _mv_orthog; mv_inplane = _mv_inplane; - if constexpr (debug_move) { - std::cout << "Break on cross point with triangle (" << _ti.i << ")\n"; - } + if constexpr (debug_move) { std::cout << "Break on cross point with triangle (" << _ti << ")\n"; } break; } else { // No crossing, did we land in the triangle? @@ -1130,9 +1046,7 @@ namespace mplot tv_sf = tv_nb; mv_orthog = _mv_orthog; mv_inplane = _mv_inplane; - if constexpr (debug_move) { - std::cout << "Break as we landed in triangle (" << _ti.i << ")\n"; - } + if constexpr (debug_move) { std::cout << "Break as we landed IN triangle (" << _ti << ")\n"; } break; } } @@ -1144,16 +1058,16 @@ namespace mplot // a triangle edge had been crossed, because the original method // (compute_crossing_location, which uses a faster, but numerically fallible approach) // failed. - sm::vec detected_edge = {}; - sm::vec detected_edgevec = {}; - mesh::face<> detected_newtri = {}; // new triangle detected as part of a vertex crossing + uint32_t detected_edge = std::numeric_limits::max(); + //sm::vec detected_edgevec = {}; + uint32_t detected_newtri = std::numeric_limits::max(); // new triangle detected as part of a vertex crossing // Now loop while our path may traverse one or more triangles while (!flags.test (cmm_fl::done)) { if constexpr (debug_move) { std::cout << "\nWHILE LOOP\n" - << "ti0 = (" << this->ti0.i << ")\n" + << "ti0 = (" << this->ti0 << ")\n" << "mv_inplane: " << hov_sf << "," << mv_inplane << "\n" << "tn0 = " << tn0 << ")\n"; } @@ -1171,53 +1085,41 @@ namespace mplot crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane); if (cd.pm.flags.test (pm_fl::no_cross_point) == false || flags.test (cmm_fl::detected_crossing) || flags.test (cmm_fl::vertex_crossing)) { - // Then an edge (or vertex)crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing') + // Then an edge (or vertex) crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing') if (flags.test (cmm_fl::detected_crossing)) { - if constexpr (debug_move) { - std::cout << "This is a detected crossing; changing edge_idx_a/b to " << detected_edge << std::endl; - } + if constexpr (debug_move) { std::cout << "This is a detected crossing; changing crossing_data.halfedge to " << detected_edge << std::endl; } // We have to update our crossing data, as we detected a crossing over // an edge (probably while moving along that edge) - cd.edge_idx_a = detected_edge[0]; - cd.edge_idx_b = detected_edge[1]; - cd.tri_edge = detected_edgevec; + cd.halfedge = detected_edge; + cd.tri_edge = this->edge_vector (detected_edge, model_to_scene); cd.pm.mv = mv_inplane; cd.pm.end = hov_sf + mv_inplane; } else { - if constexpr (debug_move) { - std::cout << "This IS a crossing (compute_crossing_location found it) " << std::endl; - } + if constexpr (debug_move) { std::cout << "This IS a crossing (compute_crossing_location found it) " << std::endl; } } // _ti, _tn are the new triangle sm::vec _tn = {}; - mesh::face<> _ti = {}; + uint32_t _ti = std::numeric_limits::max(); if (flags.test (cmm_fl::vertex_crossing)) { - if constexpr (debug_move) { - std::cout << "Setting _ti to over-the-vertex tri " << detected_newtri.i << std::endl; - } + if constexpr (debug_move) { std::cout << "Setting _ti to over-the-vertex tri " << detected_newtri << std::endl; } _ti = detected_newtri; } else { - // Can work out new triangle here across the crossed edge + // new triangle is the twin of the crossed edge + _ti = this->halfedges[cd.halfedge].twin; if constexpr (debug_move) { - std::cout << "find triangle across edge: find_other_triangle_containing (" - << cd.edge_idx_a << ", " << cd.edge_idx_b - << ", [" << this->ti0.i << "])" << std::endl; + std::cout << "find triangle across edge: halfedge " << cd.halfedge << " gives the neighbour, which is its twin: " << _ti << std::endl; } - _ti = this->find_other_triangle_containing (cd.edge_idx_a, cd.edge_idx_b, this->ti0); } - if (_ti.i[0] != std::numeric_limits::max()) { + if (_ti != std::numeric_limits::max()) { // Re-orient onto the new triangle sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); _tn = this->triangle_normal (newtv_sf); - if constexpr (debug_move) { - std::cout << "RE-ORIENT to _ti: " << _ti.i - << "\n " << newtv_sf << "\n normal " << _tn << "\n"; - } + if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); } @@ -1247,11 +1149,8 @@ namespace mplot // for another loop. sm::vec endmv = (reorient_model * cam_to_surface * sm::vec{}).less_one_dim(); // Is endmv in newtv_sf/_ti? - auto [isect2, isectpoint2] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], - endmv + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "endmv = " << endmv << " DOES" << (isect2 ? "" : " NOT") << " land in new triangle\n"; - } + auto [isect2, isectpoint2] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect2 ? "" : " NOT") << " land in new triangle\n"; } if (isect2) { // We DID land in the neighbouring triangle. We are done. cam_to_surface = reorient_model * cam_to_surface; @@ -1273,7 +1172,6 @@ namespace mplot } this->ti0 = _ti; - ne.tris.push_back (this->ti0); tn0 = _tn; } else { @@ -1299,8 +1197,7 @@ namespace mplot } else { // Test if it was movement-within; the simplest case if constexpr (debug_move) { - std::cout << "No cross point and not colinear.\n Testing if " - << (hov_sf + mv_inplane + (tn0 / 2.0f)) << "," << -tn0 + std::cout << "No cross point and not colinear.\n Testing if " << (hov_sf + mv_inplane + (tn0 / 2.0f)) << "," << -tn0 << " intersects tv_sf (" << tv_sf << "\n"; } auto [single_mv, he] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], hov_sf + mv_inplane + (tn0 / 2.0f), -tn0); @@ -1308,79 +1205,32 @@ namespace mplot } if (flags.test (cmm_fl::single_movement)) { - if constexpr (debug_move) { std::cout << "End of movement is *still* in ti0, so move mv_inplane/mv_camframe\n"; } // Perform simplest movement, which is just to translate by mv_inplane + if constexpr (debug_move) { std::cout << "End of movement is *still* in ti0, so move mv_inplane\n"; } cam_to_surface.pretranslate (mv_inplane); flags.set (cmm_fl::done, true); } else { - if constexpr (debug_move) { - std::cout << "End of movement is NOT in ti0 " << this->ti0.i << ". Look for start neighbours\n"; - } -#if 1 + if constexpr (debug_move) { std::cout << "End of movement is NOT in ti0 " << this->ti0 << ". Look for start neighbours\n"; } + // Test neighbours, new scheme using halfedge data structures // Test neighbours to find any for which the start location is also within-boundary flags.set (cmm_fl::detected_crossing, false); flags.set (cmm_fl::vertex_crossing, false); - // Was container for 'two neighbours' - mesh::face<> _ti_2n = { std::numeric_limits::max() }; - - // We have this->ti0, which contains a halfedge and can use this to get all neighbours _ti being a halfedge index (uint32_t) - for (each neighbour _ti) { - // Test to see if start location was inside a neighbour - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - auto _tn = this->triangle_normal (tv_nb); - - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); - sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "endis? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") - << " in " << _ti.i << " / " << tv_nb << std::endl; - std::cout << "End of move " << (endis ? "IS" : "is NOT") - << " in that triangle" << std::endl; - } - - // Here, start is in original, end may not be in original. This - // is an 'intersection detected crossing' of a triangle edge - // which wasn't picked up with compute_crossing_location - if (endis) { - // End is in neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } - flags.set (cmm_fl::detected_crossing, true); - detected_edge = { this->ti0.i[i1], this->ti0.i[i2] }; - detected_edgevec = tv_nb[i2] - tv_nb[i1]; - break; // out of for - } else { // end not in neighbour - if (is) { // start is in neighbour tri (will re-orient to this and re-loop) - _ti_2n = _ti; - break; // out of for - } // else end is not in neighbour, and neither is start. This - // occurs if the end is ON the boundary, but precision errors - // mean this location isn't 'in' either start or neighbour - // (according to ray_tri_intersection) - } - - } -#else + uint32_t _ti_2n = std::numeric_limits::max(); + sm::vec_tn_2n = {}; + sm::vec _tn = {}; - // Test 3 neighbours across the edges to find any for which the start location is also within-boundary - flags.set (cmm_fl::detected_crossing, false); - flags.set (cmm_fl::vertex_crossing, false); - mesh::face<> _ti_2n = { std::numeric_limits::max() }; - for (uint32_t i = 0u; i < 3u; i++) { - uint32_t i1 = i; - uint32_t i2 = (i + 1) % 3u; - auto _ti = this->find_other_triangle_containing (this->ti0.i[i1], this->ti0.i[i2], this->ti0); - if (_ti.i[0] != std::numeric_limits::max()) { + // TWO NEIGHBOURS + std::set neighbours_tested; + uint32_t hi = this->ti0; + do { + uint32_t twin = this->halfedges[hi].twin; + if (twin != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - auto _tn = this->triangle_normal (tv_nb); + sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); + _tn = this->triangle_normal (tv_nb); auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); @@ -1390,79 +1240,83 @@ namespace mplot } auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") - << " in " << _ti.i << " / " << tv_nb << std::endl; - std::cout << "End of move " << (endis ? "IS" : "is NOT") - << " in that triangle" << std::endl; + std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in twin " << twin << " / " << tv_nb << std::endl; + std::cout << "End of move " << (endis ? "IS" : "is NOT") << " in that triangle" << std::endl; } - // Here, start is in original, end may not be in original. This - // is an 'intersection detected crossing' of a triangle edge + neighbours_tested.insert (twin); + + // Here, start is in original, end may not be in original. This is an 'intersection detected crossing' of a triangle edge // which wasn't picked up with compute_crossing_location if (endis) { // End is in neighbour so this is a detected crossing if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } flags.set (cmm_fl::detected_crossing, true); - detected_edge = { this->ti0.i[i1], this->ti0.i[i2] }; - detected_edgevec = tv_nb[i2] - tv_nb[i1]; + detected_edge = hi; + //detected_edgevec = tv_nb[i2] - tv_nb[i1]; // no longer need as we have detected_edge, a halfedge? Can construct later? break; // out of for } else { // end not in neighbour if (is) { // start is in neighbour tri (will re-orient to this and re-loop) - _ti_2n = _ti; + _ti_2n = twin; + _tn_2n = _tn; break; // out of for - } // else end is not in neighbour, and neither is start. This - // occurs if the end is ON the boundary, but precision errors - // mean this location isn't 'in' either start or neighbour - // (according to ray_tri_intersection) + } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors + // mean this location isn't 'in' either start or neighbour (according to ray_tri_intersection) } } - } - // Test one-neighbours here if necessary (that is, if the two neighbour test above failed) - sm::vec _tn = {}; - if (flags.test (cmm_fl::detected_crossing) == false && - _ti_2n.i[0] == std::numeric_limits::max()) { - auto onens = this->find_one_neighbours (this->ti0); - for (auto _ti : onens) { - // Are we in this one? - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - _tn = this->triangle_normal (tv_nb); // scene frame - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); - sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "endis ONE-n? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") - << " in ONE-neighbour " << _ti.i << " / " << tv_nb << std::endl; - std::cout << "And End of move " << (endis ? "IS" : "is NOT") - << " in that ONE-neighbour " << std::endl; - } + hi = this->halfedges[hi].next; - if (endis) { - // End is in one-neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing over ONE-neighbour! Pass on to next loop!\n"; } - flags.set (cmm_fl::vertex_crossing, true); - detected_edge = { this->common_vertex (this->ti0, _ti), std::numeric_limits::max() }; - detected_edgevec = {}; // to be the cross product of the last-triangle normal and the newtri normal. - detected_newtri = _ti; - break; // out of for - } else { // end not in one-neighbour - if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) - _ti_2n = _ti; + } while (hi != this->ti0); + + // Test one-neighbours here if necessary (that is, if the two neighbour test above failed) + if (flags.test (cmm_fl::detected_crossing) == false && _ti_2n == std::numeric_limits::max()) { + uint32_t hi = this->ti0; + do { // For each half edge around ti0 + std::vector nbs = this->find_neighbours (hi); + for (auto _ti : nbs) { + // Already tested? This should avoid us testing any two-neighbours here (already did them above) + if (neighbours_tested.count (_ti)) { continue; } + neighbours_tested.insert (_ti); + + sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + _tn = this->triangle_normal (tv_nb); + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); + sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); + sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; + if constexpr (debug_move) { + std::cout << "endis ONE-n? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; + } + auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { + std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in ONE-neighbour " << _ti << " / " << tv_nb << std::endl; + std::cout << "And End of move " << (endis ? "IS" : "is NOT") << " in that ONE-neighbour " << std::endl; + } + + if (endis) { + // End is in one-neighbour so this is a detected crossing + if constexpr (debug_move) { std::cout << "DETECTED crossing over ONE-neighbour! Pass on to next loop!\n"; } + flags.set (cmm_fl::vertex_crossing, true); + detected_edge = hi; + //detected_edgevec = {std::numeric_limits::max()}; // to be the cross product of the last-triangle normal and the newtri normal. + detected_newtri = _ti; break; // out of for - } // else end is not in one-neighbour, and neither is start. + } else { // end not in one-neighbour + if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) + _ti_2n = _ti; + _tn_2n = _tn; + break; // out of for + } // else end is not in one-neighbour, and neither is start. + } } - } + hi = this->halfedges[hi].next; + + } while (hi != this->ti0); } -#endif - if (_ti_2n.i[0] != std::numeric_limits::max()) { + if (_ti_2n != std::numeric_limits::max()) { // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop this->ti0 = _ti_2n; - ne.tris.push_back (this->ti0); tn0 = _tn; // recompute mv_inplane for this neighbour triangle mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); @@ -1492,7 +1346,6 @@ namespace mplot return cam_to_surface; } // compute_mesh_movement -#endif // UNDISABLED }; // struct NavMesh diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index c0116ba1..7f65d64a 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -306,9 +306,8 @@ namespace mplot } ++i; } - if constexpr (debug_mn) { - std::cout << "make_navmesh: Created equiv inverse" << std::endl; - } + if constexpr (debug_mn) { std::cout << "make_navmesh: Created equiv inverse" << std::endl; } + if (vcount != vps) { std::cout << "make_navmesh: WARNING: Vertex count from equiv is " << vcount << " which should (but does not) equal " << vps << std::endl; @@ -324,8 +323,6 @@ namespace mplot // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. - std::cout << "At start, halfedges size is " << navmesh->halfedges.size() << std::endl; - //navmesh->halfedges.reserve (this->indices.size() / 3); for (uint32_t i = 0; i < this->indices.size(); i += 3) { // Each three entries in indices is a triangle containing 3 edges. NB: Edges must be listed in ascending order! std::array e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]] }; @@ -356,42 +353,40 @@ namespace mplot // Add three halfedges for the triangle uint32_t hesz = navmesh->halfedges.size(); navmesh->halfedges.resize (hesz + 3, {}); - //const mesh::halfedge<>* he0 = &(navmesh->halfedges[hesz]); - //const mesh::halfedge<>* he1 = &(navmesh->halfedges[hesz + 1]); - //const mesh::halfedge<>* he2 = &(navmesh->halfedges[hesz + 2]); uint32_t he0 = hesz; uint32_t he1 = hesz + 1; uint32_t he2 = hesz + 2; - if (hesz < mlines) { - std::cout << "setting halfedges["<halfedges[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, std::numeric_limits::max(), he1, he2 }; navmesh->halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, std::numeric_limits::max(), he2, he0 }; navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, std::numeric_limits::max(), he0, he1 }; - if (hesz < mlines) { - std::cout << "halfedges["<< hesz << "] contains: vi:" - << navmesh->halfedges[hesz].vi - << ", twin:" << navmesh->halfedges[hesz].twin - << ", next:" << navmesh->halfedges[hesz].next - << ", prev:" << navmesh->halfedges[hesz].prev << std::endl; + if constexpr (debug_mn) { + if (hesz < mlines) { + std::cout << "halfedges["<< hesz << "] contains: vi:" + << navmesh->halfedges[hesz].vi + << ", twin:" << navmesh->halfedges[hesz].twin + << ", next:" << navmesh->halfedges[hesz].next + << ", prev:" << navmesh->halfedges[hesz].prev << std::endl; + } } // Direct population of triangles. Three indices and a 4th number to hold flags (with bit0 meaning edge-triangle) - // mesh::face<> t = { {navmesh_idx[indices[i]], navmesh_idx[indices[i+1]], navmesh_idx[indices[i+2]]} }; - - mesh::face<> t = { he0 }; // Face will just be this? The first half edge. + mesh::face<> t = { he0 }; // Face will just be the first half edge. // The normal vector for this triangle could be obtained from the mesh normals, but // we can't trust them (though they're easy to get, as we're dealing with indices @@ -412,19 +407,12 @@ namespace mplot // Check rotational sense of triangles? if (n.dot (tn) < 0.0f) { - // need to swap order in t? throw std::runtime_error ("Need to swap triangle order\n"); //uint32_t ti = t.i[2]; //t.i[2] = t.i[1]; //t.i[1] = ti; } - if (hesz < mlines) { - std::cout << "Push_back triangle " << t.hi << std::endl; - } navmesh->triangles.push_back (t); - if (hesz < mlines) { - std::cout << "back triangle is " << navmesh->triangles.back().hi << std::endl; - } } if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles (" << navmesh->halfedges.size() << " halfedges)" << std::endl; } From eaa7a03f4ef5be47dc65234fc8980478ec83ec6d Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 14:47:44 +0000 Subject: [PATCH 18/82] A basic twin finding algo --- mplot/NavMesh.h | 55 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 230002d5..a0386dbe 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -282,9 +282,60 @@ namespace mplot * The key is the half-edge data structure. * See: https://jerryyin.info/geometry-processing-algorithms/half-edge/ */ - void compute_neighbour_relations() + void compute_neighbour_relations () { - // Writeme. This fills in the 'twin' field of the halfedges appropriately + uint32_t sz = this->halfedges.size(); + + // Search a 'band' either side of i first, assuming that neighbour triangles are likely + // to have been nearby in the indices array + const uint32_t band = 3 * 100; + for (uint32_t i = 0; i < sz; ++i) { + + uint32_t sb = i >= band ? i - band : 0; + uint32_t eb = i + band < sz ? i + band : sz; + + // std::cout << "Search " << 0 << " -> " << sb << " -> " << eb << " -> " << sz << std::endl; + + sm::vec vi = this->halfedges[i].vi; + + // bool matched = false; // can test twin + // First sb to eb, which we hope is most likely to find a twin + for (uint32_t j = sb; j < eb; ++j) { + if (j == i) { continue; } + const sm::vec& vij = this->halfedges[j].vi; + if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match + this->halfedges[i].twin = j; + } + } + + if (this->halfedges[i].twin == std::numeric_limits::max()) { + // Then, if no match, search rest + if (sb != 0) { std::cout << "Wider search pre-band\n"; } + for (uint32_t j = 0; j < sb; ++j) { + if (j == i) { continue; } + const sm::vec& vij = this->halfedges[j].vi; + if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match + this->halfedges[i].twin = j; + } + + } + } + + if (this->halfedges[i].twin == std::numeric_limits::max()) { + if (eb != sz) { std::cout << "Wider search post-band\n"; } + for (uint32_t j = eb; j < sz; ++j) { + if (j == i) { continue; } + const sm::vec& vij = this->halfedges[j].vi; + if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match + this->halfedges[i].twin = j; + } + } + } + + if (this->halfedges[i].twin != std::numeric_limits::max()) { + std::cout << "Twin of " << i << " is " << this->halfedges[i].twin << std::endl; + } // else halfedges[i] is an edge of the mesh + } } /* From 6cc054c511d4afd13a8f8704c899a7d31ca2a4aa Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 15:41:54 +0000 Subject: [PATCH 19/82] Re-instate the triangle swapping. Improve the twin-finding --- mplot/NavMesh.h | 45 ++++++++++++++++++++++++++++++++--------- mplot/VisualModelBase.h | 9 +++++---- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index a0386dbe..0b9eb8ed 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -285,57 +285,82 @@ namespace mplot void compute_neighbour_relations () { uint32_t sz = this->halfedges.size(); + std::cout << "Finding twins for " << sz << " halfedges\n"; // Search a 'band' either side of i first, assuming that neighbour triangles are likely // to have been nearby in the indices array - const uint32_t band = 3 * 100; + const uint32_t band = 3 * 1000; + + uint32_t wider_searches = 0; // Count how many times we make a wider search + + uint64_t twin_meandist = 0; // See how far a search has to search for a twin + uint32_t twins = 0; + for (uint32_t i = 0; i < sz; ++i) { - uint32_t sb = i >= band ? i - band : 0; - uint32_t eb = i + band < sz ? i + band : sz; + const sm::vec& vi = this->halfedges[i].vi; + + // halfedges[i].twin may already have been set (as we set two twins at a time) + if (this->halfedges[i].twin != std::numeric_limits::max()) { continue; } - // std::cout << "Search " << 0 << " -> " << sb << " -> " << eb << " -> " << sz << std::endl; + // It's useful to know how long you will have to wait... + if (i % 20000u == 0u) { std::cout << ((100.0f * i)/sz) << " \%...\n" << std::endl; } - sm::vec vi = this->halfedges[i].vi; + uint32_t sb = i >= band ? i - band : 0; + uint32_t eb = i + band < sz ? i + band : sz; - // bool matched = false; // can test twin // First sb to eb, which we hope is most likely to find a twin for (uint32_t j = sb; j < eb; ++j) { if (j == i) { continue; } const sm::vec& vij = this->halfedges[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match this->halfedges[i].twin = j; + this->halfedges[j].twin = i; + break; } } + uint32_t wider = 0; if (this->halfedges[i].twin == std::numeric_limits::max()) { // Then, if no match, search rest - if (sb != 0) { std::cout << "Wider search pre-band\n"; } + if (sb != 0 && !wider) { wider = 1; } for (uint32_t j = 0; j < sb; ++j) { if (j == i) { continue; } const sm::vec& vij = this->halfedges[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match this->halfedges[i].twin = j; + this->halfedges[j].twin = i; + break; } - } } if (this->halfedges[i].twin == std::numeric_limits::max()) { - if (eb != sz) { std::cout << "Wider search post-band\n"; } + if (eb != sz && !wider) { wider = 1; } for (uint32_t j = eb; j < sz; ++j) { if (j == i) { continue; } const sm::vec& vij = this->halfedges[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match this->halfedges[i].twin = j; + this->halfedges[j].twin = i; + break; } } } + wider_searches += wider; + if (this->halfedges[i].twin != std::numeric_limits::max()) { - std::cout << "Twin of " << i << " is " << this->halfedges[i].twin << std::endl; + // std::cout << "Twin of " << i << " is " << this->halfedges[i].twin << std::endl; + if (wider) { + twin_meandist += i > this->halfedges[i].twin ? i - this->halfedges[i].twin : this->halfedges[i].twin - i; + ++twins; + } } // else halfedges[i] is an edge of the mesh } + + std::cout << "In " << sz << " halfedge searches, had to widen the search in " << (100.0 * wider_searches) / sz << " \%\n"; + std::cout << "Mean wider twin search distance (in array elements) was " << static_cast(twin_meandist) / twins << "\n"; } /* diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 7f65d64a..7995538c 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -407,10 +407,11 @@ namespace mplot // Check rotational sense of triangles? if (n.dot (tn) < 0.0f) { - throw std::runtime_error ("Need to swap triangle order\n"); - //uint32_t ti = t.i[2]; - //t.i[2] = t.i[1]; - //t.i[1] = ti; + //throw std::runtime_error ("Need to swap triangle order\n"); + // Swap first and last half edge? + navmesh->halfedges[hesz].vi.rotate(); + navmesh->halfedges[hesz + 1].vi.rotate(); + navmesh->halfedges[hesz + 2].vi.rotate(); } navmesh->triangles.push_back (t); } From 6de328c98d614c67536839cdd1b5894f961b81d6 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 15:48:40 +0000 Subject: [PATCH 20/82] Removes some cruft --- mplot/NavMesh.h | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 0b9eb8ed..a4ad981c 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -237,24 +237,6 @@ namespace mplot return v1 - v0; } -#if 0 // unused? - sm::vvec neighbours (const uint32_t _idx) const - { - sm::vvec rtn; - // Search edges to find those that include _idx and then pack up the other ends in a return object - for (auto e : this->edges) { - // we have e[0] and e[1] - if (e[0] == _idx) { - // neighb is e[1] - rtn.push_back (e[1]); - } else if (e[1] == _idx) { - // neighb is e[0] - rtn.push_back (e[0]); - } - } - return rtn; - } -#endif // Find all the neighbours of triangle *vertex* index a. // \return vector of halfedges indices std::vector @@ -437,7 +419,6 @@ namespace mplot } } - // VERTEX_HANDLING if (isect_p[0] == fmax) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. for (uint32_t vi = 0; vi < this->vertex.size(); ++vi) { @@ -470,7 +451,6 @@ namespace mplot return this->find_triangle_crossing (coord_mf, vdir, model_to_scene); } - // VERTEX_HANDLING // Find the normal of the vertex specified by halfedge vhe sm::vec find_vertex_normal (const uint32_t ti, const sm::mat& transform) const { @@ -980,7 +960,6 @@ namespace mplot sm::mat cam_to_surface = cam_to_scene; cam_to_surface.pretranslate (hov_sf - camloc_sf); // This is now our init pose; the camera is now at the surface -//#ifdef NEED_DOUBLE_PRECISION // Try double precision if (isect == false) { std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (tn0 / 2.0f), -tn0); @@ -992,9 +971,7 @@ namespace mplot } } } -//#endif - // VERTEX_HANDLING // If that didn't work, try the triangle *vertices* uint32_t int_vertex_hi = std::numeric_limits::max(); // intersection vertex if (isect == false) { @@ -1094,7 +1071,6 @@ namespace mplot return cam_to_scene; } - // VERTEX_HANDLING // New section to handle the case that we started right on a vertex if (isect == true && int_vertex_hi != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. From b9c7f5adf744f0960b28e084a3f2e0af371d2113 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 16:02:27 +0000 Subject: [PATCH 21/82] Fixes to ensure everything compiles on my PC --- examples/draw_triangles_intersections.cpp | 12 ++++++------ examples/triangle_intersect.cpp | 4 ++-- mplot/NavMesh.h | 4 ++-- mplot/NormalsVisual.h | 12 +++++------- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/examples/draw_triangles_intersections.cpp b/examples/draw_triangles_intersections.cpp index 75e0488b..1adb713f 100644 --- a/examples/draw_triangles_intersections.cpp +++ b/examples/draw_triangles_intersections.cpp @@ -333,10 +333,10 @@ int main() auto start_wr = (vmi * start).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn, vm); - if (ti.i[0] == std::numeric_limits::max()) { + if (ti == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti.i << std::endl; + std::cout << "Indices: " << ti << std::endl; std::cout << "Contains hit " << hit << std::endl; sv = std::make_unique>(hit, 0.07, mplot::colour::springgreen2); @@ -347,10 +347,10 @@ int main() auto start_wr_fr2 = (vmi * start_fr2).less_one_dim(); // wr to tvp auto [hit_fr2, ti_fr2] = tvp->navmesh->find_triangle_crossing (start_wr_fr2, dirn_fr2, vm); - if (ti_fr2.i[0] == std::numeric_limits::max()) { + if (ti_fr2 == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti_fr2.i << std::endl; + std::cout << "Indices: " << ti_fr2 << std::endl; std::cout << "Contains hit " << hit_fr2 << std::endl; sv = std::make_unique>(hit_fr2, 0.07, mplot::colour::springgreen2); @@ -362,10 +362,10 @@ int main() auto start_wr_bh = (vmi * start_bh).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; auto [hit_bh, ti_bh] = tvp->navmesh->find_triangle_crossing (start_wr_bh, dirn_bh, vm); - if (ti_bh.i[0] == std::numeric_limits::max()) { + if (ti_bh == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti_bh.i << std::endl; + std::cout << "Indices: " << ti_bh << std::endl; std::cout << "Contains hit " << hit_bh << std::endl; sv = std::make_unique>(hit_bh, 0.07, mplot::colour::springgreen2); diff --git a/examples/triangle_intersect.cpp b/examples/triangle_intersect.cpp index 1a229e61..7cb21a6e 100644 --- a/examples/triangle_intersect.cpp +++ b/examples/triangle_intersect.cpp @@ -72,10 +72,10 @@ int main (int argc, char** argv) auto start_wr = (vmi * start).less_one_dim(); // wr to tvp std::cout << "start_wr = " << start_wr << std::endl; auto [hit, ti] = tvp->navmesh->find_triangle_crossing (start_wr, dirn, vm); - if (ti.i[0] == std::numeric_limits::max()) { + if (ti == std::numeric_limits::max()) { std::cout << "NO HIT\n"; } else { - std::cout << "Indices: " << ti.i << std::endl; + std::cout << "Indices: " << ti << std::endl; std::cout << "Contains hit " << hit << std::endl; sv = std::make_unique>(hit, start_sphr * 1.1f, mplot::colour::springgreen2); diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index a4ad981c..7b8642f3 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -286,7 +286,7 @@ namespace mplot if (this->halfedges[i].twin != std::numeric_limits::max()) { continue; } // It's useful to know how long you will have to wait... - if (i % 20000u == 0u) { std::cout << ((100.0f * i)/sz) << " \%...\n" << std::endl; } + if (i % 20000u == 0u) { std::cout << ((100.0f * i)/sz) << " percent...\n" << std::endl; } uint32_t sb = i >= band ? i - band : 0; uint32_t eb = i + band < sz ? i + band : sz; @@ -341,7 +341,7 @@ namespace mplot } // else halfedges[i] is an edge of the mesh } - std::cout << "In " << sz << " halfedge searches, had to widen the search in " << (100.0 * wider_searches) / sz << " \%\n"; + std::cout << "In " << sz << " halfedge searches, had to widen the search in " << (100.0 * wider_searches) / sz << " percent\n"; std::cout << "Mean wider twin search distance (in array elements) was " << static_cast(twin_meandist) / twins << "\n"; } diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index b5eb0fa8..88ae176c 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -62,17 +62,15 @@ namespace mplot for (auto ti : mymodel->navmesh->triangles) { - const sm::vec& v0 = mymodel->navmesh->vertex[ti.i[0]].p; - const sm::vec& v1 = mymodel->navmesh->vertex[ti.i[1]].p; - const sm::vec& v2 = mymodel->navmesh->vertex[ti.i[2]].p; + sm::vec, 3> vrts = mymodel->navmesh->triangle_vertices (ti.hi); - nvc = v1 - v0; - nvd = v2 - v0; + nvc = vrts[1] - vrts[0]; + nvd = vrts[2] - vrts[0]; - nv = mymodel->navmesh->triangle_normal ({v0, v1, v2}); + nv = mymodel->navmesh->triangle_normal (vrts); // Plot tn at mean location of ti - pos = (v0 + v1 + v2) / 3.0f; + pos = vrts.mean(); // Mesh triangle normals this->computeArrow (pos, (pos + nv * this->scale_factor), clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); From 060e3397456ba49381cb7c003127f11412ab2d21 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 16:03:56 +0000 Subject: [PATCH 22/82] Use latest maths --- maths | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths b/maths index db4ab32e..5ae6a077 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit db4ab32e0bc8b98fbd58ac4eb6289dae4cbf1247 +Subproject commit 5ae6a077999a01ed5c8c5840a995c079a7b01c9c From 25edca4c04b4875bb59858774ad4ead8a27c7e9c Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 16:29:14 +0000 Subject: [PATCH 23/82] A skeleton structure for NavMesh save/load --- mplot/NavMesh.h | 14 +------ mplot/VisualModelBase.h | 87 ++++++++++++++++++----------------------- 2 files changed, 40 insertions(+), 61 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 7b8642f3..ae2267ea 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -25,6 +25,7 @@ #include #include #include +#include namespace mplot { @@ -115,13 +116,6 @@ namespace mplot */ std::vector> vertex = {}; - /*! - * The edges that make up the same triangles as are shown with the parent VisualModel's - * indices & vertexPositions, but in terms of this->vertex. Each edge must be two indices - * in *ascending numerical order*. populated by VisualModel::make_navmesh() - */ - std::set> edges; // This is edges, not half edges - /*! * The vector of half edges in the mesh */ @@ -132,12 +126,6 @@ namespace mplot */ std::vector> triangles = {}; - /*! - * Maps index in vertex to the original parent->indices index. populated by - * VisualModel::make_navmesh() - */ - sm::vvec> vertexidx_to_indices; - //! Holds a copy of the bb of the parent model sm::range> bb; diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 7995538c..277b46ec 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -243,29 +243,12 @@ namespace mplot // Our navigation mesh data struct std::unique_ptr navmesh; - /*! - * Post-process vertices to generate a neighbour relationship mesh. The usual vertices and - * indices may not be useful to help an agent to navigate the surface defined by the - * mesh. This is because vertices may be duplicated at any location, so that adjacent faces - * can have different normals and colours. - * - * To help guide movement across a mesh, it would be useful to have a mesh that always gives - * neighbour relationships. - */ - void make_navmesh() + void build_navmesh() { constexpr bool debug_mn = false; - if constexpr (debug_mn) { std::cout << "make_navmesh: Called" << std::endl; } - - if (this->navmesh) { return; } // already made it - - if (this->flags.test (vm_bools::compute_bb) == false) { - throw std::runtime_error ("make_navmesh requires compute_bb flag to be true"); - } - this->update_bb(); + if constexpr (debug_mn) { std::cout << __func__ << " called" << std::endl; } - // Create a new navmesh - this->navmesh = std::make_unique(); + if (!this->navmesh) { return; } // Copy the bounding box navmesh->bb = this->bb; @@ -292,14 +275,17 @@ namespace mplot // Make inverse of equiv to translate from original (indices, vertexPositions) index to // new topographic mesh index sm::vvec navmesh_idx (vps, 0); - navmesh->vertexidx_to_indices.resize (equiv.size()); + + // Maps index in vertex to the original parent->indices index. Was originally a member + // of NavMesh + sm::vvec> vertexidx_to_indices (equiv.size()); uint32_t vcount = 0; i = 0; for (auto eqs : equiv) { vcount += eqs.second.size(); - navmesh->vertexidx_to_indices[i].resize (eqs.second.size()); - std::copy (eqs.second.begin(), eqs.second.end(), navmesh->vertexidx_to_indices[i].begin()); + vertexidx_to_indices[i].resize (eqs.second.size()); + std::copy (eqs.second.begin(), eqs.second.end(), vertexidx_to_indices[i].begin()); for (auto ev : eqs.second) { if constexpr (debug_mn) { std::cout << "make_navmesh: set navmesh_idx[" << ev << "] = " << i << std::endl; } navmesh_idx[ev] = i; @@ -320,34 +306,9 @@ namespace mplot for (auto eq : equiv) { navmesh->vertex[i++] = { (*vp)[eq.first], std::numeric_limits::max() }; } - // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. for (uint32_t i = 0; i < this->indices.size(); i += 3) { - // Each three entries in indices is a triangle containing 3 edges. NB: Edges must be listed in ascending order! - std::array e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]] }; - if (e[0] > e[1]) { - uint32_t t = e[0]; - e[0] = e[1]; - e[1] = t; - } - navmesh->edges.insert (e); - - e = { navmesh_idx[indices[i]], navmesh_idx[indices[i + 2]] }; - if (e[0] > e[1]) { - uint32_t t = e[0]; - e[0] = e[1]; - e[1] = t; - } - navmesh->edges.insert (e); - - e = { navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]] }; - if (e[0] > e[1]) { - uint32_t t = e[0]; - e[0] = e[1]; - e[1] = t; - } - navmesh->edges.insert (e); constexpr uint32_t mlines = 10000; // Add three halfedges for the triangle @@ -420,6 +381,36 @@ namespace mplot navmesh->compute_neighbour_relations(); // finds the halfedge twins } + /*! + * Post-process vertices to generate a neighbour relationship mesh. The usual vertices and + * indices may not be useful to help an agent to navigate the surface defined by the + * mesh. This is because vertices may be duplicated at any location, so that adjacent faces + * can have different normals and colours. + * + * To help guide movement across a mesh, it would be useful to have a mesh that always gives + * neighbour relationships. + */ + void make_navmesh() + { + if (this->navmesh) { return; } // already made it + + if (this->flags.test (vm_bools::compute_bb) == false) { + throw std::runtime_error ("make_navmesh requires compute_bb flag to be true"); + } + this->update_bb(); + + // Create a new navmesh + this->navmesh = std::make_unique(); + + // Have we got a saved one? + bool have_file_matching_this_visualmodel = false; + if (have_file_matching_this_visualmodel) { + // this->navmesh->load (filename); + } else { + this->build_navmesh(); + } + } + /** * End neighbour vertex mesh code */ From 17450ef80f5140773bd4b974d123ee0ce389ed77 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 16:52:47 +0000 Subject: [PATCH 24/82] Adds a visualmodel hash function and navmesh load/save skeletons --- mplot/NavMesh.h | 10 ++++++++++ mplot/VisualModelBase.h | 25 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index ae2267ea..2ec9f3d1 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -141,6 +141,16 @@ namespace mplot */ bool stabilised = false; + void save (const std::string& filename) const + { + std::cout << "FIXME: Save to " << filename << std::endl; + } + + void load (const std::string& filename) + { + std::cout << "FIXME: Load from file " << filename << std::endl; + } + /*! * Return index of this->vertex that is closest to scene_coord. Can use vertexidx_to_indices * to find the indices into vertexPositions and vertexNormals that this index in the diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 277b46ec..07e8339c 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -220,6 +220,25 @@ namespace mplot this->indices.reserve (6u * n_vertices); } + // Make a hash of vertexPositions, etc as an identifier for this model + std::size_t hash() const + { + std::size_t h = std::hash{}(this->vertexPositions[0]); + for (std::size_t i = 1u; i < this->vertexPositions.size(); ++i) { + h ^= std::hash{}(this->vertexPositions[i]); + } + for (std::size_t i = 0u; i < this->vertexNormals.size(); ++i) { + h ^= std::hash{}(this->vertexNormals[i]); + } + for (std::size_t i = 0u; i < this->vertexColors.size(); ++i) { + h ^= std::hash{}(this->vertexColors[i]); + } + for (std::size_t i = 0u; i < this->indices.size(); ++i) { + h ^= std::hash{}(this->vertexColors[i]); + } + return h; + } + // Get a single position from vertexPositions, using the index into the vector // interpretation of vertexPositions sm::vec get_position (const uint32_t vec_idx) const @@ -403,11 +422,15 @@ namespace mplot this->navmesh = std::make_unique(); // Have we got a saved one? + uint64_t h = this->hash(); + std::string filename = std::string("navmesh_") + std::to_string (h) + ".h5"; + std::cout << "Check for saved navmesh in " << filename << std::endl; bool have_file_matching_this_visualmodel = false; if (have_file_matching_this_visualmodel) { - // this->navmesh->load (filename); + this->navmesh->load (filename); } else { this->build_navmesh(); + this->navmesh->save (filename); } } From eb84debecbd6018169c2e31ad1890d9c55274f47 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 22:46:25 +0000 Subject: [PATCH 25/82] Removes stabilised a) it doesn't work as planned b) it should be an arg to compute_mesh_movement --- mplot/NavMesh.h | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 2ec9f3d1..be3908dc 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -134,13 +134,6 @@ namespace mplot //const mesh::halfedge<>* ti0 = nullptr; uint32_t ti0 = std::numeric_limits::max(); - /*! - * Stabilisation flag: if true, no rotation is applied when moving over a triangle boundary - * in NavMesh::compute_mesh_movement. If false, then a rotation about the triangle boundary - * is made. - */ - bool stabilised = false; - void save (const std::string& filename) const { std::cout << "FIXME: Save to " << filename << std::endl; @@ -1175,9 +1168,7 @@ namespace mplot if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); } // Compute the reorientation due to the requested movement. - float rotn_angle = 0.0f; - // Rotate by the angle between the normals (if stabilised is false). I think this is constrained to be <= pi - if (stabilised == false) { rotn_angle = tn0.angle (_tn, cd.tri_edge); } + float rotn_angle = tn0.angle (_tn, cd.tri_edge); // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } sm::mat reorient_model; // reorientation transformation in sf From 1127b7089b2d155eac1f566bd960da41dcf752da Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 3 Feb 2026 23:08:06 +0000 Subject: [PATCH 26/82] Adds hdfsave to NavMesh --- examples/CMakeLists.txt | 4 +++- mplot/NavMesh.h | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8168f544..f8161de3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -393,8 +393,10 @@ if(NOT APPLE) target_link_libraries(geodesic_ce OpenGL::GL glfw Freetype::Freetype) endif() +if(HDF5_FOUND) add_executable(model_crawler model_crawler.cpp) -target_link_libraries(model_crawler OpenGL::GL glfw Freetype::Freetype) +target_link_libraries(model_crawler OpenGL::GL glfw Freetype::Freetype ${HDF5_C_LIBRARIES}) +endif() add_executable(tri tri.cpp) target_link_libraries(tri OpenGL::GL glfw Freetype::Freetype) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index be3908dc..cb470f4d 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -136,7 +136,39 @@ namespace mplot void save (const std::string& filename) const { - std::cout << "FIXME: Save to " << filename << std::endl; + std::cout << "Save NavMesh to " << filename << std::endl; + sm::hdfdata d (filename, std::ios::out | std::ios::trunc); + + sm::vvec> p = {}; + sm::vvec hi = {}; + for (auto v : this->vertex) { + p.push_back (v.p); + hi.push_back (v.hi); + } + d.add_contained_vals ("/vertex_p", p); + d.add_contained_vals ("/vertex_hi", hi); + + sm::vvec> vi = {}; + sm::vvec twin = {}; + sm::vvec next = {}; + sm::vvec prev = {}; + for (auto he : this->halfedges) { + vi.push_back (he.vi); + twin.push_back (he.twin); + next.push_back (he.next); + prev.push_back (he.prev); + } + d.add_contained_vals ("/halfedges_vi", vi); + d.add_contained_vals ("/halfedges_twin", twin); + d.add_contained_vals ("/halfedges_next", next); + d.add_contained_vals ("/halfedges_prev", prev); + + hi.clear(); + for (auto t : this->triangles) { hi.push_back (t.hi); } + d.add_contained_vals ("/triangles_hi", hi); + + d.add_contained_vals ("/bb_min", bb.min); + d.add_contained_vals ("/bb_max", bb.max); } void load (const std::string& filename) From 2ce8264d755e08b30ca0e30388eb6d683f61bb1a Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 4 Feb 2026 12:42:08 +0000 Subject: [PATCH 27/82] Uses new binary_read/write to save data --- examples/CMakeLists.txt | 4 +- maths | 2 +- mplot/NavMesh.h | 88 ++++++++++++++++++++++++++++------------- mplot/VisualModelBase.h | 7 +++- 4 files changed, 69 insertions(+), 32 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f8161de3..8168f544 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -393,10 +393,8 @@ if(NOT APPLE) target_link_libraries(geodesic_ce OpenGL::GL glfw Freetype::Freetype) endif() -if(HDF5_FOUND) add_executable(model_crawler model_crawler.cpp) -target_link_libraries(model_crawler OpenGL::GL glfw Freetype::Freetype ${HDF5_C_LIBRARIES}) -endif() +target_link_libraries(model_crawler OpenGL::GL glfw Freetype::Freetype) add_executable(tri tri.cpp) target_link_libraries(tri OpenGL::GL glfw Freetype::Freetype) diff --git a/maths b/maths index 5ae6a077..a26d143b 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 5ae6a077999a01ed5c8c5840a995c079a7b01c9c +Subproject commit a26d143bccac11177c0b6f131488e33b22479acc diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index cb470f4d..b8b5388e 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include namespace mplot { @@ -137,43 +137,77 @@ namespace mplot void save (const std::string& filename) const { std::cout << "Save NavMesh to " << filename << std::endl; - sm::hdfdata d (filename, std::ios::out | std::ios::trunc); - sm::vvec> p = {}; - sm::vvec hi = {}; + std::ofstream fout (filename, std::ios::binary | std::ios::out | std::ios::trunc); + if (!fout.is_open()) { + std::cerr << "NavMesh::save: Failed to open " << filename << " for writing, continue\n"; + return; + } + // fout is open + uint64_t vertex_sz = this->vertex.size(); + uint64_t halfedges_sz = this->halfedges.size(); + uint64_t triangles_sz = this->triangles.size(); + + // Write sizes at head of file, as the first thing + sm::util::binary_write (fout, vertex_sz); + sm::util::binary_write (fout, halfedges_sz); + sm::util::binary_write (fout, triangles_sz); // 3 * 8 = 24 bytes + + // Write the bb range next. + sm::util::binary_write (fout, this->bb.min); + sm::util::binary_write (fout, this->bb.max); // 2 * 3 * 4 = 24 bytes + + // Now loop for (auto v : this->vertex) { - p.push_back (v.p); - hi.push_back (v.hi); + sm::util::binary_write (fout, v.p); + sm::util::binary_write (fout, v.hi); // 3 * 4 + 4 = 16 bytes per line } - d.add_contained_vals ("/vertex_p", p); - d.add_contained_vals ("/vertex_hi", hi); - sm::vvec> vi = {}; - sm::vvec twin = {}; - sm::vvec next = {}; - sm::vvec prev = {}; for (auto he : this->halfedges) { - vi.push_back (he.vi); - twin.push_back (he.twin); - next.push_back (he.next); - prev.push_back (he.prev); + sm::util::binary_write (fout, he.vi); + sm::util::binary_write (fout, he.twin); + sm::util::binary_write (fout, he.next); + sm::util::binary_write (fout, he.prev); // 5 * 4 = 20 bytes per line } - d.add_contained_vals ("/halfedges_vi", vi); - d.add_contained_vals ("/halfedges_twin", twin); - d.add_contained_vals ("/halfedges_next", next); - d.add_contained_vals ("/halfedges_prev", prev); - hi.clear(); - for (auto t : this->triangles) { hi.push_back (t.hi); } - d.add_contained_vals ("/triangles_hi", hi); - - d.add_contained_vals ("/bb_min", bb.min); - d.add_contained_vals ("/bb_max", bb.max); + for (auto t : this->triangles) { sm::util::binary_write (fout, t.hi); } } void load (const std::string& filename) { - std::cout << "FIXME: Load from file " << filename << std::endl; + std::cout << "Load NavMesh from " << filename << std::endl; + + std::ifstream fin (filename, std::ios::binary | std::ios::in); + if (!fin.is_open()) { throw std::runtime_error ("NavMesh::load: Failed to open file"); } + + uint64_t vertex_sz = 0; + uint64_t halfedges_sz = 0; + uint64_t triangles_sz = 0; + + sm::util::binary_read (fin, vertex_sz); + sm::util::binary_read (fin, halfedges_sz); + sm::util::binary_read (fin, triangles_sz); // 3 * 8 = 24 bytes + + sm::util::binary_read (fin, this->bb.min); + sm::util::binary_read (fin, this->bb.max); + + this->vertex.resize (vertex_sz); + this->halfedges.resize (halfedges_sz); + this->triangles.resize (triangles_sz); + + for (auto& v : this->vertex) { + sm::util::binary_read (fin, v.p); + sm::util::binary_read (fin, v.hi); // 3 * 4 + 4 = 16 bytes per line + } + + for (auto& he : this->halfedges) { + sm::util::binary_read (fin, he.vi); + sm::util::binary_read (fin, he.twin); + sm::util::binary_read (fin, he.next); + sm::util::binary_read (fin, he.prev); // 5 * 4 = 20 bytes per line + } + + for (auto& t : this->triangles) { sm::util::binary_read (fin, t.hi); } } /*! diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 07e8339c..2785575d 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -423,9 +423,14 @@ namespace mplot // Have we got a saved one? uint64_t h = this->hash(); - std::string filename = std::string("navmesh_") + std::to_string (h) + ".h5"; + std::string filename = std::string("navmesh_") + std::to_string (h); std::cout << "Check for saved navmesh in " << filename << std::endl; + bool have_file_matching_this_visualmodel = false; + if (mplot::tools::fileExists (filename)) { + have_file_matching_this_visualmodel = true; + } + if (have_file_matching_this_visualmodel) { this->navmesh->load (filename); } else { From 043bd2c73b9ce6f13121cfe66faa739cb9f5f004 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 4 Feb 2026 12:52:32 +0000 Subject: [PATCH 28/82] has the geometry only --- mplot/VisualModelBase.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 2785575d..5df8c102 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -220,7 +220,8 @@ namespace mplot this->indices.reserve (6u * n_vertices); } - // Make a hash of vertexPositions, etc as an identifier for this model + // Make a hash of vertexPositions, etc as an identifier for this model. The hash identifies + // the model's mesh geometry for NavMesh and so the vertexColors are not important. std::size_t hash() const { std::size_t h = std::hash{}(this->vertexPositions[0]); @@ -230,9 +231,6 @@ namespace mplot for (std::size_t i = 0u; i < this->vertexNormals.size(); ++i) { h ^= std::hash{}(this->vertexNormals[i]); } - for (std::size_t i = 0u; i < this->vertexColors.size(); ++i) { - h ^= std::hash{}(this->vertexColors[i]); - } for (std::size_t i = 0u; i < this->indices.size(); ++i) { h ^= std::hash{}(this->vertexColors[i]); } From c21d1dd9029b37f05d99abb913fda1cd5c726c2b Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 4 Feb 2026 13:37:13 +0000 Subject: [PATCH 29/82] Refactoring mostly --- maths | 2 +- mplot/NavMesh.h | 122 ++++++++++++++++++++-------------------- mplot/NormalsVisual.h | 6 +- mplot/VisualModelBase.h | 57 +++++++++---------- mplot/tools.h | 14 +++++ 5 files changed, 106 insertions(+), 95 deletions(-) diff --git a/maths b/maths index a26d143b..74fe7926 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit a26d143bccac11177c0b6f131488e33b22479acc +Subproject commit 74fe7926cd94489c199efe77840de4e211f559d1 diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index b8b5388e..bbe29d40 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -119,7 +119,7 @@ namespace mplot /*! * The vector of half edges in the mesh */ - std::vector> halfedges = {}; + std::vector> halfedge = {}; /*! * Triangle mesh faces. populated by VisualModel::make_navmesh() @@ -145,12 +145,12 @@ namespace mplot } // fout is open uint64_t vertex_sz = this->vertex.size(); - uint64_t halfedges_sz = this->halfedges.size(); + uint64_t halfedge_sz = this->halfedge.size(); uint64_t triangles_sz = this->triangles.size(); // Write sizes at head of file, as the first thing sm::util::binary_write (fout, vertex_sz); - sm::util::binary_write (fout, halfedges_sz); + sm::util::binary_write (fout, halfedge_sz); sm::util::binary_write (fout, triangles_sz); // 3 * 8 = 24 bytes // Write the bb range next. @@ -163,7 +163,7 @@ namespace mplot sm::util::binary_write (fout, v.hi); // 3 * 4 + 4 = 16 bytes per line } - for (auto he : this->halfedges) { + for (auto he : this->halfedge) { sm::util::binary_write (fout, he.vi); sm::util::binary_write (fout, he.twin); sm::util::binary_write (fout, he.next); @@ -181,18 +181,18 @@ namespace mplot if (!fin.is_open()) { throw std::runtime_error ("NavMesh::load: Failed to open file"); } uint64_t vertex_sz = 0; - uint64_t halfedges_sz = 0; + uint64_t halfedge_sz = 0; uint64_t triangles_sz = 0; sm::util::binary_read (fin, vertex_sz); - sm::util::binary_read (fin, halfedges_sz); + sm::util::binary_read (fin, halfedge_sz); sm::util::binary_read (fin, triangles_sz); // 3 * 8 = 24 bytes sm::util::binary_read (fin, this->bb.min); sm::util::binary_read (fin, this->bb.max); this->vertex.resize (vertex_sz); - this->halfedges.resize (halfedges_sz); + this->halfedge.resize (halfedge_sz); this->triangles.resize (triangles_sz); for (auto& v : this->vertex) { @@ -200,7 +200,7 @@ namespace mplot sm::util::binary_read (fin, v.hi); // 3 * 4 + 4 = 16 bytes per line } - for (auto& he : this->halfedges) { + for (auto& he : this->halfedge) { sm::util::binary_read (fin, he.vi); sm::util::binary_read (fin, he.twin); sm::util::binary_read (fin, he.next); @@ -247,11 +247,11 @@ namespace mplot uint32_t hi = tri_hi; do { //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; - if (this->halfedges[hi].vi[0] < this->vertex.size()) { - trivert[i] = this->vertex[this->halfedges[hi].vi[0]].p; + if (this->halfedge[hi].vi[0] < this->vertex.size()) { + trivert[i] = this->vertex[this->halfedge[hi].vi[0]].p; } ++i; - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != tri_hi); return trivert; } @@ -269,11 +269,11 @@ namespace mplot uint32_t hi = tri_hi; do { //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; - if (this->halfedges[hi].vi[0] < this->vertex.size()) { - trivert[i] = (transform * this->vertex[this->halfedges[hi].vi[0]].p).less_one_dim(); + if (this->halfedge[hi].vi[0] < this->vertex.size()) { + trivert[i] = (transform * this->vertex[this->halfedge[hi].vi[0]].p).less_one_dim(); } ++i; - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != tri_hi); return trivert; } @@ -289,13 +289,13 @@ namespace mplot // Retrieve the halfedge as a vector, transformed by the given transform sm::vec edge_vector (uint32_t hi, const sm::mat& transform) const { - const sm::vec v0 = (transform * this->vertex[this->halfedges[hi].vi[0]].p).less_one_dim(); - const sm::vec v1 = (transform * this->vertex[this->halfedges[hi].vi[1]].p).less_one_dim(); + const sm::vec v0 = (transform * this->vertex[this->halfedge[hi].vi[0]].p).less_one_dim(); + const sm::vec v1 = (transform * this->vertex[this->halfedge[hi].vi[1]].p).less_one_dim(); return v1 - v0; } // Find all the neighbours of triangle *vertex* index a. - // \return vector of halfedges indices + // \return vector of halfedge indices std::vector find_neighbours (uint32_t a) const { @@ -304,7 +304,7 @@ namespace mplot do { // hi emanates from the vertex, so return it. rtn.push_back (hi); - hi = this->halfedges[this->halfedges[hi].next].twin; + hi = this->halfedge[this->halfedge[hi].next].twin; if (hi == std::numeric_limits::max()) { std::cout << "Warning: twins need to be set up to find_neighbours\n"; break; @@ -323,10 +323,11 @@ namespace mplot */ void compute_neighbour_relations () { - uint32_t sz = this->halfedges.size(); - std::cout << "Finding twins for " << sz << " halfedges\n"; + constexpr bool debug_nr = false; + uint32_t sz = this->halfedge.size(); + if constexpr (debug_nr) { std::cout << "Finding twins for " << sz << " halfedge\n"; } - // Search a 'band' either side of i first, assuming that neighbour triangles are likely + // Search a 'band' either side of i first, assuming that neighbour faces are likely // to have been nearby in the indices array const uint32_t band = 3 * 1000; @@ -337,10 +338,10 @@ namespace mplot for (uint32_t i = 0; i < sz; ++i) { - const sm::vec& vi = this->halfedges[i].vi; + const sm::vec& vi = this->halfedge[i].vi; - // halfedges[i].twin may already have been set (as we set two twins at a time) - if (this->halfedges[i].twin != std::numeric_limits::max()) { continue; } + // halfedge[i].twin may already have been set (as we set two twins at a time) + if (this->halfedge[i].twin != std::numeric_limits::max()) { continue; } // It's useful to know how long you will have to wait... if (i % 20000u == 0u) { std::cout << ((100.0f * i)/sz) << " percent...\n" << std::endl; } @@ -351,37 +352,37 @@ namespace mplot // First sb to eb, which we hope is most likely to find a twin for (uint32_t j = sb; j < eb; ++j) { if (j == i) { continue; } - const sm::vec& vij = this->halfedges[j].vi; + const sm::vec& vij = this->halfedge[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match - this->halfedges[i].twin = j; - this->halfedges[j].twin = i; + this->halfedge[i].twin = j; + this->halfedge[j].twin = i; break; } } uint32_t wider = 0; - if (this->halfedges[i].twin == std::numeric_limits::max()) { + if (this->halfedge[i].twin == std::numeric_limits::max()) { // Then, if no match, search rest if (sb != 0 && !wider) { wider = 1; } for (uint32_t j = 0; j < sb; ++j) { if (j == i) { continue; } - const sm::vec& vij = this->halfedges[j].vi; + const sm::vec& vij = this->halfedge[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match - this->halfedges[i].twin = j; - this->halfedges[j].twin = i; + this->halfedge[i].twin = j; + this->halfedge[j].twin = i; break; } } } - if (this->halfedges[i].twin == std::numeric_limits::max()) { + if (this->halfedge[i].twin == std::numeric_limits::max()) { if (eb != sz && !wider) { wider = 1; } for (uint32_t j = eb; j < sz; ++j) { if (j == i) { continue; } - const sm::vec& vij = this->halfedges[j].vi; + const sm::vec& vij = this->halfedge[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match - this->halfedges[i].twin = j; - this->halfedges[j].twin = i; + this->halfedge[i].twin = j; + this->halfedge[j].twin = i; break; } } @@ -389,17 +390,19 @@ namespace mplot wider_searches += wider; - if (this->halfedges[i].twin != std::numeric_limits::max()) { - // std::cout << "Twin of " << i << " is " << this->halfedges[i].twin << std::endl; + if (this->halfedge[i].twin != std::numeric_limits::max()) { if (wider) { - twin_meandist += i > this->halfedges[i].twin ? i - this->halfedges[i].twin : this->halfedges[i].twin - i; + twin_meandist += i > this->halfedge[i].twin ? i - this->halfedge[i].twin : this->halfedge[i].twin - i; ++twins; } - } // else halfedges[i] is an edge of the mesh + } // else halfedge[i] is an edge of the mesh + } + if constexpr (debug_nr) { + std::cout << "In " << sz << " halfedge searches, had to widen the search in " + << (100.0 * wider_searches) / sz << " percent\n"; + std::cout << "Mean wider twin search distance (in array elements) was " + << static_cast(twin_meandist) / twins << "\n"; } - - std::cout << "In " << sz << " halfedge searches, had to widen the search in " << (100.0 * wider_searches) / sz << " percent\n"; - std::cout << "Mean wider twin search distance (in array elements) was " << static_cast(twin_meandist) / twins << "\n"; } /* @@ -433,7 +436,7 @@ namespace mplot // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml != std::numeric_limits::max()) { - std::cout << "Passing ti_ml to trangle_vertices: with he->vi = " << this->halfedges[ti_ml].vi << std::endl; + std::cout << "Passing ti_ml to trangle_vertices: with he->vi = " << this->halfedge[ti_ml].vi << std::endl; sm::vec, 3> v = this->triangle_vertices (ti_ml); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { @@ -453,12 +456,12 @@ namespace mplot for (auto tri : this->triangles) { if constexpr (debug_ftc) { - std::cout << "this->halfedges["<< 0 << "] " << (&this->halfedges[0]) << " contains: vi:" - << this->halfedges[0].vi - << ", twin:" << this->halfedges[0].twin - << ", next:" << this->halfedges[0].next - << ", prev:" << this->halfedges[0].prev << std::endl; - std::cout << "CF. Passing tri.he " << tri.hi << " to triangle_vertices(): with he->vi = " << this->halfedges[tri.hi].vi << std::endl; + std::cout << "this->halfedge["<< 0 << "] " << (&this->halfedge[0]) << " contains: vi:" + << this->halfedge[0].vi + << ", twin:" << this->halfedge[0].twin + << ", next:" << this->halfedge[0].next + << ", prev:" << this->halfedge[0].prev << std::endl; + std::cout << "CF. Passing tri.he " << tri.hi << " to triangle_vertices(): with he->vi = " << this->halfedge[tri.hi].vi << std::endl; } sm::vec, 3> v = this->triangle_vertices (tri.hi); @@ -485,9 +488,8 @@ namespace mplot if (sm::geometry::ray_point_intersection (this->vertex[vi].p, vstart, -vertex_n)) { float d = (this->vertex[vi].p - vstart).sos(); if (d < isect_d && d < vdir.sos()) { - std::cout << "Register vertex triangle_crossing\n"; + if constexpr (debug_ftc) { std::cout << "Register vertex triangle_crossing\n"; } isect_p = this->vertex[vi].p; - // now have halfedge specifying a triangle. Could attempt to look up to face, but could just return halfedge? isect_ti = this->vertex[vi].hi; isect_d = d; } @@ -751,7 +753,7 @@ namespace mplot } ++a; - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != tri && a < 3); @@ -1040,7 +1042,7 @@ namespace mplot sm::vec vertex_n = this->find_vertex_normal (hi, model_to_scene); vertex_n.renormalize(); // How to figure out tv_sf[i]? - // sm::vec v_sf = (model_to_scane * this->vertex[halfedges[hi].vi[0]]).less_one_dim(); + // sm::vec v_sf = (model_to_scane * this->vertex[halfedge[hi].vi[0]]).less_one_dim(); // or, if tv_sf[0] is the start of ti0, then we can do this: if (sm::geometry::ray_point_intersection (tv_sf[i], camloc_sf + (vertex_n / 2.0f), -vertex_n)) { if constexpr (debug_move) { @@ -1052,7 +1054,7 @@ namespace mplot isect = true; } ++i; - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != this->ti0); } @@ -1069,7 +1071,7 @@ namespace mplot // (this can occur when moving along an edge) uint32_t hi = this->ti0; do { - uint32_t twin = this->halfedges[hi].twin; + uint32_t twin = this->halfedge[hi].twin; if (twin != std::numeric_limits::max()) { // Test to see if start location was inside this twin sm::vec, 3> tv_lf = this->triangle_vertices (twin, model_to_scene); @@ -1090,7 +1092,7 @@ namespace mplot break; // out of do-while } } - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != this->ti0); if (isect == false) { @@ -1216,7 +1218,7 @@ namespace mplot _ti = detected_newtri; } else { // new triangle is the twin of the crossed edge - _ti = this->halfedges[cd.halfedge].twin; + _ti = this->halfedge[cd.halfedge].twin; if constexpr (debug_move) { std::cout << "find triangle across edge: halfedge " << cd.halfedge << " gives the neighbour, which is its twin: " << _ti << std::endl; } @@ -1333,7 +1335,7 @@ namespace mplot std::set neighbours_tested; uint32_t hi = this->ti0; do { - uint32_t twin = this->halfedges[hi].twin; + uint32_t twin = this->halfedge[hi].twin; if (twin != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); @@ -1372,7 +1374,7 @@ namespace mplot } } - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != this->ti0); @@ -1416,7 +1418,7 @@ namespace mplot } // else end is not in one-neighbour, and neither is start. } } - hi = this->halfedges[hi].next; + hi = this->halfedge[hi].next; } while (hi != this->ti0); } diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 88ae176c..6f50aac7 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -60,16 +60,16 @@ namespace mplot sm::vec nvd = {}; sm::vec pos = {}; - for (auto ti : mymodel->navmesh->triangles) { + for (auto t : mymodel->navmesh->triangles) { - sm::vec, 3> vrts = mymodel->navmesh->triangle_vertices (ti.hi); + sm::vec, 3> vrts = mymodel->navmesh->triangle_vertices (t.hi); nvc = vrts[1] - vrts[0]; nvd = vrts[2] - vrts[0]; nv = mymodel->navmesh->triangle_normal (vrts); - // Plot tn at mean location of ti + // Plot tn at mean location of f pos = vrts.mean(); // Mesh triangle normals this->computeArrow (pos, (pos + nv * this->scale_factor), diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 5df8c102..4408aac2 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -48,6 +48,7 @@ #include #include +#include #include namespace mplot @@ -329,42 +330,42 @@ namespace mplot constexpr uint32_t mlines = 10000; // Add three halfedges for the triangle - uint32_t hesz = navmesh->halfedges.size(); - navmesh->halfedges.resize (hesz + 3, {}); + uint32_t hesz = navmesh->halfedge.size(); + navmesh->halfedge.resize (hesz + 3, {}); uint32_t he0 = hesz; uint32_t he1 = hesz + 1; uint32_t he2 = hesz + 2; if constexpr (debug_mn) { if (hesz < mlines) { - std::cout << "setting halfedges["<halfedges[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, std::numeric_limits::max(), he1, he2 }; - navmesh->halfedges[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, std::numeric_limits::max(), he2, he0 }; - navmesh->halfedges[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, std::numeric_limits::max(), he0, he1 }; + navmesh->halfedge[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, std::numeric_limits::max(), he1, he2 }; + navmesh->halfedge[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, std::numeric_limits::max(), he2, he0 }; + navmesh->halfedge[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, std::numeric_limits::max(), he0, he1 }; if constexpr (debug_mn) { if (hesz < mlines) { - std::cout << "halfedges["<< hesz << "] contains: vi:" - << navmesh->halfedges[hesz].vi - << ", twin:" << navmesh->halfedges[hesz].twin - << ", next:" << navmesh->halfedges[hesz].next - << ", prev:" << navmesh->halfedges[hesz].prev << std::endl; + std::cout << "halfedge["<< hesz << "] contains: vi:" + << navmesh->halfedge[hesz].vi + << ", twin:" << navmesh->halfedge[hesz].twin + << ", next:" << navmesh->halfedge[hesz].next + << ", prev:" << navmesh->halfedge[hesz].prev << std::endl; } } - // Direct population of triangles. Three indices and a 4th number to hold flags (with bit0 meaning edge-triangle) - mesh::face<> t = { he0 }; // Face will just be the first half edge. + // A face contains just the first half edge index + mesh::face<> t = { he0 }; // The normal vector for this triangle could be obtained from the mesh normals, but // we can't trust them (though they're easy to get, as we're dealing with indices @@ -383,17 +384,18 @@ namespace mplot sm::vec n = nx.cross (ny); n.renormalize(); - // Check rotational sense of triangles? + // Check rotational sense of triangles if (n.dot (tn) < 0.0f) { - //throw std::runtime_error ("Need to swap triangle order\n"); - // Swap first and last half edge? - navmesh->halfedges[hesz].vi.rotate(); - navmesh->halfedges[hesz + 1].vi.rotate(); - navmesh->halfedges[hesz + 2].vi.rotate(); + // Swap first and last half edge + navmesh->halfedge[hesz].vi.rotate(); + navmesh->halfedge[hesz + 1].vi.rotate(); + navmesh->halfedge[hesz + 2].vi.rotate(); } navmesh->triangles.push_back (t); } - if constexpr (debug_mn) { std::cout << "make_navmesh: Created triangles (" << navmesh->halfedges.size() << " halfedges)" << std::endl; } + if constexpr (debug_mn) { + std::cout << "make_navmesh: Created triangles (" << navmesh->halfedge.size() << " halfedges)" << std::endl; + } navmesh->compute_neighbour_relations(); // finds the halfedge twins } @@ -419,17 +421,10 @@ namespace mplot // Create a new navmesh this->navmesh = std::make_unique(); - // Have we got a saved one? + // Have we got a pre-computed navmesh file for the halfedge twin relationships? uint64_t h = this->hash(); - std::string filename = std::string("navmesh_") + std::to_string (h); - std::cout << "Check for saved navmesh in " << filename << std::endl; - - bool have_file_matching_this_visualmodel = false; + std::string filename = mplot::tools::getTmpPath() + std::string("navmesh_") + std::to_string (h); if (mplot::tools::fileExists (filename)) { - have_file_matching_this_visualmodel = true; - } - - if (have_file_matching_this_visualmodel) { this->navmesh->load (filename); } else { this->build_navmesh(); diff --git a/mplot/tools.h b/mplot/tools.h index 8966c0b2..11a95e2c 100644 --- a/mplot/tools.h +++ b/mplot/tools.h @@ -399,6 +399,20 @@ namespace mplot return std::make_pair (fpath, fname); } + // Get the correct path to the temporary file store directory. /tmp/ on Unix systems. + std::string getTmpPath() + { +#ifdef _MSC_VER + char* userprofile = getenv ("USERPROFILE"); + std::string uppath(""); + if (userprofile != nullptr) { uppath = std::string (userprofile); } + std::string tmp = uppath + "\\AppData\\Local\\Temp\\"; +#else + std::string tmp = "/tmp/"; +#endif + return tmp; + } + /*! * Given a path like /path/to/file.ext or just file.ext in str, remove the file * suffix. From 07b107d694b4674c71580386520fdffdb35b763f Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 4 Feb 2026 14:25:05 +0000 Subject: [PATCH 30/82] Minor - be sure to show expected filename on stdout. Some debug messages --- mplot/NavMesh.h | 29 ++++++++++++++++++++++++----- mplot/VisualModelBase.h | 13 +++++++------ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index bbe29d40..81d33ec5 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -436,7 +436,6 @@ namespace mplot // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml != std::numeric_limits::max()) { - std::cout << "Passing ti_ml to trangle_vertices: with he->vi = " << this->halfedge[ti_ml].vi << std::endl; sm::vec, 3> v = this->triangle_vertices (ti_ml); auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { @@ -447,12 +446,32 @@ namespace mplot isect_d = d; } } - } - if (isect_d != std::numeric_limits::max()) { - // we found it already! - return { isect_p, isect_ti }; + if (isect_d != std::numeric_limits::max()) { + // we found it in the first triangle! + return { isect_p, isect_ti }; + } + + // Next, test the neighbours of ti_ml + std::vector nbs = this->find_neighbours (ti_ml); + for (uint32_t nb : nbs) { + v = this->triangle_vertices (nb); + auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); + if (isect) { + float d = (p - vstart).sos(); + if (d < vdsos) { + isect_p = p; + isect_ti = ti_ml; + isect_d = d; + } + } + + if (isect_d != std::numeric_limits::max()) { + return { isect_p, isect_ti }; + } + } } + // Fall back to testing ALL the triangles... for (auto tri : this->triangles) { if constexpr (debug_ftc) { diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 4408aac2..e529edde 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -282,11 +282,11 @@ namespace mplot for (auto e : equiv_v) { equiv[*e.second.begin()] = e.second; } if constexpr (debug_mn) { for (auto e : equiv) { - std::cout << "make_navmesh: equiv[" << e.first << "] = "; + std::cout << "build_navmesh: equiv[" << e.first << "] = "; for (auto idx : e.second) { std::cout << idx << ","; } std::cout << std::endl; } - std::cout << "make_navmesh: Populated equiv which has " + std::cout << "build_navmesh: Populated equiv which has " << equiv.size() << " vvecs" << std::endl; } @@ -305,15 +305,15 @@ namespace mplot vertexidx_to_indices[i].resize (eqs.second.size()); std::copy (eqs.second.begin(), eqs.second.end(), vertexidx_to_indices[i].begin()); for (auto ev : eqs.second) { - if constexpr (debug_mn) { std::cout << "make_navmesh: set navmesh_idx[" << ev << "] = " << i << std::endl; } + if constexpr (debug_mn) { std::cout << "build_navmesh: set navmesh_idx[" << ev << "] = " << i << std::endl; } navmesh_idx[ev] = i; } ++i; } - if constexpr (debug_mn) { std::cout << "make_navmesh: Created equiv inverse" << std::endl; } + if constexpr (debug_mn) { std::cout << "build_navmesh: Created equiv inverse" << std::endl; } if (vcount != vps) { - std::cout << "make_navmesh: WARNING: Vertex count from equiv is " << vcount + std::cout << "build_navmesh: WARNING: Vertex count from equiv is " << vcount << " which should (but does not) equal " << vps << std::endl; } @@ -394,7 +394,7 @@ namespace mplot navmesh->triangles.push_back (t); } if constexpr (debug_mn) { - std::cout << "make_navmesh: Created triangles (" << navmesh->halfedge.size() << " halfedges)" << std::endl; + std::cout << "build_navmesh: Created triangles (" << navmesh->halfedge.size() << " halfedges)" << std::endl; } navmesh->compute_neighbour_relations(); // finds the halfedge twins @@ -427,6 +427,7 @@ namespace mplot if (mplot::tools::fileExists (filename)) { this->navmesh->load (filename); } else { + std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); this->navmesh->save (filename); } From b0b06d096a101869fec2ebe818e274e5ad60468d Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 4 Feb 2026 16:49:51 +0000 Subject: [PATCH 31/82] Change NavException. Debug output change. Realised I need to fill halfedge in for boundary. --- mplot/NavMesh.h | 50 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 81d33ec5..540f19b6 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -61,13 +61,12 @@ namespace mplot }; } - // Exception that returns triangles that were near the location of the error + // Exception that (used to) return triangles that were near the location of the error struct NavException : public std::exception { enum class type : uint32_t { generic, no_intersection, zero_mv, mv_to_vertex, undetected_crossing, nan_mv, off_edge }; NavException (const type _type) : m_type(_type) {} - NavException (const type _type, const std::vector& t) : m_type(_type) { this->tris = t; } using std::exception::what; const char* what() @@ -99,8 +98,6 @@ namespace mplot } // Error type determines message generated type m_type = type::generic; - // Triangles of interest (as indices into NavMesh::halfedges) - std::vector tris; }; /*! @@ -130,8 +127,6 @@ namespace mplot sm::range> bb; //! When navigating, this is the 'current triangle' that you're located over/near - //mesh::face<> ti0; // or could be a halfedge pointer? - //const mesh::halfedge<>* ti0 = nullptr; uint32_t ti0 = std::numeric_limits::max(); void save (const std::string& filename) const @@ -304,15 +299,38 @@ namespace mplot do { // hi emanates from the vertex, so return it. rtn.push_back (hi); - hi = this->halfedge[this->halfedge[hi].next].twin; - if (hi == std::numeric_limits::max()) { - std::cout << "Warning: twins need to be set up to find_neighbours\n"; - break; - } + hi = this->halfedge[this->halfedge[hi].prev].twin; + // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise } while (hi != a); return rtn; } + /* + * After making the neighbour relations from the OpenGL mesh, the last step is to fill in + * the boundary halfedges. Find all halfedges with an unset twin and then start creating the + * new half edges to fill in. + */ + void add_boundary_halfedges() + { +#if 0 + constexpr bool debug_bnd = false; + uint32_t sz = this->halfedge.size(); + + // Create a new vector of boundary halfedges which will then be appended to this->halfedge + std::vector bhe; + + uint32_t j = sz; + for (uint32_t i = 0; i < sz; ++i) { + if (this->halfedge[i].twin == std::numeric_limits::max()) { + // This halfedge does not have a twin. + uint32_t bhe_next = 0; // Figure these out... + uint32_t bhe_prev = 0; + bhe.push_back ({this->halfedge[i].v[1], this->halfedge[i].v[0]}, i, bhe_next, bhe_prev); + } + } +#endif + } + /* * Determine neighbour relations. That means populating a halfedge data structure. Don't * think there's any way around the at-worst O(N^2) computation, so save results into an h5 @@ -321,7 +339,7 @@ namespace mplot * The key is the half-edge data structure. * See: https://jerryyin.info/geometry-processing-algorithms/half-edge/ */ - void compute_neighbour_relations () + void compute_neighbour_relations() { constexpr bool debug_nr = false; uint32_t sz = this->halfedge.size(); @@ -362,7 +380,7 @@ namespace mplot uint32_t wider = 0; if (this->halfedge[i].twin == std::numeric_limits::max()) { - // Then, if no match, search rest + // Then, if no match, search from 0 to sb if (sb != 0 && !wider) { wider = 1; } for (uint32_t j = 0; j < sb; ++j) { if (j == i) { continue; } @@ -376,6 +394,7 @@ namespace mplot } if (this->halfedge[i].twin == std::numeric_limits::max()) { + // If still no match search from eb to sz if (eb != sz && !wider) { wider = 1; } for (uint32_t j = eb; j < sz; ++j) { if (j == i) { continue; } @@ -1364,12 +1383,11 @@ namespace mplot sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; if constexpr (debug_move) { - std::cout << "endis? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; + std::cout << "TN: " << twin << ": isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; } auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in twin " << twin << " / " << tv_nb << std::endl; - std::cout << "End of move " << (endis ? "IS" : "is NOT") << " in that triangle" << std::endl; + std::cout << " Start IN? " << (is ? "Y" : "N") << "End IN? " << (endis ? "Y" : "N") << std::endl; } neighbours_tested.insert (twin); From f179f5b71519d1ab2b5c9dcdbb7a0c13bc60c635 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 5 Feb 2026 12:41:16 +0000 Subject: [PATCH 32/82] Better hash combindation --- mplot/VisualModelBase.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index e529edde..bb0a2f76 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -225,15 +225,15 @@ namespace mplot // the model's mesh geometry for NavMesh and so the vertexColors are not important. std::size_t hash() const { - std::size_t h = std::hash{}(this->vertexPositions[0]); - for (std::size_t i = 1u; i < this->vertexPositions.size(); ++i) { - h ^= std::hash{}(this->vertexPositions[i]); + std::size_t h = 17; + for (std::size_t i = 0u; i < this->vertexPositions.size(); ++i) { + h = (h << 5) - 1 + std::hash{}(this->vertexPositions[i]); } for (std::size_t i = 0u; i < this->vertexNormals.size(); ++i) { - h ^= std::hash{}(this->vertexNormals[i]); + h = (h << 5) - 1 + std::hash{}(this->vertexNormals[i]); } for (std::size_t i = 0u; i < this->indices.size(); ++i) { - h ^= std::hash{}(this->vertexColors[i]); + h = (h << 5) - 1 + std::hash{}(this->indices[i]); } return h; } From 927908ba46f8fcb98d0e74dcadbb847f6ee37c87 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 5 Feb 2026 12:48:25 +0000 Subject: [PATCH 33/82] Corrects ordering of triangles in GridVIsual, triangle mode --- mplot/GridVisual.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mplot/GridVisual.h b/mplot/GridVisual.h index 72ff5fae..1c6056a2 100644 --- a/mplot/GridVisual.h +++ b/mplot/GridVisual.h @@ -493,12 +493,12 @@ namespace mplot // Triangle 1 I ii = ri * dims[0] + ci; this->indices[ind_idx++] = (ii); - this->indices[ind_idx++] = (ii + dims[0] + 1); // NNE this->indices[ind_idx++] = (ii + 1); // NE + this->indices[ind_idx++] = (ii + dims[0] + 1); // NNE // Triangle 2 this->indices[ind_idx++] = (ii); - this->indices[ind_idx++] = (ii + dims[0]); // NN this->indices[ind_idx++] = (ii + dims[0] + 1); // NNE + this->indices[ind_idx++] = (ii + dims[0]); // NN } } } else if (this->grid->get_order() == sm::gridorder::topleft_to_bottomright) { From e5141c0ef689a3662c3cd97f13fc2eb85195691f Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 5 Feb 2026 12:53:23 +0000 Subject: [PATCH 34/82] Corrects clockwiseness of triangles in computeFlatQuad --- mplot/VisualModelBase.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index bb0a2f76..ec5194de 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -1558,11 +1558,11 @@ namespace mplot size_t i0 = this->indices.size(); this->indices.resize (i0 + 6, 0); this->indices[i0++] = this->idx; - this->indices[i0++] = this->idx + 1; this->indices[i0++] = this->idx + 2; + this->indices[i0++] = this->idx + 1; this->indices[i0++] = this->idx; - this->indices[i0++] = this->idx + 2; this->indices[i0++] = this->idx + 3; + this->indices[i0++] = this->idx + 2; this->idx += 4; } From 0de1c8fce5a1b79f4c9d47b77ae6d682eab6316c Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 5 Feb 2026 17:17:31 +0000 Subject: [PATCH 35/82] Thursday work on NavMesh boundary halfedges --- examples/grid_border2.cpp | 27 +++++++++++++++ examples/model_crawler.cpp | 7 ++-- mplot/NavMesh.h | 69 +++++++++++++++++++++++++++++++------- mplot/NormalsVisual.h | 16 +++++++++ mplot/VisualModelBase.h | 2 ++ 5 files changed, 104 insertions(+), 17 deletions(-) diff --git a/examples/grid_border2.cpp b/examples/grid_border2.cpp index e84b1f66..c9b1a2fe 100644 --- a/examples/grid_border2.cpp +++ b/examples/grid_border2.cpp @@ -15,6 +15,7 @@ #include #include #include +#include int main() { @@ -172,6 +173,32 @@ int main() gv->addLabel ("Triangles, border (smaller is as expected)", lblpos, mplot::TextFeatures(0.08f)); gv->finalize(); v.addVisualModel (gv); + + offset[0] += grid.width_of_pixels() * 1.2f; + gv = std::make_unique>(&grid, offset); + v.bindmodel (gv); + gv->gridVisMode = mplot::GridVisMode::Triangles; + gv->setScalarData (&data); + gv->cm.setType (mplot::ColourMapType::Cork); + gv->zScale.do_autoscale = false; + gv->zScale.null_scaling(); + gv->colourScale.do_autoscale = false; + gv->colourScale.compute_scaling (-1, 1); + // Border specific parameters + gv->showborder (false); + gv->addLabel ("Triangles, no border (smaller is as expected)", lblpos, mplot::TextFeatures(0.08f)); + gv->finalize(); + auto gvp = v.addVisualModel (gv); + + // Make a navmesh for this last one + gvp->make_navmesh(); + + // Add a Normals visual for the last one, too + auto nrm = std::make_unique> (gvp); + v.bindmodel (nrm); + nrm->finalize(); + v.addVisualModel (nrm); + v.keepOpen(); return 0; diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 6685fe7f..8764bb10 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -81,11 +81,10 @@ int main (int argc, char** argv) gvp->reinitColours(); // Make the navmesh for the geodesic, this doesn't occur automatically and has to come after finalize() gvp->make_navmesh(); - std::cout << "\nmake_navmesh() returned\n\n"; // We're going to move the coordinate arrows forwards (along its z-axis), so that it 'orbits' float move_step = 0.1f; // 0.075 <= move_step and iterations 6 to fail - [[maybe_unused]] sm::vec mv_ca = sm::vec::uz() * move_step; + sm::vec mv_ca = sm::vec::uz() * move_step; // The viewmatrices have to be passed to mplot::NavMesh::compute_mesh_movement sm::mat ca_view = cap->getViewMatrix(); @@ -103,7 +102,7 @@ int main (int argc, char** argv) // Wait .018 s and also poll for mouse/keyboard events v.waitevents (0.018); -#if 1 + // Compute a new movement over the landscape mesh (the sphere) try { ca_view = gvp->navmesh->compute_mesh_movement (mv_ca, ca_view, sph_view, hoverheight); @@ -114,7 +113,7 @@ int main (int argc, char** argv) std::cout << "Exception navigating mesh at movement count " << move_counter << ": " << e.what() << std::endl; throw e; } -#endif + // Update the viewmatrix of the coord arrows, setting its position within the scene cap->setViewMatrix (ca_view); diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 540f19b6..e245d9fc 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -42,6 +42,7 @@ namespace mplot I twin = std::numeric_limits::max(); // twin half edge I next = std::numeric_limits::max(); // next half edge in face (or hole) I prev = std::numeric_limits::max(); // prev half edge in face (or hole) + I flags = 0; // 0x1: boundary halfedge }; template requires std::is_integral_v @@ -163,6 +164,7 @@ namespace mplot sm::util::binary_write (fout, he.twin); sm::util::binary_write (fout, he.next); sm::util::binary_write (fout, he.prev); // 5 * 4 = 20 bytes per line + sm::util::binary_write (fout, he.flags); // plus 4 } for (auto t : this->triangles) { sm::util::binary_write (fout, t.hi); } @@ -200,6 +202,7 @@ namespace mplot sm::util::binary_read (fin, he.twin); sm::util::binary_read (fin, he.next); sm::util::binary_read (fin, he.prev); // 5 * 4 = 20 bytes per line + sm::util::binary_read (fin, he.flags); } for (auto& t : this->triangles) { sm::util::binary_read (fin, t.hi); } @@ -312,23 +315,63 @@ namespace mplot */ void add_boundary_halfedges() { -#if 0 - constexpr bool debug_bnd = false; - uint32_t sz = this->halfedge.size(); - - // Create a new vector of boundary halfedges which will then be appended to this->halfedge - std::vector bhe; + constexpr uint32_t max = std::numeric_limits::max(); + constexpr bool debug_bnd = true; - uint32_t j = sz; + const uint32_t sz = this->halfedge.size(); + uint32_t j = 0; for (uint32_t i = 0; i < sz; ++i) { - if (this->halfedge[i].twin == std::numeric_limits::max()) { - // This halfedge does not have a twin. - uint32_t bhe_next = 0; // Figure these out... - uint32_t bhe_prev = 0; - bhe.push_back ({this->halfedge[i].v[1], this->halfedge[i].v[0]}, i, bhe_next, bhe_prev); + if (this->halfedge[i].twin == max) { + // This halfedge does not have a twin, walk the boundary from here + const uint32_t j0 = j; // j index at boundary start + uint32_t bprev = max; + uint32_t cur = i; + uint32_t done = 0u; + while (!done) { + if constexpr (debug_bnd) { + std::cout << " Search for boundary from cur = " << cur << std::endl; + } + uint32_t bcand = cur; // bcand starts as an internal halfedge + uint32_t bcandi = max; + uint32_t counter = 0u; + std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; + //uint32_t bcand0 = this->halfedge[this->halfedge[bcand].prev].twin; + //uint32_t bcand0t = max; + do { + bcandi = this->halfedge[bcand].prev; + std::cout << "bcandi: " << bcandi << std::endl; + bcand = this->halfedge[bcandi].twin; // if max, it's a boundary, else it's internal + std::cout << "bcand: " << bcand << std::endl; + if (counter++ > 6) { + std::cout << "Something is wrong; returning\n"; + return; + } + //if (counter == 1) { bcand0t = bcand0; } + + } while (bcand != max // halfedge[i].twin is usually max, so second condition may be sufficient + && bcand != this->halfedge[i].twin + //&& bcand != bcand0t + && bcandi != cur + ); + + this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, bprev, sz + j + 1, 1}); + this->halfedge[cur].twin = sz + j; + + if (bcandi == i) { + this->halfedge[sz + j0].prev = sz + j - 1; + ++done; + } else { + bprev = sz + j; + cur = bcandi; + ++j; + } + } + if constexpr (debug_bnd) { + std::cout << "Added " << (j - j0) << " halfedges to that boundary\n"; + } } } -#endif + if constexpr (debug_bnd) { std::cout << __func__ << " returning\n"; } } /* diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 6f50aac7..287d5a49 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -80,6 +80,22 @@ namespace mplot this->computeArrow (pos, (pos + nvd * scale_factor), clrnd, tube_r, this->arrowhead_prop, cone_r, this->shapesides); } + + // Can also show halfedges from the navmesh + for (auto h : mymodel->navmesh->halfedge) { + + auto p0 = mymodel->navmesh->vertex[h.vi[0]].p; + auto p1 = mymodel->navmesh->vertex[h.vi[1]].p; + if (h.flags) { + // boundary + this->computeArrow (p0, p1, mplot::colour::crimson, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } else { + // internal + this->computeArrow (p0, p1, mplot::colour::dodgerblue2, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + } } }; diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index ec5194de..e368854e 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -398,6 +398,8 @@ namespace mplot } navmesh->compute_neighbour_relations(); // finds the halfedge twins + + navmesh->add_boundary_halfedges(); } /*! From bcff8ccfe097bc45d4323faba2ceb3a0bc158d49 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 6 Feb 2026 10:33:04 +0000 Subject: [PATCH 36/82] More flexibility in NormalsVisual --- examples/grid_border2.cpp | 1 + maths | 2 +- mplot/NormalsVisual.h | 128 +++++++++++++++++++++++++------------- 3 files changed, 88 insertions(+), 43 deletions(-) diff --git a/examples/grid_border2.cpp b/examples/grid_border2.cpp index c9b1a2fe..c64a2456 100644 --- a/examples/grid_border2.cpp +++ b/examples/grid_border2.cpp @@ -196,6 +196,7 @@ int main() // Add a Normals visual for the last one, too auto nrm = std::make_unique> (gvp); v.bindmodel (nrm); + nrm->options.set (mplot::normalsvisual_flags::show_halfedges); nrm->finalize(); v.addVisualModel (nrm); diff --git a/maths b/maths index 74fe7926..2865cb34 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 74fe7926cd94489c199efe77840de4e211f559d1 +Subproject commit 2865cb349e652cf71ee520885bdb8bf47b1e8528 diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 287d5a49..12594818 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -6,11 +6,23 @@ #include #include +#include #include #include namespace mplot { + enum class normalsvisual_flags : uint32_t + { + show_gl_normals, // Show the OpenGL vertex normals? + show_tri_edges, // Show the OpenGLtriangle edge vectors? + show_tri_normals, // Show the OpenGL triangle-derived normal? + show_halfedges, // Show the navmesh halfedges (all of them)? + show_inner_halfedges, // Show the main, internal navmesh halfedges (the blue ones)? + show_boundary_halfedges, // Show the boundary navmesh halfedges (the red ones)? + singlecolour // Plot vertex normals in a single colour? + }; + //! A class to visualize normals for another model template class NormalsVisual : public VisualModel @@ -18,6 +30,7 @@ namespace mplot public: NormalsVisual(mplot::VisualModel* _mymodel) { + this->options_defaults(); this->mymodel = _mymodel; this->viewmatrix = _mymodel->getViewMatrix(); // We create the model's navmesh, in case it wasn't already done @@ -30,28 +43,31 @@ namespace mplot std::cout << "NormalsVisual: I have no model; returning\n"; return; } - + std::cout << "InitializeVertices\n"; const float cone_r = this->thickness * this->scale_factor * 2.0f; const float tube_r = this->thickness * this->scale_factor; // Copy data out of my model... std::vector mymodelPositions = mymodel->getVertexPositions(); std::vector mymodelNormals = mymodel->getVertexNormals(); std::vector mymodelColors = {}; - if (!singlecolour) { mymodelColors = mymodel->getVertexColors(); } + if (this->options.test (normalsvisual_flags::singlecolour) == false) { + mymodelColors = mymodel->getVertexColors(); + } // And interpret it auto vp = reinterpret_cast>*>(&mymodelPositions); auto vn = reinterpret_cast>*>(&mymodelNormals); auto vc = reinterpret_cast>*>(&mymodelColors); - for (uint32_t ii = 0; ii < vn->size(); ++ii) { - // (*vp)[ii] is position, (*vn)[ii] is normal - std::array _clr = clr; - if (!singlecolour) { _clr = (*vc)[ii]; } - this->computeArrow ((*vp)[ii], ((*vp)[ii] + (*vn)[ii] * this->scale_factor), - _clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); + if (this->options.test (normalsvisual_flags::show_gl_normals)) { + std::cout << "Showing " << vn->size() << " OpenGL vertex normals" << std::endl; + for (uint32_t ii = 0; ii < vn->size(); ++ii) { + // (*vp)[ii] is position, (*vn)[ii] is normal + std::array _clr = clr; + if (this->options.test (normalsvisual_flags::singlecolour) == false) { _clr = (*vc)[ii]; } + this->computeArrow ((*vp)[ii], ((*vp)[ii] + (*vn)[ii] * this->scale_factor), + _clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } } - - // If we also have the navmesh, then use its triangles to show face normals if (mymodel->navmesh) { @@ -60,40 +76,55 @@ namespace mplot sm::vec nvd = {}; sm::vec pos = {}; - for (auto t : mymodel->navmesh->triangles) { - - sm::vec, 3> vrts = mymodel->navmesh->triangle_vertices (t.hi); - - nvc = vrts[1] - vrts[0]; - nvd = vrts[2] - vrts[0]; - - nv = mymodel->navmesh->triangle_normal (vrts); - - // Plot tn at mean location of f - pos = vrts.mean(); - // Mesh triangle normals - this->computeArrow (pos, (pos + nv * this->scale_factor), - clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); - // Computed triangle normals - this->computeArrow (pos, (pos + nvc * this->scale_factor), - clrnc, tube_r, this->arrowhead_prop, cone_r, this->shapesides); - this->computeArrow (pos, (pos + nvd * scale_factor), - clrnd, tube_r, this->arrowhead_prop, cone_r, this->shapesides); + if (this->options.any_of ({normalsvisual_flags::show_tri_normals, normalsvisual_flags::show_tri_edges})) { + std::cout << "About to show normals/edges for " << mymodel->navmesh->triangles.size() << " triangles" << std::endl; + + for (auto t : mymodel->navmesh->triangles) { + + sm::vec, 3> vrts = mymodel->navmesh->triangle_vertices (t.hi); + // Plot normal/edge vectors at mean location of f + pos = vrts.mean(); + if (this->options.test (normalsvisual_flags::show_tri_normals)) { + nv = mymodel->navmesh->triangle_normal (vrts); + // Mesh triangle normals + this->computeArrow (pos, (pos + nv * this->scale_factor), + clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + if (this->options.test (normalsvisual_flags::show_tri_edges)) { + // Computed triangle edges + nvc = vrts[1] - vrts[0]; + nvd = vrts[2] - vrts[0]; + this->computeArrow (pos, (pos + nvc * this->scale_factor), + clrnc, tube_r, this->arrowhead_prop, cone_r, this->shapesides); + this->computeArrow (pos, (pos + nvd * scale_factor), + clrnd, tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + } } // Can also show halfedges from the navmesh - for (auto h : mymodel->navmesh->halfedge) { - - auto p0 = mymodel->navmesh->vertex[h.vi[0]].p; - auto p1 = mymodel->navmesh->vertex[h.vi[1]].p; - if (h.flags) { - // boundary - this->computeArrow (p0, p1, mplot::colour::crimson, - tube_r, this->arrowhead_prop, cone_r, this->shapesides); - } else { - // internal - this->computeArrow (p0, p1, mplot::colour::dodgerblue2, - tube_r, this->arrowhead_prop, cone_r, this->shapesides); + if (this->options.any_of ({normalsvisual_flags::show_halfedges, + normalsvisual_flags::show_inner_halfedges, + normalsvisual_flags::show_boundary_halfedges})) { + std::cout << "About to show " << mymodel->navmesh->halfedge.size() << " halfedges from the model..." << std::endl; + for (auto h : mymodel->navmesh->halfedge) { + + auto p0 = mymodel->navmesh->vertex[h.vi[0]].p; + auto p1 = mymodel->navmesh->vertex[h.vi[1]].p; + if (h.flags) { + // boundary + if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges})) { + + this->computeArrow (p0, p1, mplot::colour::crimson, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + } else { + // internal + if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_inner_halfedges})) { + this->computeArrow (p0, p1, mplot::colour::dodgerblue2, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + } } } } @@ -109,8 +140,21 @@ namespace mplot float arrowhead_prop = 0.25f; // How much to linearly scale the size of the vector float scale_factor = 0.1f; + // Options for this VisualModel. Set these with calls like + // vm.options.set (mplot::normalsvisual_flags::show_gl_normals, false) + // from your client code + sm::flags options; + void options_defaults() + { + std::cout << __func__ << " called\n"; + this->options.reset(); + this->options.set (normalsvisual_flags::show_gl_normals, true); + this->options.set (normalsvisual_flags::show_tri_edges, false); + this->options.set (normalsvisual_flags::show_tri_normals, true); + this->options.set (normalsvisual_flags::show_halfedges, false); + this->options.set (normalsvisual_flags::singlecolour, false); + } // Vector single colour - bool singlecolour = false; std::array clr = mplot::colour::grey20; std::array clrnc = mplot::colour::grey60; // computed norm std::array clrnd = mplot::colour::grey90; // computed norm From 06fc4e721e7b9719928d2576c9c518034947cc2e Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 6 Feb 2026 10:44:38 +0000 Subject: [PATCH 37/82] cout changes in NormalsVisual --- mplot/NormalsVisual.h | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 12594818..7f536a21 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -77,7 +77,7 @@ namespace mplot sm::vec pos = {}; if (this->options.any_of ({normalsvisual_flags::show_tri_normals, normalsvisual_flags::show_tri_edges})) { - std::cout << "About to show normals/edges for " << mymodel->navmesh->triangles.size() << " triangles" << std::endl; + std::cout << "About to show normals and/or edges for " << mymodel->navmesh->triangles.size() << " triangles" << std::endl; for (auto t : mymodel->navmesh->triangles) { @@ -106,7 +106,18 @@ namespace mplot if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_inner_halfedges, normalsvisual_flags::show_boundary_halfedges})) { - std::cout << "About to show " << mymodel->navmesh->halfedge.size() << " halfedges from the model..." << std::endl; + + if (this->options.test (normalsvisual_flags::show_halfedges) + || this->options.test ({normalsvisual_flags::show_inner_halfedges, normalsvisual_flags::show_boundary_halfedges})) { + std::cout << "About to show " << mymodel->navmesh->halfedge.size() << " halfedges from the model...\n"; + } else { + if (this->options.test (normalsvisual_flags::show_inner_halfedges)) { + std::cout << "Showing only inner halfedges (approx " << mymodel->navmesh->halfedge.size() << ")\n"; + } else if (this->options.test (normalsvisual_flags::show_inner_halfedges)) { + std::cout << "Showing only boundary halfedges (approx " << mymodel->navmesh->halfedge.size() << ")\n"; + } + } + for (auto h : mymodel->navmesh->halfedge) { auto p0 = mymodel->navmesh->vertex[h.vi[0]].p; @@ -146,7 +157,6 @@ namespace mplot sm::flags options; void options_defaults() { - std::cout << __func__ << " called\n"; this->options.reset(); this->options.set (normalsvisual_flags::show_gl_normals, true); this->options.set (normalsvisual_flags::show_tri_edges, false); From 3c34f88d914b2c60d623a4f04172513e184bb80f Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 6 Feb 2026 10:44:59 +0000 Subject: [PATCH 38/82] Make some messages debug only --- mplot/NavMesh.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index e245d9fc..64392f1a 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -334,14 +334,14 @@ namespace mplot uint32_t bcand = cur; // bcand starts as an internal halfedge uint32_t bcandi = max; uint32_t counter = 0u; - std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; + if constexpr (debug_bnd) { std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; } //uint32_t bcand0 = this->halfedge[this->halfedge[bcand].prev].twin; //uint32_t bcand0t = max; do { bcandi = this->halfedge[bcand].prev; - std::cout << "bcandi: " << bcandi << std::endl; + if constexpr (debug_bnd) { std::cout << "bcandi: " << bcandi << std::endl; } bcand = this->halfedge[bcandi].twin; // if max, it's a boundary, else it's internal - std::cout << "bcand: " << bcand << std::endl; + if constexpr (debug_bnd) { std::cout << "bcand: " << bcand << std::endl; } if (counter++ > 6) { std::cout << "Something is wrong; returning\n"; return; From d17caadde228f84d4992ff8592462f298b37dc83 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 6 Feb 2026 12:26:35 +0000 Subject: [PATCH 39/82] Current progress. Seem to be correctly creating the navmesh. Found a rouge vertex/halfedge in the Seville env --- maths | 2 +- mplot/NavMesh.h | 56 ++++++++++++++++++++++++------------------- mplot/NormalsVisual.h | 10 +++++++- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/maths b/maths index 2865cb34..0159d047 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 2865cb349e652cf71ee520885bdb8bf47b1e8528 +Subproject commit 0159d04745089efafaacf1be8ee3a9041db9953f diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 64392f1a..64a95029 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -317,6 +317,7 @@ namespace mplot { constexpr uint32_t max = std::numeric_limits::max(); constexpr bool debug_bnd = true; + constexpr bool debug_bnd2 = false; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; @@ -333,37 +334,44 @@ namespace mplot } uint32_t bcand = cur; // bcand starts as an internal halfedge uint32_t bcandi = max; - uint32_t counter = 0u; - if constexpr (debug_bnd) { std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; } - //uint32_t bcand0 = this->halfedge[this->halfedge[bcand].prev].twin; - //uint32_t bcand0t = max; + if constexpr (debug_bnd2) { std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; } + uint32_t bcand0 = max; do { bcandi = this->halfedge[bcand].prev; - if constexpr (debug_bnd) { std::cout << "bcandi: " << bcandi << std::endl; } bcand = this->halfedge[bcandi].twin; // if max, it's a boundary, else it's internal - if constexpr (debug_bnd) { std::cout << "bcand: " << bcand << std::endl; } - if (counter++ > 6) { - std::cout << "Something is wrong; returning\n"; - return; + if constexpr (debug_bnd2) { + std::cout << "bcandi (inner): " << bcandi << ", bcand: " << bcand << std::endl; } - //if (counter == 1) { bcand0t = bcand0; } - - } while (bcand != max // halfedge[i].twin is usually max, so second condition may be sufficient - && bcand != this->halfedge[i].twin - //&& bcand != bcand0t - && bcandi != cur - ); + if (bcand != max && bcand == bcand0) { + // We've looped back without finding a boundary halfedge. halfedge[cur] is probably a rogue halfedge/vertex + ++done; + if constexpr (debug_bnd) { + std::cout << "Completed a loop, but didn't find a boundary halfedge. cur is probably a rogue halfedge/vertex\n"; + } + } + if (bcand0 == max) { bcand0 = bcand; } // bcand0 tests we we looped back, but not to halfedge[i].twin - this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, bprev, sz + j + 1, 1}); - this->halfedge[cur].twin = sz + j; + } while (bcand != max && // halfedge[i].twin is usually max, so second condition may be sufficient + bcand != this->halfedge[i].twin + && !done + /*&& bcandi != cur*/); // This last test probably crept in during development without being necessary - if (bcandi == i) { - this->halfedge[sz + j0].prev = sz + j - 1; - ++done; + if (done) { + // The bcand we have right now is NOT a boundary halfedge, nor is it the twin for cur, so just mark halfedge flags with the 'rogue' flag (0x2) + this->halfedge[cur].flags |= 0x2; // Mark halfedge[cur] } else { - bprev = sz + j; - cur = bcandi; - ++j; + // Here, we add the new halfedge twin for cur. + this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, bprev, sz + j + 1, 1}); + this->halfedge[cur].twin = sz + j; + + if (bcandi == i) { + this->halfedge[sz + j0].prev = sz + j - 1; + ++done; + } else { + bprev = sz + j; + cur = bcandi; + ++j; + } } } if constexpr (debug_bnd) { diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 7f536a21..b5ce09d2 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -122,7 +122,15 @@ namespace mplot auto p0 = mymodel->navmesh->vertex[h.vi[0]].p; auto p1 = mymodel->navmesh->vertex[h.vi[1]].p; - if (h.flags) { + if ((h.flags & 0x2) == 0x2) { + // special/rogue + std::cout << "Showing a rogue!\n"; + if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges, normalsvisual_flags::show_inner_halfedges})) { + + this->computeArrow (p0, p1, mplot::colour::yellow, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + } else if ((h.flags & 0x1) == 0x1) { // boundary if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges})) { From f30baaac4d938df43755a6aae2422ba7c630d73a Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 6 Feb 2026 12:35:35 +0000 Subject: [PATCH 40/82] Tidy up --- mplot/NavMesh.h | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 64a95029..5f959018 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -316,8 +316,7 @@ namespace mplot void add_boundary_halfedges() { constexpr uint32_t max = std::numeric_limits::max(); - constexpr bool debug_bnd = true; - constexpr bool debug_bnd2 = false; + constexpr bool debug_bnd = false; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; @@ -330,53 +329,49 @@ namespace mplot uint32_t done = 0u; while (!done) { if constexpr (debug_bnd) { - std::cout << " Search for boundary from cur = " << cur << std::endl; + std::cout << "** Search for boundary from cur = " << cur << std::endl; } uint32_t bcand = cur; // bcand starts as an internal halfedge uint32_t bcandi = max; - if constexpr (debug_bnd2) { std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; } + if constexpr (debug_bnd) { std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; } uint32_t bcand0 = max; do { bcandi = this->halfedge[bcand].prev; bcand = this->halfedge[bcandi].twin; // if max, it's a boundary, else it's internal - if constexpr (debug_bnd2) { - std::cout << "bcandi (inner): " << bcandi << ", bcand: " << bcand << std::endl; - } + if constexpr (debug_bnd) { std::cout << "bcandi (inner): " << bcandi << ", bcand: " << bcand << std::endl; } if (bcand != max && bcand == bcand0) { // We've looped back without finding a boundary halfedge. halfedge[cur] is probably a rogue halfedge/vertex ++done; - if constexpr (debug_bnd) { - std::cout << "Completed a loop, but didn't find a boundary halfedge. cur is probably a rogue halfedge/vertex\n"; - } + if constexpr (debug_bnd) { std::cout << "halfedge[cur] is a rogue halfedge/vertex?\n"; } } if (bcand0 == max) { bcand0 = bcand; } // bcand0 tests we we looped back, but not to halfedge[i].twin - } while (bcand != max && // halfedge[i].twin is usually max, so second condition may be sufficient - bcand != this->halfedge[i].twin - && !done - /*&& bcandi != cur*/); // This last test probably crept in during development without being necessary + } while (bcand != max && bcand != this->halfedge[i].twin && !done); + // && bcandi != cur <-- This last while() test probably crept in during development with out being necessary if (done) { - // The bcand we have right now is NOT a boundary halfedge, nor is it the twin for cur, so just mark halfedge flags with the 'rogue' flag (0x2) - this->halfedge[cur].flags |= 0x2; // Mark halfedge[cur] + // The bcand we have right now is NOT a boundary halfedge, nor is it the + // twin for cur, so just mark halfedge flags with the 'rogue' flag (0x2) + // which can be used by NormalsVisual to show the offending halfedge + this->halfedge[cur].flags |= 0x2; } else { - // Here, we add the new halfedge twin for cur. + // Now we add the new halfedge twin for cur. this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, bprev, sz + j + 1, 1}); this->halfedge[cur].twin = sz + j; if (bcandi == i) { + // We've come all the way around the boundary loop and we are finished. this->halfedge[sz + j0].prev = sz + j - 1; ++done; } else { + // We've only added one new halfedge to the boundary loop, so carry on... bprev = sz + j; cur = bcandi; ++j; } } } - if constexpr (debug_bnd) { - std::cout << "Added " << (j - j0) << " halfedges to that boundary\n"; - } + if constexpr (debug_bnd) { std::cout << "Added " << (j - j0) << " halfedges to that boundary\n"; } } } if constexpr (debug_bnd) { std::cout << __func__ << " returning\n"; } From 6af97b6a1c397c63b057262b82706d54bfb74bee Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 6 Feb 2026 16:23:28 +0000 Subject: [PATCH 41/82] Now we are off-edge tolerant (well as long as client code catches off-edge - could also just return) --- mplot/NavMesh.h | 162 +++++++++++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 65 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 5f959018..a8d811e8 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -237,20 +237,23 @@ namespace mplot { sm::vec, 3> trivert = {}; if (tri_hi == std::numeric_limits::max()) { - std::cout << "tri_hi is null?\n"; + std::cout << "tri_hi is unset?\n"; return trivert; } - uint32_t i = 0; uint32_t hi = tri_hi; - do { - //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; + for (uint32_t i = 0; i < 3; ++i) { if (this->halfedge[hi].vi[0] < this->vertex.size()) { trivert[i] = this->vertex[this->halfedge[hi].vi[0]].p; } - ++i; hi = this->halfedge[hi].next; - } while (hi != tri_hi); + if (hi >= this->halfedge.size()) { break; } + } + if (hi != tri_hi) { + // Triangle didn't close. This can occur at the edge of a flat model + trivert[0][0] = std::numeric_limits::max(); // to tell client code + } + return trivert; } @@ -259,20 +262,23 @@ namespace mplot { sm::vec, 3> trivert = {}; if (tri_hi == std::numeric_limits::max()) { - std::cout << "tri_hi is null?\n"; + std::cout << "tri_hi is unset?\n"; return trivert; } - uint32_t i = 0; uint32_t hi = tri_hi; - do { - //std::cout << "vertex.size(): " << this->vertex.size() << std::endl; + for (uint32_t i = 0; i < 3; ++i) { if (this->halfedge[hi].vi[0] < this->vertex.size()) { trivert[i] = (transform * this->vertex[this->halfedge[hi].vi[0]].p).less_one_dim(); } - ++i; hi = this->halfedge[hi].next; - } while (hi != tri_hi); + if (hi >= this->halfedge.size()) { break; } + } + if (hi != tri_hi) { + // Triangle didn't close. This can occur at the edge of a flat model + trivert[0][0] = std::numeric_limits::max(); // to tell client code + } + return trivert; } @@ -1057,20 +1063,26 @@ namespace mplot const sm::mat& model_to_scene, const float hoverheight) { - constexpr bool debug_move = false; + constexpr bool debug_move = true; constexpr bool debug_move2 = true; // A data-containing exception to throw mplot::NavException ne (mplot::NavException::type::generic); + // In case we throw off-edge, we need to restore ti0's state + const uint32_t ti0_save = this->ti0; + // Boolean state flags used in this function enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing }; sm::flags flags; // Camera location, scene frame - sm::vec camloc_sf = cam_to_scene.translation(); + const sm::vec camloc_sf = cam_to_scene.translation(); // Convert indices to vertices for triangle ti0, converting to the scene frame sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); + if (tv_sf[0][0] == std::numeric_limits::max()) { + throw std::runtime_error ("ti0 is not a triangle"); + } // Compute the triangle normal in the scene frame sm::vec tn0 = this->triangle_normal (tv_sf); @@ -1159,6 +1171,9 @@ namespace mplot if (twin != std::numeric_limits::max()) { // Test to see if start location was inside this twin sm::vec, 3> tv_lf = this->triangle_vertices (twin, model_to_scene); + if (tv_lf[0][0] == std::numeric_limits::max()) { + throw std::runtime_error ("twin is not a triangle"); + } sm::vec _tn = this->triangle_normal (tv_lf); auto [is, h] = sm::geometry::ray_tri_intersection (tv_lf[0], tv_lf[1], tv_lf[2], camloc_sf + (_tn / 2.0f), -_tn); if constexpr (debug_move) { std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in twin " << twin << std::endl; } @@ -1220,6 +1235,9 @@ namespace mplot auto onens = this->find_neighbours (int_vertex_hi); for (auto _ti : onens) { sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + if (tv_nb[0][0] == std::numeric_limits::max()) { + throw std::runtime_error ("_ti is not a triangle"); + } auto _tn = this->triangle_normal (tv_nb); // gets normal in *scene frame* sm::vec _mv_orthog = _tn * (mv_sf.dot (_tn) / (_tn.dot (_tn))); // This tn needs to be in scene frame sm::vec _mv_inplane = mv_sf - _mv_orthog; // scene frame, a relative movement @@ -1312,64 +1330,72 @@ namespace mplot // Re-orient onto the new triangle sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); - _tn = this->triangle_normal (newtv_sf); - - if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } - - // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals - if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); } - - // Compute the reorientation due to the requested movement. - float rotn_angle = tn0.angle (_tn, cd.tri_edge); - // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation - if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } - sm::mat reorient_model; // reorientation transformation in sf - reorient_model.rotate (cd.tri_edge, rotn_angle); - sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); - reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); - reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf - - if (mv_rest.length() == 0) { - // The first movement to edge completed the movement. We actually landed ON the edge. - cam_to_surface = reorient_model * cam_to_surface; - flags.set (cmm_fl::done, true); - } else { - // There's additional movement to complete. - if constexpr (debug_move) { std::cout << "mv_rest length is " << mv_rest.length() << std::endl; } - - // At this point, can test to see if the end point of the movement - // lands in the adjacent triangle. If so, we're done, if not, time - // for another loop. - sm::vec endmv = (reorient_model * cam_to_surface * sm::vec{}).less_one_dim(); - // Is endmv in newtv_sf/_ti? - auto [isect2, isectpoint2] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect2 ? "" : " NOT") << " land in new triangle\n"; } - if (isect2) { - // We DID land in the neighbouring triangle. We are done. + if (newtv_sf[0][0] != std::numeric_limits::max()) { + + _tn = this->triangle_normal (newtv_sf); + + if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } + + // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals + if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); } + + // Compute the reorientation due to the requested movement. + float rotn_angle = tn0.angle (_tn, cd.tri_edge); + // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation + if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } + sm::mat reorient_model; // reorientation transformation in sf + reorient_model.rotate (cd.tri_edge, rotn_angle); + sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); + reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); + reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf + + if (mv_rest.length() == 0) { + // The first movement to edge completed the movement. We actually landed ON the edge. cam_to_surface = reorient_model * cam_to_surface; flags.set (cmm_fl::done, true); } else { - if constexpr (debug_move) { std::cout << "did we sail past or land on the boundary or land in a 1-neighbour?\n"; } - // Incomplete; We've sailed past newtv_sf. Or perhaps landed on the boundary??? - // We need to - // set an end-point that is on newtv_sf, update hov_sf, - // then recurse. also recompute the movement encoded in - // reorient_model - reorient_model.pretranslate (-mv_rest); - cam_to_surface = reorient_model * cam_to_surface; - hov_sf = cd.pm.end; // crossing data planned movement end - // Also update planned move, which is now shorter and in a new direction - tv_sf = newtv_sf; - mv_inplane = mv_rest; + // There's additional movement to complete. + if constexpr (debug_move) { std::cout << "mv_rest length is " << mv_rest.length() << std::endl; } + + // At this point, can test to see if the end point of the movement + // lands in the adjacent triangle. If so, we're done, if not, time + // for another loop. + sm::vec endmv = (reorient_model * cam_to_surface * sm::vec{}).less_one_dim(); + // Is endmv in newtv_sf/_ti? + auto [isect2, isectpoint2] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect2 ? "" : " NOT") << " land in new triangle\n"; } + if (isect2) { + // We DID land in the neighbouring triangle. We are done. + cam_to_surface = reorient_model * cam_to_surface; + flags.set (cmm_fl::done, true); + } else { + if constexpr (debug_move) { std::cout << "did we sail past or land on the boundary or land in a 1-neighbour?\n"; } + // Incomplete; We've sailed past newtv_sf. Or perhaps landed on the boundary??? + // We need to + // set an end-point that is on newtv_sf, update hov_sf, + // then recurse. also recompute the movement encoded in + // reorient_model + reorient_model.pretranslate (-mv_rest); + cam_to_surface = reorient_model * cam_to_surface; + hov_sf = cd.pm.end; // crossing data planned movement end + // Also update planned move, which is now shorter and in a new direction + tv_sf = newtv_sf; + mv_inplane = mv_rest; + } } - } - - this->ti0 = _ti; - tn0 = _tn; + this->ti0 = _ti; + tn0 = _tn; + } else { + ne.m_type = NavException::type::off_edge; + this->ti0 = ti0_save; + throw ne; + continue; + } } else { // other triangle not found?! We probably went off the edge of our navigation model mesh ne.m_type = NavException::type::off_edge; + this->ti0 = ti0_save; throw ne; continue; } @@ -1412,7 +1438,7 @@ namespace mplot flags.set (cmm_fl::vertex_crossing, false); uint32_t _ti_2n = std::numeric_limits::max(); - sm::vec_tn_2n = {}; + sm::vec _tn_2n = {}; sm::vec _tn = {}; // TWO NEIGHBOURS @@ -1423,6 +1449,9 @@ namespace mplot if (twin != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); + if (tv_nb[0][0] == std::numeric_limits::max()) { + throw std::runtime_error ("two-neighbour twin is not a triangle"); + } _tn = this->triangle_normal (tv_nb); auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); @@ -1472,6 +1501,9 @@ namespace mplot neighbours_tested.insert (_ti); sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + if (tv_nb[0][0] == std::numeric_limits::max()) { + throw std::runtime_error ("one-neighbour _ti is not a triangle"); + } _tn = this->triangle_normal (tv_nb); auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); From fb835451a2cd1392dc802f4526ea57ec6b4745e0 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 9 Feb 2026 08:57:34 +0000 Subject: [PATCH 42/82] Tail end of Friday's debugging --- mplot/NavMesh.h | 143 +++++++++++++++++++++------------------- mplot/VisualModelBase.h | 10 ++- 2 files changed, 84 insertions(+), 69 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index a8d811e8..1696c3d2 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -69,7 +69,7 @@ namespace mplot NavException (const type _type) : m_type(_type) {} - using std::exception::what; + //using std::exception::what; const char* what() { switch (m_type) { @@ -305,12 +305,20 @@ namespace mplot { uint32_t hi = a; std::vector rtn = {}; + + if (hi > this->halfedge.size()) { return rtn; } + do { // hi emanates from the vertex, so return it. rtn.push_back (hi); - hi = this->halfedge[this->halfedge[hi].prev].twin; - // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise - } while (hi != a); + uint32_t pr = this->halfedge[hi].prev; + if (pr != std::numeric_limits::max()) { + hi = this->halfedge[this->halfedge[hi].prev].twin; + // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise + } else { + hi = pr; // pr was max, so set hi to max too + } + } while (hi != a && hi != std::numeric_limits::max()); return rtn; } @@ -321,8 +329,9 @@ namespace mplot */ void add_boundary_halfedges() { + std::cout << __func__ << " called\n"; constexpr uint32_t max = std::numeric_limits::max(); - constexpr bool debug_bnd = false; + constexpr bool debug_bnd = true; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; @@ -1207,6 +1216,7 @@ namespace mplot // make tiny adjustment to camloc_sf so we ARE in the triangle? OR... isect = true; // SAY we are, and proceed? <-- this if it works. } else { + std::cout << "Got no_intersection exception...\n"; ne.m_type = NavException::type::no_intersection; throw ne; } @@ -1449,41 +1459,40 @@ namespace mplot if (twin != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); - if (tv_nb[0][0] == std::numeric_limits::max()) { - throw std::runtime_error ("two-neighbour twin is not a triangle"); - } - _tn = this->triangle_normal (tv_nb); + if (tv_nb[0][0] != std::numeric_limits::max()) { + _tn = this->triangle_normal (tv_nb); - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); - sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "TN: " << twin << ": isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << " Start IN? " << (is ? "Y" : "N") << "End IN? " << (endis ? "Y" : "N") << std::endl; - } + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); + sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); + sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; + if constexpr (debug_move) { + std::cout << "TN: " << twin << ": isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; + } + auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { + std::cout << " Start IN? " << (is ? "Y" : "N") << "End IN? " << (endis ? "Y" : "N") << std::endl; + } + + neighbours_tested.insert (twin); - neighbours_tested.insert (twin); - - // Here, start is in original, end may not be in original. This is an 'intersection detected crossing' of a triangle edge - // which wasn't picked up with compute_crossing_location - if (endis) { - // End is in neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } - flags.set (cmm_fl::detected_crossing, true); - detected_edge = hi; - //detected_edgevec = tv_nb[i2] - tv_nb[i1]; // no longer need as we have detected_edge, a halfedge? Can construct later? - break; // out of for - } else { // end not in neighbour - if (is) { // start is in neighbour tri (will re-orient to this and re-loop) - _ti_2n = twin; - _tn_2n = _tn; + // Here, start is in original, end may not be in original. This is an 'intersection detected crossing' of a triangle edge + // which wasn't picked up with compute_crossing_location + if (endis) { + // End is in neighbour so this is a detected crossing + if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } + flags.set (cmm_fl::detected_crossing, true); + detected_edge = hi; + //detected_edgevec = tv_nb[i2] - tv_nb[i1]; // no longer need as we have detected_edge, a halfedge? Can construct later? break; // out of for - } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors - // mean this location isn't 'in' either start or neighbour (according to ray_tri_intersection) - } + } else { // end not in neighbour + if (is) { // start is in neighbour tri (will re-orient to this and re-loop) + _ti_2n = twin; + _tn_2n = _tn; + break; // out of for + } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors + // mean this location isn't 'in' either start or neighbour (according to ray_tri_intersection) + } + } // else tv_nb is not a triangle } hi = this->halfedge[hi].next; @@ -1501,37 +1510,36 @@ namespace mplot neighbours_tested.insert (_ti); sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - if (tv_nb[0][0] == std::numeric_limits::max()) { - throw std::runtime_error ("one-neighbour _ti is not a triangle"); - } - _tn = this->triangle_normal (tv_nb); - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); - sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "endis ONE-n? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in ONE-neighbour " << _ti << " / " << tv_nb << std::endl; - std::cout << "And End of move " << (endis ? "IS" : "is NOT") << " in that ONE-neighbour " << std::endl; - } - - if (endis) { - // End is in one-neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing over ONE-neighbour! Pass on to next loop!\n"; } - flags.set (cmm_fl::vertex_crossing, true); - detected_edge = hi; - //detected_edgevec = {std::numeric_limits::max()}; // to be the cross product of the last-triangle normal and the newtri normal. - detected_newtri = _ti; - break; // out of for - } else { // end not in one-neighbour - if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) - _ti_2n = _ti; - _tn_2n = _tn; + if (tv_nb[0][0] != std::numeric_limits::max()) { + _tn = this->triangle_normal (tv_nb); + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); + sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); + sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; + if constexpr (debug_move) { + std::cout << "endis ONE-n? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; + } + auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { + std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in ONE-neighbour " << _ti << " / " << tv_nb << std::endl; + std::cout << "And End of move " << (endis ? "IS" : "is NOT") << " in that ONE-neighbour " << std::endl; + } + + if (endis) { + // End is in one-neighbour so this is a detected crossing + if constexpr (debug_move) { std::cout << "DETECTED crossing over ONE-neighbour! Pass on to next loop!\n"; } + flags.set (cmm_fl::vertex_crossing, true); + detected_edge = hi; + //detected_edgevec = {std::numeric_limits::max()}; // to be the cross product of the last-triangle normal and the newtri normal. + detected_newtri = _ti; break; // out of for - } // else end is not in one-neighbour, and neither is start. - } + } else { // end not in one-neighbour + if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) + _ti_2n = _ti; + _tn_2n = _tn; + break; // out of for + } // else end is not in one-neighbour, and neither is start. + } + } // else one-neighbour is not a triangle } hi = this->halfedge[hi].next; @@ -1540,6 +1548,7 @@ namespace mplot if (_ti_2n != std::numeric_limits::max()) { // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop + if constexpr (debug_move) { std::cout << "CHANGE ti0 to _ti_2n = " << _ti_2n << "\n"; } this->ti0 = _ti_2n; tn0 = _tn; // recompute mv_inplane for this neighbour triangle diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index e368854e..58ffafc1 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -398,8 +398,6 @@ namespace mplot } navmesh->compute_neighbour_relations(); // finds the halfedge twins - - navmesh->add_boundary_halfedges(); } /*! @@ -426,11 +424,19 @@ namespace mplot // Have we got a pre-computed navmesh file for the halfedge twin relationships? uint64_t h = this->hash(); std::string filename = mplot::tools::getTmpPath() + std::string("navmesh_") + std::to_string (h); + std::string filename_pre_boundary = filename + ".pre"; + if (mplot::tools::fileExists (filename)) { this->navmesh->load (filename); + } else if (mplot::tools::fileExists (filename_pre_boundary)) { + std::cout << "Pre-boundary navmesh\n"; + this->navmesh->load (filename_pre_boundary); + this->navmesh->add_boundary_halfedges(); } else { std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); + this->navmesh->save (filename_pre_boundary); + this->navmesh->add_boundary_halfedges(); this->navmesh->save (filename); } } From 5afcb44a6361535198dc168b7cddc125e1d2fc8e Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 9 Feb 2026 10:27:17 +0000 Subject: [PATCH 43/82] Avoid inf loop in find_neighbours. Break out of one-neighbour do-while at right time. Fixes some hangs --- mplot/NavMesh.h | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 1696c3d2..88facada 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -69,7 +69,7 @@ namespace mplot NavException (const type _type) : m_type(_type) {} - //using std::exception::what; + using std::exception::what; const char* what() { switch (m_type) { @@ -307,10 +307,17 @@ namespace mplot std::vector rtn = {}; if (hi > this->halfedge.size()) { return rtn; } - + // We have to defensively check for repeated halfedges as we cycle around neighbours. + std::set repeat; do { + if (repeat.count (hi)) { + // hi is a repeat. This means the mesh isn't perfect. + std::cout << "find_neighbours: Found a repeated halfedge that wasn't the first one; break (imperfect mesh)\n"; + break; + } // hi emanates from the vertex, so return it. rtn.push_back (hi); + repeat.insert (hi); uint32_t pr = this->halfedge[hi].prev; if (pr != std::numeric_limits::max()) { hi = this->halfedge[this->halfedge[hi].prev].twin; @@ -318,6 +325,7 @@ namespace mplot } else { hi = pr; // pr was max, so set hi to max too } + } while (hi != a && hi != std::numeric_limits::max()); return rtn; } @@ -329,9 +337,8 @@ namespace mplot */ void add_boundary_halfedges() { - std::cout << __func__ << " called\n"; constexpr uint32_t max = std::numeric_limits::max(); - constexpr bool debug_bnd = true; + constexpr bool debug_bnd = false; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; @@ -1529,13 +1536,14 @@ namespace mplot if constexpr (debug_move) { std::cout << "DETECTED crossing over ONE-neighbour! Pass on to next loop!\n"; } flags.set (cmm_fl::vertex_crossing, true); detected_edge = hi; - //detected_edgevec = {std::numeric_limits::max()}; // to be the cross product of the last-triangle normal and the newtri normal. detected_newtri = _ti; + hi = this->ti0; // to end the do-while break; // out of for } else { // end not in one-neighbour if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) _ti_2n = _ti; _tn_2n = _tn; + hi = this->ti0; // to end the do-while break; // out of for } // else end is not in one-neighbour, and neither is start. } From b07f7e7cfa43a7f935b3b608ab71b0f1f761d86a Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 9 Feb 2026 11:54:48 +0000 Subject: [PATCH 44/82] Removes NavException --- mplot/NavMesh.h | 61 +++++-------------------------------------------- 1 file changed, 6 insertions(+), 55 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 88facada..a62ff5d9 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -62,45 +62,6 @@ namespace mplot }; } - // Exception that (used to) return triangles that were near the location of the error - struct NavException : public std::exception - { - enum class type : uint32_t { generic, no_intersection, zero_mv, mv_to_vertex, undetected_crossing, nan_mv, off_edge }; - - NavException (const type _type) : m_type(_type) {} - - using std::exception::what; - const char* what() - { - switch (m_type) { - case type::no_intersection: - return "No intersection (at start) with triangle or neighbours"; - break; - case type::zero_mv: - return "Zero length mv_inplane so stop/freeze/crash"; - break; - case type::mv_to_vertex: - return "We've moved to a vertex, should have captured this case"; - break; - case type::undetected_crossing: - return "Should have detected crossing just now"; - break; - case type::nan_mv: - return "mv_inplane contained NaN"; - break; - case type::off_edge: - return "The movement went off the edge of the model"; - break; - case type::generic: - default: - break; - } - return "Generic"; - } - // Error type determines message generated - type m_type = type::generic; - }; - /*! * Navigation mesh of triangles. * @@ -1082,9 +1043,6 @@ namespace mplot constexpr bool debug_move = true; constexpr bool debug_move2 = true; - // A data-containing exception to throw - mplot::NavException ne (mplot::NavException::type::generic); - // In case we throw off-edge, we need to restore ti0's state const uint32_t ti0_save = this->ti0; @@ -1223,9 +1181,7 @@ namespace mplot // make tiny adjustment to camloc_sf so we ARE in the triangle? OR... isect = true; // SAY we are, and proceed? <-- this if it works. } else { - std::cout << "Got no_intersection exception...\n"; - ne.m_type = NavException::type::no_intersection; - throw ne; + throw std::runtime_error ("No intersection (at start) with triangle or neighbours"); } } else { if constexpr (debug_move2) { @@ -1303,12 +1259,10 @@ namespace mplot } if (mv_inplane.length() == 0) { - ne.m_type = NavException::type::zero_mv; - throw ne; + throw std::runtime_error ("Zero length mv_inplane so stop/freeze/crash"); } if (mv_inplane.has_nan()) { - ne.m_type = NavException::type::nan_mv; - throw ne; + throw std::runtime_error ("mv_inplane contained NaN"); } // Apply the edge crossing algorithm @@ -1404,16 +1358,14 @@ namespace mplot this->ti0 = _ti; tn0 = _tn; } else { - ne.m_type = NavException::type::off_edge; this->ti0 = ti0_save; - throw ne; + throw std::runtime_error ("off-edge: The movement went off the edge of the model"); continue; } } else { // other triangle not found?! We probably went off the edge of our navigation model mesh - ne.m_type = NavException::type::off_edge; this->ti0 = ti0_save; - throw ne; + throw std::runtime_error ("off-edge: The movement went off the edge of the model"); continue; } @@ -1427,8 +1379,7 @@ namespace mplot if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { flags.set (cmm_fl::single_movement, true); } else { // We've moved to a vertex, should have captured this case - ne.m_type = NavException::type::mv_to_vertex; - throw ne; + throw std::runtime_error ("We've moved to a vertex, should have captured this case"); } } else { // Test if it was movement-within; the simplest case From f87c0656f69372a7e4302ed3712c1630a021978d Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 9 Feb 2026 11:59:12 +0000 Subject: [PATCH 45/82] Because I removed NavException --- examples/model_crawler.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 8764bb10..7ca951f1 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -106,12 +106,8 @@ int main (int argc, char** argv) // Compute a new movement over the landscape mesh (the sphere) try { ca_view = gvp->navmesh->compute_mesh_movement (mv_ca, ca_view, sph_view, hoverheight); - } catch (mplot::NavException& e) { - if (e.m_type == mplot::NavException::type::off_edge) { - std::cout << "You can handle movements that go off the edge of a flat model\n"; - } + } catch (std::exception& e) { std::cout << "Exception navigating mesh at movement count " << move_counter << ": " << e.what() << std::endl; - throw e; } // Update the viewmatrix of the coord arrows, setting its position within the scene From 4e0e27419d9b4b1593ef92cea12adc8ace85eae5 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 9 Feb 2026 17:13:02 +0000 Subject: [PATCH 46/82] More debugging. Also add teh ability to pass in a navmesh directory for navmesh data fiels --- mplot/NavMesh.h | 70 ++++++++++++++++++++++++++++++----------- mplot/VisualModelBase.h | 17 +++++----- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index a62ff5d9..cd197131 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -775,7 +775,7 @@ namespace mplot const sm::vec& mv_s, const sm::vec& mv_inplane) { - constexpr bool debug = false; + constexpr bool debug = true; crossing_data cd; cd.pm.flags.set (pm_fl::no_cross_point, true); @@ -1054,9 +1054,8 @@ namespace mplot const sm::vec camloc_sf = cam_to_scene.translation(); // Convert indices to vertices for triangle ti0, converting to the scene frame sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); - if (tv_sf[0][0] == std::numeric_limits::max()) { - throw std::runtime_error ("ti0 is not a triangle"); - } + if (tv_sf[0][0] == std::numeric_limits::max()) { throw std::runtime_error ("ti0 is not a triangle"); } + // Compute the triangle normal in the scene frame sm::vec tn0 = this->triangle_normal (tv_sf); @@ -1277,6 +1276,8 @@ namespace mplot // an edge (probably while moving along that edge) cd.halfedge = detected_edge; cd.tri_edge = this->edge_vector (detected_edge, model_to_scene); + if constexpr (debug_move) { std::cout << "Obtained cd.tri_edge edge_vector (detected_edge=" << detected_edge << ", model_to_scene) = " + << cd.tri_edge << std::endl; } cd.pm.mv = mv_inplane; cd.pm.end = hov_sf + mv_inplane; } else { @@ -1293,30 +1294,50 @@ namespace mplot // new triangle is the twin of the crossed edge _ti = this->halfedge[cd.halfedge].twin; if constexpr (debug_move) { - std::cout << "find triangle across edge: halfedge " << cd.halfedge << " gives the neighbour, which is its twin: " << _ti << std::endl; + std::cout << "find triangle across edge: halfedge " << cd.halfedge << " twins to: " << _ti << std::endl; } } if (_ti != std::numeric_limits::max()) { - // Re-orient onto the new triangle sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); - if (newtv_sf[0][0] != std::numeric_limits::max()) { + if (newtv_sf[0][0] == std::numeric_limits::max()) { + this->ti0 = ti0_save; + throw std::runtime_error ("off-edge: The movement went off the edge of the model"); + continue; + } else { + + // Check that _ti is a triangle, else we were on the boundary + // BUT newtv_sf[0][0] check should already have done this?!? _tn = this->triangle_normal (newtv_sf); if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals - if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); } + if (flags.test (cmm_fl::vertex_crossing)) { + cd.tri_edge = tn0.cross (_tn); + if constexpr (debug_move) { std::cout << "Setting cd.tri_edge from tn0.cross (_tn) = " + << tn0 << ".cross (" << _tn << ") = " + << cd.tri_edge << std::endl; } + } // Compute the reorientation due to the requested movement. float rotn_angle = tn0.angle (_tn, cd.tri_edge); // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } sm::mat reorient_model; // reorientation transformation in sf + // No good if cd.tri_edge is (0,0,0)! reorient_model.rotate (cd.tri_edge, rotn_angle); sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); + if (std::isnan (mv_rest.length())) { + if constexpr (debug_move) { + std::cout << "Got NaN in mv_rest. mv_inplane: " << mv_inplane << ", cd.pm.mv: " << cd.pm.mv << "reorient_model:" << std::endl; + std::cout << "reorient_model created from tri_edge " << cd.tri_edge << " and rotn_angle " << rotn_angle << std::endl; + std::cout << reorient_model << std::endl; + } + throw std::runtime_error ("NaN in mv_rest"); + } reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf @@ -1333,6 +1354,8 @@ namespace mplot // for another loop. sm::vec endmv = (reorient_model * cam_to_surface * sm::vec{}).less_one_dim(); // Is endmv in newtv_sf/_ti? + std::cout << "Crazy? endmv = " << endmv << std::endl; + std::cout << "ray_tri_intersection with _ti: " << (endmv + (_tn / 2.0f)) << "," << -_tn << std::endl; auto [isect2, isectpoint2] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect2 ? "" : " NOT") << " land in new triangle\n"; } if (isect2) { @@ -1342,6 +1365,9 @@ namespace mplot } else { if constexpr (debug_move) { std::cout << "did we sail past or land on the boundary or land in a 1-neighbour?\n"; } // Incomplete; We've sailed past newtv_sf. Or perhaps landed on the boundary??? + + // Can now test if we went beyond the boundary. + // We need to // set an end-point that is on newtv_sf, update hov_sf, // then recurse. also recompute the movement encoded in @@ -1357,10 +1383,6 @@ namespace mplot this->ti0 = _ti; tn0 = _tn; - } else { - this->ti0 = ti0_save; - throw std::runtime_error ("off-edge: The movement went off the edge of the model"); - continue; } } else { // other triangle not found?! We probably went off the edge of our navigation model mesh @@ -1417,18 +1439,24 @@ namespace mplot if (twin != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); - if (tv_nb[0][0] != std::numeric_limits::max()) { + if (tv_nb[0][0] == std::numeric_limits::max()) { + // tv_nb is not a triangle + std::cout << "Are we off-edge?"; + throw std::runtime_error ("off-edge: Over a two-neighbour boundary"); + } else { _tn = this->triangle_normal (tv_nb); - + if constexpr (debug_move) { + std::cout << "TN: " << twin << ": START isect vector " << (hov_sf + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; + } auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; if constexpr (debug_move) { - std::cout << "TN: " << twin << ": isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; + std::cout << "TN: " << twin << ": END isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; } auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); if constexpr (debug_move) { - std::cout << " Start IN? " << (is ? "Y" : "N") << "End IN? " << (endis ? "Y" : "N") << std::endl; + std::cout << " Start IN? " << (is ? "Y" : "N") << "; End IN? " << (endis ? "Y" : "N") << std::endl; } neighbours_tested.insert (twin); @@ -1437,20 +1465,22 @@ namespace mplot // which wasn't picked up with compute_crossing_location if (endis) { // End is in neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing! Pass on to next loop!\n"; } + if constexpr (debug_move) { std::cout << "DETECTED crossing (end of movement intesects two-neighbour). set detected_crossing flag.\n"; } flags.set (cmm_fl::detected_crossing, true); detected_edge = hi; - //detected_edgevec = tv_nb[i2] - tv_nb[i1]; // no longer need as we have detected_edge, a halfedge? Can construct later? + hi = this->ti0; // to end the do-while break; // out of for + } else { // end not in neighbour if (is) { // start is in neighbour tri (will re-orient to this and re-loop) _ti_2n = twin; _tn_2n = _tn; + hi = this->ti0; // to end the do-while break; // out of for } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors // mean this location isn't 'in' either start or neighbour (according to ray_tri_intersection) } - } // else tv_nb is not a triangle + } } hi = this->halfedge[hi].next; @@ -1462,6 +1492,8 @@ namespace mplot uint32_t hi = this->ti0; do { // For each half edge around ti0 std::vector nbs = this->find_neighbours (hi); + std::cout << "Got " << nbs.size() << " neighbours\n"; + if (nbs.size() > 20) { throw std::runtime_error ("HOW many neighbours??"); } for (auto _ti : nbs) { // Already tested? This should avoid us testing any two-neighbours here (already did them above) if (neighbours_tested.count (_ti)) { continue; } diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 58ffafc1..9451e590 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -401,15 +401,11 @@ namespace mplot } /*! - * Post-process vertices to generate a neighbour relationship mesh. The usual vertices and - * indices may not be useful to help an agent to navigate the surface defined by the - * mesh. This is because vertices may be duplicated at any location, so that adjacent faces - * can have different normals and colours. + * Post-process vertices to generate a neighbour relationship mesh suitable for navigation. * - * To help guide movement across a mesh, it would be useful to have a mesh that always gives - * neighbour relationships. + * \param navmesh_dir The directory into which to store/read the navmesh data file. */ - void make_navmesh() + void make_navmesh (std::string navmesh_dir = "") { if (this->navmesh) { return; } // already made it @@ -423,7 +419,12 @@ namespace mplot // Have we got a pre-computed navmesh file for the halfedge twin relationships? uint64_t h = this->hash(); - std::string filename = mplot::tools::getTmpPath() + std::string("navmesh_") + std::to_string (h); + if (navmesh_dir.empty()) { + navmesh_dir = mplot::tools::getTmpPath(); + } else { + if (navmesh_dir.back() != '/') { navmesh_dir += "/"; } + } + std::string filename = navmesh_dir + std::string("navmesh_") + std::to_string (h); std::string filename_pre_boundary = filename + ".pre"; if (mplot::tools::fileExists (filename)) { From 8c320e1ba6aa8cced4dc84355295b5915f1c3cc5 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 9 Feb 2026 18:10:50 +0000 Subject: [PATCH 47/82] Monday debugging --- mplot/NavMesh.h | 65 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index cd197131..9d0d3536 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -453,6 +453,25 @@ namespace mplot } } + // A subroutine for find_triangle_crossing + void test_tri (std::set& tested, const uint32_t ontest, + const sm::vec& vstart, const sm::vec& vdir, const float vdsos, + sm::vec& isect_p, uint32_t& isect_ti, float & isect_d) const + { + if (tested.count (ontest)) { return; } + tested.insert (ontest); + sm::vec, 3> v = this->triangle_vertices (ontest); + auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); + if (isect) { + float d = (p - vstart).sos(); + if (d < vdsos) { + isect_p = p; + isect_ti = ontest; + isect_d = d; + } + } + } + /* * Find the location, and the triangle indices at which a ray starting from coord with * direction vdir - the 'penetration point' intersects with this NavMesh model. The length @@ -482,44 +501,49 @@ namespace mplot const float vdsos = vdir.sos(); + std::set tested; + // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml != std::numeric_limits::max()) { - sm::vec, 3> v = this->triangle_vertices (ti_ml); - auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); - if (isect) { - float d = (p - vstart).sos(); - if (d < vdsos) { - isect_p = p; - isect_ti = ti_ml; - isect_d = d; - } - } + + test_tri (tested, ti_ml, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); + if (isect_d != std::numeric_limits::max()) { // we found it in the first triangle! + //std::cout << "Got a first-tri hit!\n"; return { isect_p, isect_ti }; } // Next, test the neighbours of ti_ml std::vector nbs = this->find_neighbours (ti_ml); + //std::cout << "Testing " << nbs.size() << " neighbours of ti_ml for a hit\n"; for (uint32_t nb : nbs) { - v = this->triangle_vertices (nb); - auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); - if (isect) { - float d = (p - vstart).sos(); - if (d < vdsos) { - isect_p = p; - isect_ti = ti_ml; - isect_d = d; - } - } + + test_tri (tested, nb, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); if (isect_d != std::numeric_limits::max()) { + //std::cout << "Got a neighbour hit!\n"; return { isect_p, isect_ti }; } } + + // Do neighbours of neighbours? + for (uint32_t nb : nbs) { + std::vector nbs2 = this->find_neighbours (nb); + + for (uint32_t nb2 : nbs2) { + test_tri (tested, nb2, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); + + if (isect_d != std::numeric_limits::max()) { + std::cout << "Got a neighbour-neighbour hit!\n"; + return { isect_p, isect_ti }; + } + } + } } // Fall back to testing ALL the triangles... + //std::cout << "Oh, no have to test ALL triangles now...\n"; for (auto tri : this->triangles) { if constexpr (debug_ftc) { @@ -548,6 +572,7 @@ namespace mplot if (isect_p[0] == fmax) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. + std::cout << "Finally, check vertices...\n"; for (uint32_t vi = 0; vi < this->vertex.size(); ++vi) { sm::vec vertex_n = this->find_vertex_normal (this->vertex[vi].hi, model_to_scene); vertex_n.renormalize(); From 7823310d115087776dd92a123e8b55c0de7bf4ce Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 10 Feb 2026 15:17:40 +0000 Subject: [PATCH 48/82] next/prev visualization options --- mplot/NormalsVisual.h | 50 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index b5ce09d2..138554b5 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -20,6 +20,10 @@ namespace mplot show_halfedges, // Show the navmesh halfedges (all of them)? show_inner_halfedges, // Show the main, internal navmesh halfedges (the blue ones)? show_boundary_halfedges, // Show the boundary navmesh halfedges (the red ones)? + show_inner_next, // Visualize the 'next' halfedge of each inner halfedge + show_inner_prev, // Visualize the 'prev' halfedge of each inner halfedge + show_boundary_next, // Visualize the 'next' halfedge of each boundary halfedge + show_boundary_prev, // Visualize the 'prev' halfedge of each boundary halfedge singlecolour // Plot vertex normals in a single colour? }; @@ -30,7 +34,6 @@ namespace mplot public: NormalsVisual(mplot::VisualModel* _mymodel) { - this->options_defaults(); this->mymodel = _mymodel; this->viewmatrix = _mymodel->getViewMatrix(); // We create the model's navmesh, in case it wasn't already done @@ -137,6 +140,7 @@ namespace mplot this->computeArrow (p0, p1, mplot::colour::crimson, tube_r, this->arrowhead_prop, cone_r, this->shapesides); } + } else { // internal if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_inner_halfedges})) { @@ -144,6 +148,32 @@ namespace mplot tube_r, this->arrowhead_prop, cone_r, this->shapesides); } } + + if ((this->options.test (normalsvisual_flags::show_inner_next) && (h.flags & 0x1) == 0x0) + || + (this->options.test (normalsvisual_flags::show_boundary_next) && (h.flags & 0x1) == 0x1)) { + auto mid = (p0 + p1) / 2.0f + nextprev_offset; + auto nexti = h.next; + if (nexti < mymodel->navmesh->halfedge.size()) { + auto n0 = mymodel->navmesh->vertex[mymodel->navmesh->halfedge[nexti].vi[0]].p; + auto n1 = mymodel->navmesh->vertex[mymodel->navmesh->halfedge[nexti].vi[1]].p; + this->computeArrow (mid, (n0 + n1)/2.0f, mplot::colour::goldenrod2, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + + } + if ((this->options.test (normalsvisual_flags::show_inner_prev) && (h.flags & 0x1) == 0x0) + || + (this->options.test (normalsvisual_flags::show_boundary_prev) && (h.flags & 0x1) == 0x1)) { + auto mid = (p0 + p1) / 2.0f + nextprev_offset; + auto previ = h.prev; + if (previ < mymodel->navmesh->halfedge.size()) { + auto pr0 = mymodel->navmesh->vertex[mymodel->navmesh->halfedge[previ].vi[0]].p; + auto pr1 = mymodel->navmesh->vertex[mymodel->navmesh->halfedge[previ].vi[1]].p; + this->computeArrow (mid, (pr0 + pr1)/2.0f, mplot::colour::springgreen2, + tube_r, this->arrowhead_prop, cone_r, this->shapesides); + } + } } } } @@ -162,20 +192,22 @@ namespace mplot // Options for this VisualModel. Set these with calls like // vm.options.set (mplot::normalsvisual_flags::show_gl_normals, false) // from your client code - sm::flags options; - void options_defaults() + sm::flags options = options_defaults(); + constexpr sm::flags options_defaults() { - this->options.reset(); - this->options.set (normalsvisual_flags::show_gl_normals, true); - this->options.set (normalsvisual_flags::show_tri_edges, false); - this->options.set (normalsvisual_flags::show_tri_normals, true); - this->options.set (normalsvisual_flags::show_halfedges, false); - this->options.set (normalsvisual_flags::singlecolour, false); + sm::flags _options; + _options.reset(); + _options.set (normalsvisual_flags::show_gl_normals, true); + _options.set (normalsvisual_flags::show_tri_normals, true); + return _options; } // Vector single colour std::array clr = mplot::colour::grey20; std::array clrnc = mplot::colour::grey60; // computed norm std::array clrnd = mplot::colour::grey90; // computed norm + + // An offset to make the 'next' and 'prev' vectors meaningful. + sm::vec nextprev_offset = sm::vec::uz() * 0.1f; }; } // namespace mplot From e62940a928a7ee23b0ef96fd18a8bec459e2ef7a Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 10 Feb 2026 15:17:59 +0000 Subject: [PATCH 49/82] next/prev visualization options --- examples/grid_border2.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/grid_border2.cpp b/examples/grid_border2.cpp index c64a2456..3720cf95 100644 --- a/examples/grid_border2.cpp +++ b/examples/grid_border2.cpp @@ -21,6 +21,7 @@ int main() { mplot::Visual v(1600, 1000, "Flat GridVisual grids with borders"); v.lightingEffects(); + v.rotateAboutNearest (true); // Create a grid to show in the scene constexpr unsigned int Nside = 4; // You can change this @@ -186,7 +187,7 @@ int main() gv->colourScale.compute_scaling (-1, 1); // Border specific parameters gv->showborder (false); - gv->addLabel ("Triangles, no border (smaller is as expected)", lblpos, mplot::TextFeatures(0.08f)); + gv->addLabel ("Triangles, no border, showing halfedges", lblpos, mplot::TextFeatures(0.08f)); gv->finalize(); auto gvp = v.addVisualModel (gv); @@ -197,6 +198,8 @@ int main() auto nrm = std::make_unique> (gvp); v.bindmodel (nrm); nrm->options.set (mplot::normalsvisual_flags::show_halfedges); + nrm->options.set (mplot::normalsvisual_flags::show_boundary_next); + nrm->options.set (mplot::normalsvisual_flags::show_boundary_prev); nrm->finalize(); v.addVisualModel (nrm); From 6cb126b2d4213101ffd2d16572c4bd16989ec271 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 10 Feb 2026 15:19:16 +0000 Subject: [PATCH 50/82] Starting to write navmesh test code, but more importantly fixes for next/prev on the boundary halfedges --- mplot/NavMesh.h | 122 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 118 insertions(+), 4 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 9d0d3536..52ab5c4b 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -193,6 +193,71 @@ namespace mplot return i; } + bool verify_boundary (const uint32_t boundary_hi) const + { + constexpr uint32_t max = std::numeric_limits::max(); + + if (boundary_hi >= this->halfedge.size()) { return false; } + + // Each boundary should be the same forwards and backwards. + // Via 'next': + uint32_t fcount = 0; + uint32_t fhi = boundary_hi; + do { + std::cout << "this->halfedge["<halfedge[fhi].flags << " go to next..." << std::endl; + fhi = this->halfedge[fhi].next; + ++fcount; + } while (fhi != boundary_hi && fhi != max && fcount < this->halfedge.size()); + + std::cout << "fcount = " << fcount << " and fhi = " << fhi << " cf boundary_hi = " << boundary_hi << std::endl; + + uint32_t rcount = 0; + uint32_t rhi = boundary_hi; + do { + std::cout << "this->halfedge["<halfedge[rhi].flags << " go to prev..." << std::endl; + if (this->halfedge[rhi].prev == rhi) { throw std::runtime_error ("self prev referral!"); } + rhi = this->halfedge[rhi].prev; + ++rcount; + } while (rhi != boundary_hi && rhi != max && rcount < this->halfedge.size()); + + std::cout << "rcount = " << rcount << " and rhi = " << rhi << " cf boundary_hi = " << boundary_hi << std::endl; + + if (fcount == rcount && rhi == boundary_hi && fhi == boundary_hi) { + return true; + } else { + return false; + } + } + + bool verify_triangle (const uint32_t tri_hi) const + { + if (tri_hi == std::numeric_limits::max()) { + std::cout << __func__ << ": not triangle; tri_hi=" << tri_hi << " is max\n"; + return false; + } + bool is_triangle = false; + + // Should do-while this one in both directions, like the boundary + + uint32_t hi = tri_hi; + // We should go to 3 halfedges and get back to tri_hi. Each half edge should have a valid vertex, too. + for (uint32_t i = 0; i < 3; ++i) { + if (this->halfedge[hi].vi[0] >= this->vertex.size() || this->halfedge[hi].vi[1] >= this->vertex.size()) { + std::cout << __func__ << ": not triangle; invalid vertex index\n"; + hi = std::numeric_limits::max(); + break; + } + hi = this->halfedge[hi].next; + if (hi >= this->halfedge.size()) { + std::cout << __func__ << ": not triangle; invalid next halfedge index\n"; + hi = std::numeric_limits::max(); + break; + } + } + if (hi == tri_hi) { is_triangle = true; } + return is_triangle; + } + // Return the three vertices for the triangle specified as three indices into NavMesh::vertex sm::vec, 3> triangle_vertices (uint32_t tri_hi) const { @@ -274,6 +339,15 @@ namespace mplot if (repeat.count (hi)) { // hi is a repeat. This means the mesh isn't perfect. std::cout << "find_neighbours: Found a repeated halfedge that wasn't the first one; break (imperfect mesh)\n"; +#if 0 + for (auto h : rtn) { + std::cout << h << " flag: " << this->halfedge[h].flags + << " prev: " << this->halfedge[h].prev + << " prev.twin: " << this->halfedge[this->halfedge[h].prev].twin << std::endl; + } + std::cout << "The repeat was: " << hi << std::endl; + throw std::runtime_error ("Repeated halfedge"); // caused by 2 boundary halfedges with the same 'prev' +#endif break; } // hi emanates from the vertex, so return it. @@ -291,6 +365,34 @@ namespace mplot return rtn; } + void test() + { + std::cout << __func__ << " called to test the navmesh\n"; + // 1) Verify that each halfedge is part of a face or hole (boundary) + for (uint32_t hi = 0; hi < this->halfedge.size(); ++hi) { + if ((this->halfedge[hi].flags & 0x1) == 0x1) { + //std::cout << "This halfedge was marked as boundary\n"; + if (this->verify_boundary (hi) == false) { + throw std::runtime_error ("Imperfect halfedge mesh (boundary hole)"); + } + } else { + if (this->verify_triangle (hi) == false) { + throw std::runtime_error ("Imperfect halfedge mesh (face triangle)"); + } +#if 0 + std::vector nb = this->find_neighbours (hi); + if (nb.size() > 100) { + for (auto n : nb) { + std::cout << n << std::endl; + } + throw std::runtime_error ("Imperfect halfedge mesh (too many neighbours)"); + } + std::cout << "num neighbours = " << nb.size() << std::endl; +#endif + } + } + } + /* * After making the neighbour relations from the OpenGL mesh, the last step is to fill in * the boundary halfedges. Find all halfedges with an unset twin and then start creating the @@ -299,12 +401,14 @@ namespace mplot void add_boundary_halfedges() { constexpr uint32_t max = std::numeric_limits::max(); - constexpr bool debug_bnd = false; + constexpr bool debug_bnd = true; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; + std::cout << "BEFORE BEFORE adding boundary, halfedge.size() = " << halfedge.size() << std::endl; for (uint32_t i = 0; i < sz; ++i) { if (this->halfedge[i].twin == max) { + std::cout << "STARTING at i = " << i << std::endl; // This halfedge does not have a twin, walk the boundary from here const uint32_t j0 = j; // j index at boundary start uint32_t bprev = max; @@ -339,12 +443,22 @@ namespace mplot this->halfedge[cur].flags |= 0x2; } else { // Now we add the new halfedge twin for cur. - this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, bprev, sz + j + 1, 1}); - this->halfedge[cur].twin = sz + j; + const uint32_t _bnext = sz + j + 1; + const uint32_t newi = halfedge.size(); + if constexpr (debug_bnd) { std::cout << "Push-back to halfedge[" << newi + << "] with prev = " << bprev << " next = " << _bnext + << " and twin = " << cur << std::endl; } + this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, _bnext, bprev, 1}); + this->halfedge[cur].twin = newi; if (bcandi == i) { // We've come all the way around the boundary loop and we are finished. - this->halfedge[sz + j0].prev = sz + j - 1; + const uint32_t _first = sz + j0; + const uint32_t _last = sz + j; + if constexpr (debug_bnd) { std::cout << "Set final prev for halfedge[" << _first << "] to " << _last << std::endl; } + this->halfedge[_first].prev = _last; + if constexpr (debug_bnd) { std::cout << "Set final next for halfedge[" << _last << "] to " << _first << std::endl; } + this->halfedge[_last].next = _first; ++done; } else { // We've only added one new halfedge to the boundary loop, so carry on... From 24c8ac1400fda403a0acbdf4788bb845eafbca70 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 10 Feb 2026 15:57:16 +0000 Subject: [PATCH 51/82] Final fix on prev/next and boundaries --- mplot/NavMesh.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 52ab5c4b..5bcbfb83 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -402,30 +402,30 @@ namespace mplot { constexpr uint32_t max = std::numeric_limits::max(); constexpr bool debug_bnd = true; + constexpr bool debug_bnd2 = false; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; - std::cout << "BEFORE BEFORE adding boundary, halfedge.size() = " << halfedge.size() << std::endl; + std::cout << "BEFORE adding boundary, halfedge.size() = " << halfedge.size() << std::endl; for (uint32_t i = 0; i < sz; ++i) { if (this->halfedge[i].twin == max) { - std::cout << "STARTING at i = " << i << std::endl; + std::cout << "STARTING at i = " << i; // This halfedge does not have a twin, walk the boundary from here const uint32_t j0 = j; // j index at boundary start + std::cout << ".... with j0 = " << j0 << std::endl; uint32_t bprev = max; uint32_t cur = i; uint32_t done = 0u; while (!done) { - if constexpr (debug_bnd) { - std::cout << "** Search for boundary from cur = " << cur << std::endl; - } + if constexpr (debug_bnd2) { std::cout << "** Search for boundary from cur = " << cur << std::endl; } uint32_t bcand = cur; // bcand starts as an internal halfedge uint32_t bcandi = max; - if constexpr (debug_bnd) { std::cout << "halfedge[i].twin = " << this->halfedge[i].twin << std::endl; } + if constexpr (debug_bnd2) { std::cout << "halfedge[" << i << "].twin = " << this->halfedge[i].twin << std::endl; } uint32_t bcand0 = max; do { bcandi = this->halfedge[bcand].prev; bcand = this->halfedge[bcandi].twin; // if max, it's a boundary, else it's internal - if constexpr (debug_bnd) { std::cout << "bcandi (inner): " << bcandi << ", bcand: " << bcand << std::endl; } + if constexpr (debug_bnd2) { std::cout << "bcandi (inner): " << bcandi << ", bcand: " << bcand << std::endl; } if (bcand != max && bcand == bcand0) { // We've looped back without finding a boundary halfedge. halfedge[cur] is probably a rogue halfedge/vertex ++done; @@ -455,17 +455,17 @@ namespace mplot // We've come all the way around the boundary loop and we are finished. const uint32_t _first = sz + j0; const uint32_t _last = sz + j; - if constexpr (debug_bnd) { std::cout << "Set final prev for halfedge[" << _first << "] to " << _last << std::endl; } this->halfedge[_first].prev = _last; - if constexpr (debug_bnd) { std::cout << "Set final next for halfedge[" << _last << "] to " << _first << std::endl; } + if constexpr (debug_bnd) { std::cout << "Update final next for halfedge[" << _last << "] to " << _first << std::endl; } + if constexpr (debug_bnd) { std::cout << "Set initial prev for halfedge[" << _first << "] to " << _last << std::endl; } this->halfedge[_last].next = _first; ++done; } else { // We've only added one new halfedge to the boundary loop, so carry on... bprev = sz + j; cur = bcandi; - ++j; } + ++j; } } if constexpr (debug_bnd) { std::cout << "Added " << (j - j0) << " halfedges to that boundary\n"; } From cbf6214763c0ca0ca6d09c6ca6df09027a9badcd Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 10 Feb 2026 17:20:23 +0000 Subject: [PATCH 52/82] Progress. Still have a problem in ground_smaller2.gltf, but boundaries are good now --- mplot/NavMesh.h | 114 ++++++++++++++++++++-------------------- mplot/VisualModelBase.h | 7 ++- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 5bcbfb83..7cdee3ee 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -193,70 +193,51 @@ namespace mplot return i; } - bool verify_boundary (const uint32_t boundary_hi) const + bool verify_halfedge_chain (const uint32_t _hi, const uint32_t chain_length = std::numeric_limits::max()) const { constexpr uint32_t max = std::numeric_limits::max(); - if (boundary_hi >= this->halfedge.size()) { return false; } + if (_hi >= this->halfedge.size()) { return false; } - // Each boundary should be the same forwards and backwards. - // Via 'next': + // Each boundary should be the same forwards and backwards from every possible start halfedge uint32_t fcount = 0; - uint32_t fhi = boundary_hi; + uint32_t fhi = _hi; do { - std::cout << "this->halfedge["<halfedge[fhi].flags << " go to next..." << std::endl; + //std::cout << "this->halfedge["<halfedge[fhi].flags << " go to next..." << std::endl; + if (this->halfedge[fhi].next == fhi) { throw std::runtime_error ("self next referral!"); } fhi = this->halfedge[fhi].next; ++fcount; - } while (fhi != boundary_hi && fhi != max && fcount < this->halfedge.size()); + } while (fhi != _hi && fhi != max && fcount < this->halfedge.size()); - std::cout << "fcount = " << fcount << " and fhi = " << fhi << " cf boundary_hi = " << boundary_hi << std::endl; + //std::cout << "fcount = " << fcount << " and fhi = " << fhi << " cf _hi = " << _hi << std::endl; uint32_t rcount = 0; - uint32_t rhi = boundary_hi; + uint32_t rhi = _hi; do { - std::cout << "this->halfedge["<halfedge[rhi].flags << " go to prev..." << std::endl; + //std::cout << "this->halfedge["<halfedge[rhi].flags << " go to prev..." << std::endl; if (this->halfedge[rhi].prev == rhi) { throw std::runtime_error ("self prev referral!"); } rhi = this->halfedge[rhi].prev; ++rcount; - } while (rhi != boundary_hi && rhi != max && rcount < this->halfedge.size()); + } while (rhi != _hi && rhi != max && rcount < this->halfedge.size()); - std::cout << "rcount = " << rcount << " and rhi = " << rhi << " cf boundary_hi = " << boundary_hi << std::endl; + //std::cout << "rcount = " << rcount << " and rhi = " << rhi << " cf _hi = " << _hi << std::endl; - if (fcount == rcount && rhi == boundary_hi && fhi == boundary_hi) { - return true; + if (fcount == rcount && rhi == _hi && fhi == _hi) { + if (chain_length != max) { + if (fcount == chain_length) { + return true; + } else { + return false; + } + } else { + return true; + } } else { return false; } } - - bool verify_triangle (const uint32_t tri_hi) const - { - if (tri_hi == std::numeric_limits::max()) { - std::cout << __func__ << ": not triangle; tri_hi=" << tri_hi << " is max\n"; - return false; - } - bool is_triangle = false; - - // Should do-while this one in both directions, like the boundary - - uint32_t hi = tri_hi; - // We should go to 3 halfedges and get back to tri_hi. Each half edge should have a valid vertex, too. - for (uint32_t i = 0; i < 3; ++i) { - if (this->halfedge[hi].vi[0] >= this->vertex.size() || this->halfedge[hi].vi[1] >= this->vertex.size()) { - std::cout << __func__ << ": not triangle; invalid vertex index\n"; - hi = std::numeric_limits::max(); - break; - } - hi = this->halfedge[hi].next; - if (hi >= this->halfedge.size()) { - std::cout << __func__ << ": not triangle; invalid next halfedge index\n"; - hi = std::numeric_limits::max(); - break; - } - } - if (hi == tri_hi) { is_triangle = true; } - return is_triangle; - } + bool verify_boundary (const uint32_t boundary_hi) const { return verify_halfedge_chain (boundary_hi); } + bool verify_triangle (const uint32_t tri_hi) const { return verify_halfedge_chain (tri_hi, 3); } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex sm::vec, 3> triangle_vertices (uint32_t tri_hi) const @@ -339,7 +320,7 @@ namespace mplot if (repeat.count (hi)) { // hi is a repeat. This means the mesh isn't perfect. std::cout << "find_neighbours: Found a repeated halfedge that wasn't the first one; break (imperfect mesh)\n"; -#if 0 +#if 1 for (auto h : rtn) { std::cout << h << " flag: " << this->halfedge[h].flags << " prev: " << this->halfedge[h].prev @@ -365,6 +346,7 @@ namespace mplot return rtn; } + // Test the navmesh, to make sure it is perfect void test() { std::cout << __func__ << " called to test the navmesh\n"; @@ -379,16 +361,15 @@ namespace mplot if (this->verify_triangle (hi) == false) { throw std::runtime_error ("Imperfect halfedge mesh (face triangle)"); } -#if 0 + std::vector nb = this->find_neighbours (hi); if (nb.size() > 100) { for (auto n : nb) { std::cout << n << std::endl; } - throw std::runtime_error ("Imperfect halfedge mesh (too many neighbours)"); + throw std::runtime_error ("too many neighbours?"); } - std::cout << "num neighbours = " << nb.size() << std::endl; -#endif + // std::cout << "num neighbours = " << nb.size() << std::endl; } } } @@ -401,18 +382,18 @@ namespace mplot void add_boundary_halfedges() { constexpr uint32_t max = std::numeric_limits::max(); - constexpr bool debug_bnd = true; + constexpr bool debug_bnd = false; constexpr bool debug_bnd2 = false; const uint32_t sz = this->halfedge.size(); uint32_t j = 0; - std::cout << "BEFORE adding boundary, halfedge.size() = " << halfedge.size() << std::endl; + if constexpr (debug_bnd) { std::cout << "BEFORE adding boundary, halfedge.size() = " << halfedge.size() << std::endl; } for (uint32_t i = 0; i < sz; ++i) { if (this->halfedge[i].twin == max) { - std::cout << "STARTING at i = " << i; + if constexpr (debug_bnd) { std::cout << "STARTING at i = " << i; } // This halfedge does not have a twin, walk the boundary from here const uint32_t j0 = j; // j index at boundary start - std::cout << ".... with j0 = " << j0 << std::endl; + if constexpr (debug_bnd) { std::cout << ".... with j0 = " << j0 << std::endl; } uint32_t bprev = max; uint32_t cur = i; uint32_t done = 0u; @@ -484,7 +465,7 @@ namespace mplot */ void compute_neighbour_relations() { - constexpr bool debug_nr = false; + constexpr bool debug_nr = true; uint32_t sz = this->halfedge.size(); if constexpr (debug_nr) { std::cout << "Finding twins for " << sz << " halfedge\n"; } @@ -507,21 +488,38 @@ namespace mplot // It's useful to know how long you will have to wait... if (i % 20000u == 0u) { std::cout << ((100.0f * i)/sz) << " percent...\n" << std::endl; } - uint32_t sb = i >= band ? i - band : 0; - uint32_t eb = i + band < sz ? i + band : sz; + [[maybe_unused]] uint32_t sb = i >= band ? i - band : 0; + [[maybe_unused]] uint32_t eb = i + band < sz ? i + band : sz; + uint32_t wider = 0; + +#if 1 // Simplest code + for (uint32_t j = 0; j < sz; ++j) { + if (j == i) { continue; } + const sm::vec& vij = this->halfedge[j].vi; + if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match. + if (j == 1105771 || i == 1105771) { + std::cout << "halfedge["<halfedge[i].twin = j; + this->halfedge[j].twin = i; + break; + } + } +#else // Optimize // First sb to eb, which we hope is most likely to find a twin for (uint32_t j = sb; j < eb; ++j) { if (j == i) { continue; } const sm::vec& vij = this->halfedge[j].vi; - if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match + if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match. this->halfedge[i].twin = j; this->halfedge[j].twin = i; break; } } - uint32_t wider = 0; if (this->halfedge[i].twin == std::numeric_limits::max()) { // Then, if no match, search from 0 to sb if (sb != 0 && !wider) { wider = 1; } @@ -549,7 +547,7 @@ namespace mplot } } } - +#endif wider_searches += wider; if (this->halfedge[i].twin != std::numeric_limits::max()) { diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 9451e590..ee8bbc26 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -429,16 +429,21 @@ namespace mplot if (mplot::tools::fileExists (filename)) { this->navmesh->load (filename); + this->navmesh->test(); + } else if (mplot::tools::fileExists (filename_pre_boundary)) { std::cout << "Pre-boundary navmesh\n"; this->navmesh->load (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); + this->navmesh->test(); + //this->navmesh->save (filename); } else { std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); this->navmesh->save (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); - this->navmesh->save (filename); + //this->navmesh->test(); + //this->navmesh->save (filename); } } From cf269d3c88028ef0e04633fff4aaa3e870fc8172 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 11 Feb 2026 17:06:50 +0000 Subject: [PATCH 53/82] Ok, got code to know if a navmesh built from a model is good or not. --- maths | 2 +- mplot/NavMesh.h | 80 +++++++++++++++++++++---------- mplot/VisualModelBase.h | 101 ++++++++++++++++++++++------------------ 3 files changed, 112 insertions(+), 71 deletions(-) diff --git a/maths b/maths index 0159d047..67a41672 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 0159d04745089efafaacf1be8ee3a9041db9953f +Subproject commit 67a4167212fcebf86469bf9f7eb5abfdd9685db4 diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 7cdee3ee..bb508935 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -193,15 +193,32 @@ namespace mplot return i; } - bool verify_halfedge_chain (const uint32_t _hi, const uint32_t chain_length = std::numeric_limits::max()) const + bool verify_halfedge_chain (const uint32_t _hi, + const uint32_t chain_length = std::numeric_limits::max(), + const bool debug = false) const { constexpr uint32_t max = std::numeric_limits::max(); if (_hi >= this->halfedge.size()) { return false; } - // Each boundary should be the same forwards and backwards from every possible start halfedge - uint32_t fcount = 0; + uint32_t fcount = 0u; uint32_t fhi = _hi; + + if (debug) { + // Loop through once showing chain info (index, prev, next, twin) + do { + std::cout << "(prev: " << this->halfedge[fhi].prev << ") halfedge[" + << fhi << "] (next: " << this->halfedge[fhi].next << ") has twin " + << this->halfedge[fhi].twin << " and flags " << this->halfedge[fhi].flags << std::endl; + fhi = this->halfedge[fhi].next; + ++fcount; + } while (fhi != _hi && fhi != max && fcount < this->halfedge.size()); + // Reset for forward again: + fcount = 0u; + fhi = _hi; + } + + // Each boundary should be the same forwards and backwards from every possible start halfedge do { //std::cout << "this->halfedge["<halfedge[fhi].flags << " go to next..." << std::endl; if (this->halfedge[fhi].next == fhi) { throw std::runtime_error ("self next referral!"); } @@ -209,9 +226,11 @@ namespace mplot ++fcount; } while (fhi != _hi && fhi != max && fcount < this->halfedge.size()); - //std::cout << "fcount = " << fcount << " and fhi = " << fhi << " cf _hi = " << _hi << std::endl; + if (debug) { + std::cout << "From forwards loop: fcount = " << fcount << " and fhi = " << fhi << " cf _hi = " << _hi << std::endl; + } - uint32_t rcount = 0; + uint32_t rcount = 0u; uint32_t rhi = _hi; do { //std::cout << "this->halfedge["<halfedge[rhi].flags << " go to prev..." << std::endl; @@ -220,7 +239,9 @@ namespace mplot ++rcount; } while (rhi != _hi && rhi != max && rcount < this->halfedge.size()); - //std::cout << "rcount = " << rcount << " and rhi = " << rhi << " cf _hi = " << _hi << std::endl; + if (debug) { + std::cout << "From reverse loop: rcount = " << rcount << " and rhi = " << rhi << " cf _hi = " << _hi << std::endl; + } if (fcount == rcount && rhi == _hi && fhi == _hi) { if (chain_length != max) { @@ -236,8 +257,10 @@ namespace mplot return false; } } - bool verify_boundary (const uint32_t boundary_hi) const { return verify_halfedge_chain (boundary_hi); } - bool verify_triangle (const uint32_t tri_hi) const { return verify_halfedge_chain (tri_hi, 3); } + bool verify_boundary (const uint32_t boundary_hi, const bool debug = false) const + { return verify_halfedge_chain (boundary_hi, std::numeric_limits::max(), debug); } + bool verify_triangle (const uint32_t tri_hi, const bool debug = false) const + { return verify_halfedge_chain (tri_hi, 3, debug); } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex sm::vec, 3> triangle_vertices (uint32_t tri_hi) const @@ -319,30 +342,32 @@ namespace mplot do { if (repeat.count (hi)) { // hi is a repeat. This means the mesh isn't perfect. - std::cout << "find_neighbours: Found a repeated halfedge that wasn't the first one; break (imperfect mesh)\n"; #if 1 + std::cout << "find_neighbours: Found a repeated halfedge that wasn't the first one\n"; for (auto h : rtn) { std::cout << h << " flag: " << this->halfedge[h].flags << " prev: " << this->halfedge[h].prev << " prev.twin: " << this->halfedge[this->halfedge[h].prev].twin << std::endl; } std::cout << "The repeat was: " << hi << std::endl; - throw std::runtime_error ("Repeated halfedge"); // caused by 2 boundary halfedges with the same 'prev' #endif - break; + throw std::runtime_error ("Repeated non-start halfedge"); // caused by 2 boundary halfedges with the same 'prev' } // hi emanates from the vertex, so return it. rtn.push_back (hi); repeat.insert (hi); uint32_t pr = this->halfedge[hi].prev; - if (pr != std::numeric_limits::max()) { - hi = this->halfedge[this->halfedge[hi].prev].twin; - // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise - } else { - hi = pr; // pr was max, so set hi to max too + if (pr == std::numeric_limits::max()) { + throw std::runtime_error ("halfedge.prev was unset!?!"); } - + hi = this->halfedge[pr].twin; + // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise } while (hi != a && hi != std::numeric_limits::max()); + + if (hi == std::numeric_limits::max()) { + throw std::runtime_error ("A twin was unset!?!"); + } + return rtn; } @@ -369,7 +394,6 @@ namespace mplot } throw std::runtime_error ("too many neighbours?"); } - // std::cout << "num neighbours = " << nb.size() << std::endl; } } } @@ -421,6 +445,8 @@ namespace mplot // The bcand we have right now is NOT a boundary halfedge, nor is it the // twin for cur, so just mark halfedge flags with the 'rogue' flag (0x2) // which can be used by NormalsVisual to show the offending halfedge + bool rogue_is_tri = this->verify_triangle (cur, true); + std::cout << "Identified a rogue, which " << (rogue_is_tri ? "IS" : "ISN'T") << " a triangle.\n"; this->halfedge[cur].flags |= 0x2; } else { // Now we add the new halfedge twin for cur. @@ -452,6 +478,17 @@ namespace mplot if constexpr (debug_bnd) { std::cout << "Added " << (j - j0) << " halfedges to that boundary\n"; } } } + + // Lastly - check through for rogues! + std::cout << "Post-search for rogues\n"; + for (uint32_t i = 0; i < sz; ++i) { + if (this->halfedge[i].twin == max) { + bool rogue_is_tri = this->verify_triangle (i, true); + std::cout << "Identified a rogue, which " << (rogue_is_tri ? "IS" : "ISN'T") << " a triangle.\n"; + // How to remove? + } + } + if constexpr (debug_bnd) { std::cout << __func__ << " returning\n"; } } @@ -493,16 +530,11 @@ namespace mplot uint32_t wider = 0; -#if 1 // Simplest code +#if 0 // Simplest code for (uint32_t j = 0; j < sz; ++j) { if (j == i) { continue; } const sm::vec& vij = this->halfedge[j].vi; if (vi[0] == vij[1] && vi[1] == vij[0]) { // It's a match. - if (j == 1105771 || i == 1105771) { - std::cout << "halfedge["<halfedge[i].twin = j; this->halfedge[j].twin = i; break; diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index ee8bbc26..0f6e2eb8 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -254,6 +254,16 @@ namespace mplot return (*vn)[vec_idx]; } + // Get the area of the triangle whose start index is vec_idx + float get_area (const uint32_t vec_idx0, const uint32_t vec_idx1, const uint32_t vec_idx2) const + { + auto vp = reinterpret_cast>*>(&this->vertexPositions); + auto t0 = (*vp)[vec_idx0]; + auto t1 = (*vp)[vec_idx1]; + auto t2 = (*vp)[vec_idx2]; + return sm::geometry::tri_area (t0, t1, t2); + } + /** * Neighbour vertex mesh code. */ @@ -286,26 +296,20 @@ namespace mplot for (auto idx : e.second) { std::cout << idx << ","; } std::cout << std::endl; } - std::cout << "build_navmesh: Populated equiv which has " - << equiv.size() << " vvecs" << std::endl; + std::cout << "build_navmesh: Populated equiv which has " << equiv.size() << " vvecs" << std::endl; } // Make inverse of equiv to translate from original (indices, vertexPositions) index to // new topographic mesh index sm::vvec navmesh_idx (vps, 0); - - // Maps index in vertex to the original parent->indices index. Was originally a member - // of NavMesh - sm::vvec> vertexidx_to_indices (equiv.size()); - uint32_t vcount = 0; i = 0; for (auto eqs : equiv) { vcount += eqs.second.size(); - vertexidx_to_indices[i].resize (eqs.second.size()); - std::copy (eqs.second.begin(), eqs.second.end(), vertexidx_to_indices[i].begin()); for (auto ev : eqs.second) { - if constexpr (debug_mn) { std::cout << "build_navmesh: set navmesh_idx[" << ev << "] = " << i << std::endl; } + if constexpr (debug_mn) { + std::cout << "build_navmesh: set navmesh_idx[" << ev << "] = " << i << std::endl; + } navmesh_idx[ev] = i; } ++i; @@ -324,45 +328,49 @@ namespace mplot for (auto eq : equiv) { navmesh->vertex[i++] = { (*vp)[eq.first], std::numeric_limits::max() }; } + + // We're turing a triangle mesh into a navmesh. Don't know what to do if there are stray vertices. + if (this->indices.size() % 3u != 0u) { + throw std::runtime_error ("Uh oh, indices size not divisible by 3!!!! Call the cops!"); + } + // Lastly, generate edges. For which we require use of indices, which is expressed in // terms of the old indices. That lookup is navmesh_idx. for (uint32_t i = 0; i < this->indices.size(); i += 3) { - constexpr uint32_t mlines = 10000; // Add three halfedges for the triangle - uint32_t hesz = navmesh->halfedge.size(); - navmesh->halfedge.resize (hesz + 3, {}); - uint32_t he0 = hesz; - uint32_t he1 = hesz + 1; - uint32_t he2 = hesz + 2; + const uint32_t hesz = navmesh->halfedge.size(); + const uint32_t he0 = hesz; + const uint32_t he1 = hesz + 1; + const uint32_t he2 = hesz + 2; if constexpr (debug_mn) { - if (hesz < mlines) { - std::cout << "setting halfedge["<halfedge[hesz] = { {navmesh_idx[indices[i]], navmesh_idx[indices[i + 1]]}, std::numeric_limits::max(), he1, he2 }; - navmesh->halfedge[hesz + 1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, std::numeric_limits::max(), he2, he0 }; - navmesh->halfedge[hesz + 2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i]] }, std::numeric_limits::max(), he0, he1 }; + + navmesh->halfedge.resize (hesz + 3, {}); + + // Now, could also try to identify LINES + navmesh->halfedge[he0] = { {navmesh_idx[indices[i ]], navmesh_idx[indices[i + 1]]}, std::numeric_limits::max(), he1, he2, 0u }; + navmesh->halfedge[he1] = { {navmesh_idx[indices[i + 1]], navmesh_idx[indices[i + 2]]}, std::numeric_limits::max(), he2, he0, 0u }; + navmesh->halfedge[he2] = { {navmesh_idx[indices[i + 2]], navmesh_idx[indices[i ]]}, std::numeric_limits::max(), he0, he1, 0u }; if constexpr (debug_mn) { - if (hesz < mlines) { - std::cout << "halfedge["<< hesz << "] contains: vi:" - << navmesh->halfedge[hesz].vi - << ", twin:" << navmesh->halfedge[hesz].twin - << ", next:" << navmesh->halfedge[hesz].next - << ", prev:" << navmesh->halfedge[hesz].prev << std::endl; - } + std::cout << "halfedge["<< hesz << "] contains: vi:" + << navmesh->halfedge[hesz].vi + << ", twin:" << navmesh->halfedge[hesz].twin + << ", next:" << navmesh->halfedge[hesz].next + << ", prev:" << navmesh->halfedge[hesz].prev << std::endl; } // A face contains just the first half edge index mesh::face<> t = { he0 }; @@ -371,7 +379,7 @@ namespace mplot // we can't trust them (though they're easy to get, as we're dealing with indices // already). However, use this to ensure that our triangle indices order is in // agreement with mesh normal as far as direction goes. - sm::vec tn = this->get_normal (indices[i]) + this->get_normal (indices[i+1]) + this->get_normal (indices[i+2]) ; + sm::vec tn = this->get_normal (indices[i]) + this->get_normal (indices[i + 1]) + this->get_normal (indices[i + 2]) ; tn.renormalize(); // Compute trinorm as well and compare with the one from the mesh - perhaps it's @@ -386,10 +394,11 @@ namespace mplot // Check rotational sense of triangles if (n.dot (tn) < 0.0f) { + std::cout << "Swap order of triangle with he " << he0 << std::endl; // Swap first and last half edge - navmesh->halfedge[hesz].vi.rotate(); - navmesh->halfedge[hesz + 1].vi.rotate(); - navmesh->halfedge[hesz + 2].vi.rotate(); + navmesh->halfedge[he0].vi.rotate(); + navmesh->halfedge[he1].vi.rotate(); + navmesh->halfedge[he2].vi.rotate(); } navmesh->triangles.push_back (t); } @@ -436,14 +445,14 @@ namespace mplot this->navmesh->load (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); this->navmesh->test(); - //this->navmesh->save (filename); + this->navmesh->save (filename); } else { std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); this->navmesh->save (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); - //this->navmesh->test(); - //this->navmesh->save (filename); + this->navmesh->test(); + this->navmesh->save (filename); } } From 7cf7aa3388d1f3d370296e71929309519d4105ee Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 11 Feb 2026 19:18:06 +0000 Subject: [PATCH 54/82] Make a test_neighbours function and a leaner find_neighbours function for perfect navmeshes --- mplot/NavMesh.h | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index bb508935..178ffd8a 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -328,10 +328,11 @@ namespace mplot return v1 - v0; } - // Find all the neighbours of triangle *vertex* index a. + // Find all the neighbours of triangle *vertex* index a, throwing exceptions on errors. + // Returns the same vector of halfedge indices as find_neighbours, but without assuming that the navmesh is good. // \return vector of halfedge indices std::vector - find_neighbours (uint32_t a) const + test_neighbours (const uint32_t a) const { uint32_t hi = a; std::vector rtn = {}; @@ -342,15 +343,13 @@ namespace mplot do { if (repeat.count (hi)) { // hi is a repeat. This means the mesh isn't perfect. -#if 1 - std::cout << "find_neighbours: Found a repeated halfedge that wasn't the first one\n"; + std::cout << "test_neighbours: Found a repeated halfedge that wasn't the first one\n"; for (auto h : rtn) { std::cout << h << " flag: " << this->halfedge[h].flags << " prev: " << this->halfedge[h].prev << " prev.twin: " << this->halfedge[this->halfedge[h].prev].twin << std::endl; } std::cout << "The repeat was: " << hi << std::endl; -#endif throw std::runtime_error ("Repeated non-start halfedge"); // caused by 2 boundary halfedges with the same 'prev' } // hi emanates from the vertex, so return it. @@ -364,10 +363,25 @@ namespace mplot // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise } while (hi != a && hi != std::numeric_limits::max()); - if (hi == std::numeric_limits::max()) { - throw std::runtime_error ("A twin was unset!?!"); - } + if (hi == std::numeric_limits::max()) { throw std::runtime_error ("A twin was unset!?!"); } + return rtn; + } + // Find all the neighbours of triangle *vertex* index a. + // Assumes the navmesh is good, and has passed NavMesh::test() + // \return vector of halfedge indices + std::vector find_neighbours (const uint32_t a) const + { + uint32_t hi = a; + std::vector rtn = {}; + if (hi > this->halfedge.size()) { return rtn; } + do { + // hi emanates from the vertex, so return it. + rtn.push_back (hi); + uint32_t pr = this->halfedge[hi].prev; + hi = this->halfedge[pr].twin; + // or hi = this->halfedge[this->halfedge[hi].twin].next; // Clockwise + } while (hi != a); return rtn; } @@ -387,7 +401,7 @@ namespace mplot throw std::runtime_error ("Imperfect halfedge mesh (face triangle)"); } - std::vector nb = this->find_neighbours (hi); + std::vector nb = this->test_neighbours (hi); if (nb.size() > 100) { for (auto n : nb) { std::cout << n << std::endl; @@ -395,6 +409,10 @@ namespace mplot throw std::runtime_error ("too many neighbours?"); } } + // Make sure twin is set for every halfedge + if (this->halfedge[hi].twin == std::numeric_limits::max()) { + throw std::runtime_error ("Contains an untwinned halfedge"); + } } } @@ -446,7 +464,8 @@ namespace mplot // twin for cur, so just mark halfedge flags with the 'rogue' flag (0x2) // which can be used by NormalsVisual to show the offending halfedge bool rogue_is_tri = this->verify_triangle (cur, true); - std::cout << "Identified a rogue, which " << (rogue_is_tri ? "IS" : "ISN'T") << " a triangle.\n"; + std::cout << "Identified a rogue, which " << (rogue_is_tri ? "IS" : "ISN'T") + << " a triangle; navmesh not likely to be usable\n"; this->halfedge[cur].flags |= 0x2; } else { // Now we add the new halfedge twin for cur. From 7f3b1a84096b75769991f7e7b995714f33d3c32b Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 11 Feb 2026 19:22:06 +0000 Subject: [PATCH 55/82] Up to date maths --- maths | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths b/maths index 67a41672..9bb5b4e4 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 67a4167212fcebf86469bf9f7eb5abfdd9685db4 +Subproject commit 9bb5b4e471e3b5f013e203c125d12229140440de From e40e68c5acabf1c93c26638e99d8f934fbcaf6cf Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 11 Feb 2026 19:26:23 +0000 Subject: [PATCH 56/82] Revert optimization flag --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c27e78b6..8280c82f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,7 +52,7 @@ else() endif() endif() set(COMPREHENSIVE_WARNING_FLAGS "-Wall -Wextra -Wpedantic -pedantic-errors -Werror -Wfatal-errors -Wno-psabi") - set(CMAKE_CXX_FLAGS "-g ${COMPREHENSIVE_WARNING_FLAGS} -O0") + set(CMAKE_CXX_FLAGS "-g ${COMPREHENSIVE_WARNING_FLAGS} -O3") if(CMAKE_CXX_COMPILER_ID MATCHES GNU) # Make it possible to compile complex constexpr functions (gcc only) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fconstexpr-ops-limit=5000000000") From 543d25ab6edd2d659ddd6fb0f9272acfc473e567 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 09:26:42 +0000 Subject: [PATCH 57/82] Showing anomaly locations --- mplot/NormalsVisual.h | 1 + mplot/VisualModelBase.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 138554b5..af1ca158 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -133,6 +133,7 @@ namespace mplot this->computeArrow (p0, p1, mplot::colour::yellow, tube_r, this->arrowhead_prop, cone_r, this->shapesides); } + this->computeSphere (((p0 + p1) / 2.0f) + sm::vec<>::uy() * 0.2f, mplot::colour::yellow, 0.1f); } else if ((h.flags & 0x1) == 0x1) { // boundary if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges})) { diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 0f6e2eb8..64d2802e 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -444,8 +444,8 @@ namespace mplot std::cout << "Pre-boundary navmesh\n"; this->navmesh->load (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); - this->navmesh->test(); - this->navmesh->save (filename); + //this->navmesh->test(); + //this->navmesh->save (filename); } else { std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); From fbdb02c43cea4c209f886cd54bab520b62b82946 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 14:37:13 +0000 Subject: [PATCH 58/82] Show rogue position (in Blender coords) --- mplot/NormalsVisual.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index af1ca158..216fe270 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -127,13 +127,19 @@ namespace mplot auto p1 = mymodel->navmesh->vertex[h.vi[1]].p; if ((h.flags & 0x2) == 0x2) { // special/rogue - std::cout << "Showing a rogue!\n"; + sm::vec<> rpos = ((p0 + p1) / 2.0f); + std::cout << "Showing a rogue at " + << (mplot::compoundray::blender_transform_mat() * p0).less_one_dim() + << " " << rpos.length() << " from origin\n" + << " otherend: " << (mplot::compoundray::blender_transform_mat() * p1).less_one_dim() << std::endl; + + if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges, normalsvisual_flags::show_inner_halfedges})) { this->computeArrow (p0, p1, mplot::colour::yellow, tube_r, this->arrowhead_prop, cone_r, this->shapesides); } - this->computeSphere (((p0 + p1) / 2.0f) + sm::vec<>::uy() * 0.2f, mplot::colour::yellow, 0.1f); + this->computeSphere (rpos + sm::vec<>::uy() * 0.2f, mplot::colour::yellow, 0.1f); } else if ((h.flags & 0x1) == 0x1) { // boundary if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges})) { From 4d59fc36bb36f34b72847455f5a79c92354082e1 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 14:37:41 +0000 Subject: [PATCH 59/82] Adds mat<> returning blender_transform_mat --- mplot/compoundray/interop.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mplot/compoundray/interop.h b/mplot/compoundray/interop.h index 134abc9f..c97fc8c4 100644 --- a/mplot/compoundray/interop.h +++ b/mplot/compoundray/interop.h @@ -36,15 +36,20 @@ namespace mplot::compoundray // Blender has a transformation to convert the native y-up OpenGL/GLTF coordinate system into a // z-up coordinate system. To work in Blender, we need a "match blender" mode in which we apply - // the same transform. This function returns the matrix that should be passed to + // the same transform. This function returns the matrix (as sm::mat) that should be passed to // libEyeRenderer's loadGlTFscene + sm::mat blender_transform_mat() + { + return sm::mat::frombasis (sm::vec::ux(), sm::vec::uz(), -sm::vec::uy()); + } + + // Blender has a transformation to convert the native y-up OpenGL/GLTF coordinate system into a + // z-up coordinate system. To work in Blender, we need a "match blender" mode in which we apply + // the same transform. This function returns the matrix (as sutil::Matrix4x4) that should be + // passed to libEyeRenderer's loadGlTFscene sutil::Matrix4x4 blender_transform() { - constexpr sm::vec ux = { 1.0f, 0.0f, 0.0f }; - constexpr sm::vec uy = { 0.0f, 1.0f, 0.0f }; - constexpr sm::vec uz = { 0.0f, 0.0f, 1.0f }; - sm::mat world_transform = sm::mat::frombasis (ux, uz, -uy); - return mplot::compoundray::mat44_to_Matrix4x4 (world_transform); + return mplot::compoundray::mat44_to_Matrix4x4 (mplot::compoundray::blender_transform_mat()); } /*! From 943f50b0138648d7609f8dfeaf70a0e06c2b3d20 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 14:38:39 +0000 Subject: [PATCH 60/82] Use openmp in navmesh computation --- mplot/NavMesh.h | 1 + 1 file changed, 1 insertion(+) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 178ffd8a..37030a35 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -534,6 +534,7 @@ namespace mplot uint64_t twin_meandist = 0; // See how far a search has to search for a twin uint32_t twins = 0; +#pragma omp parallel for for (uint32_t i = 0; i < sz; ++i) { const sm::vec& vi = this->halfedge[i].vi; From aabbd9d238c43206a99562a89c6298b20a441ff9 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 15:16:52 +0000 Subject: [PATCH 61/82] twin is not a tri should include off-edge: in its message --- mplot/NavMesh.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 37030a35..0426d607 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1334,7 +1334,8 @@ namespace mplot // Test to see if start location was inside this twin sm::vec, 3> tv_lf = this->triangle_vertices (twin, model_to_scene); if (tv_lf[0][0] == std::numeric_limits::max()) { - throw std::runtime_error ("twin is not a triangle"); + // This probably means we've attempted to go over a boundary. client code should turn around + throw std::runtime_error ("off-edge: twin is not a triangle"); } sm::vec _tn = this->triangle_normal (tv_lf); auto [is, h] = sm::geometry::ray_tri_intersection (tv_lf[0], tv_lf[1], tv_lf[2], camloc_sf + (_tn / 2.0f), -_tn); From 1c9365326ec4e0ef79eed45ccc11b8078993fcdf Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 15:47:18 +0000 Subject: [PATCH 62/82] Avoid need for mplot/compoundray/interop.h --- mplot/NormalsVisual.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 216fe270..59356caf 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -128,10 +129,12 @@ namespace mplot if ((h.flags & 0x2) == 0x2) { // special/rogue sm::vec<> rpos = ((p0 + p1) / 2.0f); + // output text position of rogue half edge in Blender (not glTF/OpenGL y-up) z-up coordinates, so need this transform: + auto to_blender = sm::mat::frombasis (sm::vec::ux(), sm::vec::uz(), -sm::vec::uy()); std::cout << "Showing a rogue at " - << (mplot::compoundray::blender_transform_mat() * p0).less_one_dim() + << (to_blender * p0).less_one_dim() << " " << rpos.length() << " from origin\n" - << " otherend: " << (mplot::compoundray::blender_transform_mat() * p1).less_one_dim() << std::endl; + << " otherend: " << (to_blender * p1).less_one_dim() << std::endl; if (this->options.any_of ({normalsvisual_flags::show_halfedges, normalsvisual_flags::show_boundary_halfedges, normalsvisual_flags::show_inner_halfedges})) { From 4baac6c4055cea4b47702a4816b5083a8598848d Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 12 Feb 2026 16:09:41 +0000 Subject: [PATCH 63/82] Test and save in each case here. --- mplot/VisualModelBase.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 64d2802e..0f6e2eb8 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -444,8 +444,8 @@ namespace mplot std::cout << "Pre-boundary navmesh\n"; this->navmesh->load (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); - //this->navmesh->test(); - //this->navmesh->save (filename); + this->navmesh->test(); + this->navmesh->save (filename); } else { std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); From d2659ba8d4ba56742e3a8a74ac051777e316106d Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 13 Feb 2026 12:31:41 +0000 Subject: [PATCH 64/82] Brings in a fix in geometry --- maths | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths b/maths index 9bb5b4e4..df93ca2e 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 9bb5b4e471e3b5f013e203c125d12229140440de +Subproject commit df93ca2e8d496a0d9eda9c7fd2f2a1c5dd7ef01a From e243f10108f1262fc99085b0a8b2ce6ac043dfc3 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 13 Feb 2026 13:31:31 +0000 Subject: [PATCH 65/82] Updated maths geometry PR branch --- maths | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths b/maths index df93ca2e..e3a9aef3 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit df93ca2e8d496a0d9eda9c7fd2f2a1c5dd7ef01a +Subproject commit e3a9aef359d802545d4bccdf9120921e54d68d31 From 9b94a214697b9a65b9961a04052143fd7ace289b Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 13 Feb 2026 19:23:16 +0000 Subject: [PATCH 66/82] Debugging code, chasing movements that land on triangle edge/borders --- maths | 2 +- mplot/NavMesh.h | 66 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/maths b/maths index e3a9aef3..62c82eea 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit e3a9aef359d802545d4bccdf9120921e54d68d31 +Subproject commit 62c82eeac6d56892fb143e49d645bd8ac49a4437 diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 0426d607..20e3f4fa 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1339,7 +1339,7 @@ namespace mplot } sm::vec _tn = this->triangle_normal (tv_lf); auto [is, h] = sm::geometry::ray_tri_intersection (tv_lf[0], tv_lf[1], tv_lf[2], camloc_sf + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in twin " << twin << std::endl; } + if constexpr (debug_move) { std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in twin " << twin << ", " << tv_lf << std::endl; } if (is) { if constexpr (debug_move) { std::cout << "CORRECT ti0 to the twin " << twin << std::endl; } // We're in this neighbour, so update ti0/tn0 and mark isect true @@ -1442,7 +1442,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "\nWHILE LOOP\n" - << "ti0 = (" << this->ti0 << ")\n" + << "ti0 = (" << this->ti0 << ") = " << tv_sf << "\n" << "mv_inplane: " << hov_sf << "," << mv_inplane << "\n" << "tn0 = " << tn0 << ")\n"; } @@ -1458,7 +1458,7 @@ namespace mplot crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane); if (cd.pm.flags.test (pm_fl::no_cross_point) == false || flags.test (cmm_fl::detected_crossing) || flags.test (cmm_fl::vertex_crossing)) { - // Then an edge (or vertex) crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing') + std::cout << "A) Then an edge (or vertex) crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing')\n"; if (flags.test (cmm_fl::detected_crossing)) { if constexpr (debug_move) { std::cout << "This is a detected crossing; changing crossing_data.halfedge to " << detected_edge << std::endl; } @@ -1496,10 +1496,8 @@ namespace mplot throw std::runtime_error ("off-edge: The movement went off the edge of the model"); continue; } else { - - // Check that _ti is a triangle, else we were on the boundary + // Check that _ti is a triangle, else we were on the model boundary // BUT newtv_sf[0][0] check should already have done this?!? - _tn = this->triangle_normal (newtv_sf); if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } @@ -1553,21 +1551,35 @@ namespace mplot cam_to_surface = reorient_model * cam_to_surface; flags.set (cmm_fl::done, true); } else { + if constexpr (debug_move) { std::cout << "did we sail past or land on the boundary or land in a 1-neighbour?\n"; } // Incomplete; We've sailed past newtv_sf. Or perhaps landed on the boundary??? - // Can now test if we went beyond the boundary. + // Can now test if we went beyond the boundary? - // We need to - // set an end-point that is on newtv_sf, update hov_sf, - // then recurse. also recompute the movement encoded in - // reorient_model - reorient_model.pretranslate (-mv_rest); - cam_to_surface = reorient_model * cam_to_surface; - hov_sf = cd.pm.end; // crossing data planned movement end - // Also update planned move, which is now shorter and in a new direction - tv_sf = newtv_sf; - mv_inplane = mv_rest; + // Think I HAVE to here. On next loop (4th WHILE LOOP in my debug example), mv_inplane is weird - too long. + // Use dist_to_lineseg?? + for (uint32_t i = 0; i < 3; ++i) { + float d2 = sm::geometry::dist_to_lineseg (newtv_sf[i], newtv_sf[(i + 1) % 3u], endmv); + std::cout << "Dist to triedge: " << d2 << std::endl; + if (d2 <= std::numeric_limits::epsilon()) { + cam_to_surface = reorient_model * cam_to_surface; + flags.set (cmm_fl::done, true); + } + } + + if (!flags.test (cmm_fl::done)) { + // We need to + // set an end-point that is on newtv_sf, update hov_sf, + // then recurse. also recompute the movement encoded in + // reorient_model + reorient_model.pretranslate (-mv_rest); + cam_to_surface = reorient_model * cam_to_surface; + hov_sf = cd.pm.end; // crossing data planned movement end + // Also update planned move, which is now shorter and in a new direction + tv_sf = newtv_sf; + mv_inplane = mv_rest; + } } } @@ -1582,6 +1594,7 @@ namespace mplot } } else { // NO triangle edge crossing was detected with compute_crossing_location + std::cout << "B) NOT: Then an edge (or vertex) crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing')\n"; // We had intersection in ti0, but no apparent crossing over its edges. // We may have moved entirely within the starting triangle or colinear with an edge. Test for these cases. @@ -1599,7 +1612,7 @@ namespace mplot std::cout << "No cross point and not colinear.\n Testing if " << (hov_sf + mv_inplane + (tn0 / 2.0f)) << "," << -tn0 << " intersects tv_sf (" << tv_sf << "\n"; } - auto [single_mv, he] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], hov_sf + mv_inplane + (tn0 / 2.0f), -tn0); + auto [single_mv, he] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], hov_sf + mv_inplane + (tn0 / 2.0f), -tn0); flags.set (cmm_fl::single_movement, single_mv); } @@ -1610,7 +1623,10 @@ namespace mplot flags.set (cmm_fl::done, true); } else { - if constexpr (debug_move) { std::cout << "End of movement is NOT in ti0 " << this->ti0 << ". Look for start neighbours\n"; } + if constexpr (debug_move) { + std::cout << "End of movement is NOT in ti0 " << this->ti0 // But it might be ON the boundary + << ". Look for start neighbours\n"; // << Why 'look for *start* neighbours'? + } // Test neighbours, new scheme using halfedge data structures // Test neighbours to find any for which the start location is also within-boundary @@ -1636,17 +1652,17 @@ namespace mplot } else { _tn = this->triangle_normal (tv_nb); if constexpr (debug_move) { - std::cout << "TN: " << twin << ": START isect vector " << (hov_sf + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; + std::cout << "TN: " << twin << ": START isect vector " << (hov_sf + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; } auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; if constexpr (debug_move) { - std::cout << "TN: " << twin << ": END isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb; + std::cout << "TN: " << twin << ": END isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; } auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); if constexpr (debug_move) { - std::cout << " Start IN? " << (is ? "Y" : "N") << "; End IN? " << (endis ? "Y" : "N") << std::endl; + std::cout << "Start IN? " << (is ? "Y" : "N") << "; End IN? " << (endis ? "Y" : "N") << std::endl; } neighbours_tested.insert (twin); @@ -1658,6 +1674,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "DETECTED crossing (end of movement intesects two-neighbour). set detected_crossing flag.\n"; } flags.set (cmm_fl::detected_crossing, true); detected_edge = hi; + std::cout << "Start in neighbour AND end in neighbour exit do-while\n"; hi = this->ti0; // to end the do-while break; // out of for @@ -1665,10 +1682,14 @@ namespace mplot if (is) { // start is in neighbour tri (will re-orient to this and re-loop) _ti_2n = twin; _tn_2n = _tn; + std::cout << "Start in neighbour, end NOT in neighbour exit do-while\n"; hi = this->ti0; // to end the do-while break; // out of for } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors // mean this location isn't 'in' either start or neighbour (according to ray_tri_intersection) + else { + std::cout << "Start not in neighbour, end not in neighbour...\n"; + } } } } @@ -1734,6 +1755,7 @@ namespace mplot tn0 = _tn; // recompute mv_inplane for this neighbour triangle mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); + if constexpr (debug_move) { std::cout << "RECOMPUTE mv_inplane, too (think here is a problem cause)\n"; } mv_inplane = mv_sf - mv_orthog; // sf } else if (flags.test (cmm_fl::detected_crossing)) { // We didn't find an alternative start triangle, but we did detect an edge crossing by intersection, so continue. From c22dfe44f5e0f2c06022c30f9f3ae44772256945 Mon Sep 17 00:00:00 2001 From: Seb James Date: Sun, 15 Feb 2026 18:32:13 +0000 Subject: [PATCH 67/82] Updates to model crawler and some improvements on what to do when landing on a boundary --- examples/CMakeLists.txt | 6 ++-- examples/model_crawler.cpp | 70 +++++++++++++++++++++++--------------- mplot/NavMesh.h | 55 ++++++++++++++++-------------- mplot/VisualResourcesMX.h | 1 + 4 files changed, 76 insertions(+), 56 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8168f544..37493591 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -260,6 +260,9 @@ if(NOT APPLE) target_link_libraries(scatter_instanced OpenGL::GL glfw Freetype::Freetype) add_executable(breadcrumbs breadcrumbs.cpp) target_link_libraries(breadcrumbs OpenGL::GL glfw Freetype::Freetype) + + add_executable(model_crawler model_crawler.cpp) + target_link_libraries(model_crawler OpenGL::GL glfw Freetype::Freetype) endif() add_executable(scatter_dynamic scatter_dynamic.cpp) @@ -393,9 +396,6 @@ if(NOT APPLE) target_link_libraries(geodesic_ce OpenGL::GL glfw Freetype::Freetype) endif() -add_executable(model_crawler model_crawler.cpp) -target_link_libraries(model_crawler OpenGL::GL glfw Freetype::Freetype) - add_executable(tri tri.cpp) target_link_libraries(tri OpenGL::GL glfw Freetype::Freetype) diff --git a/examples/model_crawler.cpp b/examples/model_crawler.cpp index 7ca951f1..0037a8bc 100644 --- a/examples/model_crawler.cpp +++ b/examples/model_crawler.cpp @@ -14,18 +14,22 @@ #include #include #include +#include + +#include +constexpr int32_t glver = mplot::gl::version_4_3; #include #include #include -#include +#include #include int main (int argc, char** argv) { int rtn = -1; - mplot::Visual v(1024, 768, "Crawling a surface with NavMesh features"); + mplot::Visual v(1024, 768, "Crawling a surface with NavMesh features"); v.rotateAboutNearest (true); // How big to make the sphere? @@ -46,33 +50,32 @@ int main (int argc, char** argv) sm::vec sphere_loc = {}; // A CoordArrows is our "crawling" agent - auto ca = std::make_unique> (arrows_loc); + auto ca = std::make_unique> (arrows_loc); v.bindmodel (ca); ca->finalize(); [[maybe_unused]] auto cap = v.addVisualModel (ca); - // A ScatterVisual will show the agent's trail - sm::vvec> sv_points; - sm::vvec sv_data; - auto sv = std::make_unique> (sphere_loc); - v.bindmodel (sv); - sv->setDataCoords (&sv_points); - sv->setScalarData (&sv_data); - sv->radiusFixed = 0.015f; - sv->cm.setType (mplot::ColourMapType::Plasma); - sv->colourScale.compute_scaling (0.0f, 1.0f); - sv->finalize(); - [[maybe_unused]] auto svp = v.addVisualModel (sv); // use svp->add (coord, value) + // Breadcrumb trail + uint64_t move_counter = 0u; + uint64_t max_bc = 1000; + sm::vvec> sv_points = {}; + sm::vvec sv_data = {}; + auto isv = std::make_unique> (sphere_loc); + v.bindmodel (isv); + isv->max_instances = max_bc; + isv->radiusFixed = 0.01f; + isv->finalize(); + auto isvp = v.addVisualModel (isv); // A sphere, approximated by an icosahedral geodesic, is our landscape mplot::ColourMap cm (mplot::ColourMapType::Jet); auto cl = cm.convert (0.5f); - auto gv = std::make_unique> (sphere_loc, radius); + auto gv = std::make_unique> (sphere_loc, radius); v.bindmodel (gv); gv->iterations = geo_itrns; std::string lbl = "GeodesicVisual with computed NavMesh"; gv->addLabel (lbl, {0, -(radius + 0.1f), 0}, mplot::TextFeatures (0.06f)); - gv->cm.setType (mplot::ColourMapType::Jet); + gv->cm.setType (mplot::ColourMapType::NaviaW); gv->colour_bb = cl; gv->finalize(); auto gvp = v.addVisualModel (gv); @@ -82,10 +85,15 @@ int main (int argc, char** argv) // Make the navmesh for the geodesic, this doesn't occur automatically and has to come after finalize() gvp->make_navmesh(); - // We're going to move the coordinate arrows forwards (along its z-axis), so that it 'orbits' - float move_step = 0.1f; // 0.075 <= move_step and iterations 6 to fail + // We're going to move the coordinate arrows forwards (along its z-axis) on each step + float move_step = 0.01f; sm::vec mv_ca = sm::vec::uz() * move_step; + // We'll also rotate by a small amount on each step, drawn from a Von Mises distribution + constexpr float mu = 0.0f; + constexpr float kappa = 3.0f; + sm::rand_vonmises rvm (mu, kappa); + // The viewmatrices have to be passed to mplot::NavMesh::compute_mesh_movement sm::mat ca_view = cap->getViewMatrix(); sm::mat sph_view = gvp->getViewMatrix(); @@ -96,15 +104,20 @@ int main (int argc, char** argv) auto[hp_scene, ti0] = gvp->navmesh->find_triangle_hit (ca_view, sph_view); std::cout << "Find hit finds hit point " << hp_scene << " with ti0 halfedge: " << ti0 << std::endl; - int move_counter = 0; - constexpr int move_max = 1000; + cap->setHide (true); + while (!v.readyToFinish()) { + // Render the scene. Make sure this happens before first call to set_instance_data + v.render(); + // Wait .018 s and also poll for mouse/keyboard events - v.waitevents (0.018); + v.waitevents (0.002); // Compute a new movement over the landscape mesh (the sphere) try { + // rotate ca_view each time by a little (randomly) + ca_view.rotate (sm::vec<>::uy(), rvm.get()); ca_view = gvp->navmesh->compute_mesh_movement (mv_ca, ca_view, sph_view, hoverheight); } catch (std::exception& e) { std::cout << "Exception navigating mesh at movement count " << move_counter << ": " << e.what() << std::endl; @@ -118,12 +131,15 @@ int main (int argc, char** argv) // We're adding and rebuilding the not-very-optimized ScatterVisual, so if move_max is too // high, the program will slow down (too many tiny spheres!) - if (move_counter++ < move_max) { - svp->add (arrows_loc, static_cast(move_counter) / move_max); + move_counter++; + // This should be the right place to update breadcrumbs + if (sv_points.size() < max_bc) { + sv_points.push_back (arrows_loc); + sv_data.push_back (0.0f); // dummy for now + } else { + sv_points[move_counter % max_bc] = arrows_loc; } - - // Re-render the scene - v.render(); + isvp->set_instance_data (sv_points); } v.keepOpen(); diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 20e3f4fa..6170995d 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1236,7 +1236,7 @@ namespace mplot const uint32_t ti0_save = this->ti0; // Boolean state flags used in this function - enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing }; + enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing, end_on_boundary }; sm::flags flags; // Camera location, scene frame @@ -1441,7 +1441,7 @@ namespace mplot while (!flags.test (cmm_fl::done)) { if constexpr (debug_move) { - std::cout << "\nWHILE LOOP\n" + std::cout << "\nWHILE LOOP, TRAVERSING TRIANGLES\n" << "ti0 = (" << this->ti0 << ") = " << tv_sf << "\n" << "mv_inplane: " << hov_sf << "," << mv_inplane << "\n" << "tn0 = " << tn0 << ")\n"; @@ -1552,21 +1552,8 @@ namespace mplot flags.set (cmm_fl::done, true); } else { - if constexpr (debug_move) { std::cout << "did we sail past or land on the boundary or land in a 1-neighbour?\n"; } - // Incomplete; We've sailed past newtv_sf. Or perhaps landed on the boundary??? - - // Can now test if we went beyond the boundary? - - // Think I HAVE to here. On next loop (4th WHILE LOOP in my debug example), mv_inplane is weird - too long. - // Use dist_to_lineseg?? - for (uint32_t i = 0; i < 3; ++i) { - float d2 = sm::geometry::dist_to_lineseg (newtv_sf[i], newtv_sf[(i + 1) % 3u], endmv); - std::cout << "Dist to triedge: " << d2 << std::endl; - if (d2 <= std::numeric_limits::epsilon()) { - cam_to_surface = reorient_model * cam_to_surface; - flags.set (cmm_fl::done, true); - } - } + if constexpr (debug_move) { std::cout << "did we sail past or land in a 1-neighbour?\n"; } + // Incomplete; We've sailed past newtv_sf. if (!flags.test (cmm_fl::done)) { // We need to @@ -1594,7 +1581,7 @@ namespace mplot } } else { // NO triangle edge crossing was detected with compute_crossing_location - std::cout << "B) NOT: Then an edge (or vertex) crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing')\n"; + std::cout << "B) An edge (or vertex) crossing was NOT detected (by compute_crossing_location or a prev. 'detected crossing')\n"; // We had intersection in ti0, but no apparent crossing over its edges. // We may have moved entirely within the starting triangle or colinear with an edge. Test for these cases. @@ -1625,7 +1612,7 @@ namespace mplot } else { if constexpr (debug_move) { std::cout << "End of movement is NOT in ti0 " << this->ti0 // But it might be ON the boundary - << ". Look for start neighbours\n"; // << Why 'look for *start* neighbours'? + << ". Checking neighbours, but Check BOUNDARY TOO; it might have landed there?\n"; } // Test neighbours, new scheme using halfedge data structures @@ -1645,6 +1632,7 @@ namespace mplot if (twin != std::numeric_limits::max()) { // Test to see if start location was inside a neighbour sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); + std::cout << "Checking two-neighbour " << twin << ": " << tv_nb << std::endl; if (tv_nb[0][0] == std::numeric_limits::max()) { // tv_nb is not a triangle std::cout << "Are we off-edge?"; @@ -1679,17 +1667,28 @@ namespace mplot break; // out of for } else { // end not in neighbour + + // Check if end is on boundary between self and twin here. + // Need the exact endmv on the triangle surface here + sm::vec endmv = hov_sf + mv_inplane; + float d2 = sm::geometry::dist_to_lineseg (tv_nb[0], tv_nb[1], endmv); + std::cout << "Dist from endmv: " << endmv << " to twin boundary: " << d2 << std::endl; + if (d2 <= std::numeric_limits::epsilon()) { + // Take appropriate action... + std::cout << "Movement ends ON that boundary: " << tv_nb[0] << "," << (tv_nb[1] - tv_nb[0]) << "\n"; + flags.set (cmm_fl::end_on_boundary); + hi = this->ti0; // to end the do-while + break; // out of for + } +#ifdef OLD_REORIENT_START_CODE if (is) { // start is in neighbour tri (will re-orient to this and re-loop) + std::cout << "Start in neighbour, end NOT in neighbour exit do-while\n"; _ti_2n = twin; _tn_2n = _tn; - std::cout << "Start in neighbour, end NOT in neighbour exit do-while\n"; hi = this->ti0; // to end the do-while break; // out of for } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors - // mean this location isn't 'in' either start or neighbour (according to ray_tri_intersection) - else { - std::cout << "Start not in neighbour, end not in neighbour...\n"; - } +#endif } } } @@ -1697,7 +1696,7 @@ namespace mplot hi = this->halfedge[hi].next; } while (hi != this->ti0); - +#if 0 // Test one-neighbours here if necessary (that is, if the two neighbour test above failed) if (flags.test (cmm_fl::detected_crossing) == false && _ti_2n == std::numeric_limits::max()) { uint32_t hi = this->ti0; @@ -1747,7 +1746,7 @@ namespace mplot } while (hi != this->ti0); } - +#endif if (_ti_2n != std::numeric_limits::max()) { // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop if constexpr (debug_move) { std::cout << "CHANGE ti0 to _ti_2n = " << _ti_2n << "\n"; } @@ -1761,6 +1760,10 @@ namespace mplot // We didn't find an alternative start triangle, but we did detect an edge crossing by intersection, so continue. } else if (flags.test (cmm_fl::vertex_crossing)) { // We didn't find an alternative start triangle, but we did detect a vertex crossing, so continue. + } else if (flags.test (cmm_fl::end_on_boundary)) { + if constexpr (debug_move2) { std::cout << "Movement complete on boundary (detected)\n"; } + cam_to_surface.pretranslate (mv_inplane); + flags.set (cmm_fl::done, true); } else { // End of move not evidently in self or neighbours, so assume it's bang on the boundary if constexpr (debug_move2) { std::cout << "Movement complete on boundary ASSUMPTION\n"; } diff --git a/mplot/VisualResourcesMX.h b/mplot/VisualResourcesMX.h index b275b5db..75ba13fe 100644 --- a/mplot/VisualResourcesMX.h +++ b/mplot/VisualResourcesMX.h @@ -135,6 +135,7 @@ namespace mplot void insert_instance_data (const unsigned int instance_idx, const sm::vec& coord) { + // If this function fails, make sure to call v.render before calling set_instance_data :) if (instance_idx >= this->max_instances) { throw std::runtime_error ("insert_instance_data: bad instance_idx"); } From 0a6c5cc0389adefda85558db6c1db5920764de56 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 16 Feb 2026 09:22:29 +0000 Subject: [PATCH 68/82] Comment out an unused variable for now --- mplot/NavMesh.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 6170995d..023a06bb 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1621,7 +1621,7 @@ namespace mplot flags.set (cmm_fl::vertex_crossing, false); uint32_t _ti_2n = std::numeric_limits::max(); - sm::vec _tn_2n = {}; + //sm::vec _tn_2n = {}; sm::vec _tn = {}; // TWO NEIGHBOURS From 14b2573a482cf1d206f5034992b9d71f55172f3c Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 16 Feb 2026 11:21:09 +0000 Subject: [PATCH 69/82] A start at splitting compute_mesh_movement up a bit --- mplot/NavMesh.h | 222 +++++++++++++++++++++++++++++------------------- 1 file changed, 136 insertions(+), 86 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 023a06bb..55f43dea 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1211,50 +1211,22 @@ namespace mplot return this->position_camera (hp_scene, model_to_scene, _x, tn, _z, hoverheight); } - /*! - * Compute a movement over this navigation mesh. - * - * We convert the triangle vertices from the model frame to the scene frame before computing - * reorientations, so that non-uniform scalings in the model do not fox us. - * - * \param mv_camframe A movement vector in the camera's own frame of reference (an ego-motion) - * \param cam_to_scene The transformation matrix to bring the camera coordinates to the scene frame - * \param model_to_scene The transformation matrix to convert model coordinates to the scene frame - * \param hoverheight - * - * \return The re-positioned camera transform matrix + /** + * Return true: intersection found; false: no intersection found */ - sm::mat compute_mesh_movement (const sm::vec& mv_camframe, - const sm::mat& cam_to_scene, - const sm::mat& model_to_scene, - const float hoverheight) + bool find_first_intersection (sm::vec, 3>& tv_sf, // triangle vertices as coordinates. Input, may be modified? + sm::vec& tn0, // Triangle normal of tv_sf. In/out? + sm::vec& hov_sf, // Hit location on triangle, output + sm::mat& cam_to_surface, // output. pose matrix for hov_sf (is hov_sf in the end just cam_to_surface.translation()?) + [[maybe_unused]] const sm::vec& mv_camframe, // FIXME: Don't think we should need mv_camframe to find first intersection. + const sm::mat& cam_to_scene, + const sm::mat& model_to_scene, + const float hoverheight) { constexpr bool debug_move = true; - constexpr bool debug_move2 = true; - - // In case we throw off-edge, we need to restore ti0's state - const uint32_t ti0_save = this->ti0; - - // Boolean state flags used in this function - enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing, end_on_boundary }; - sm::flags flags; // Camera location, scene frame const sm::vec camloc_sf = cam_to_scene.translation(); - // Convert indices to vertices for triangle ti0, converting to the scene frame - sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); - if (tv_sf[0][0] == std::numeric_limits::max()) { throw std::runtime_error ("ti0 is not a triangle"); } - - // Compute the triangle normal in the scene frame - sm::vec tn0 = this->triangle_normal (tv_sf); - - if constexpr (debug_move) { - std::cout << "\n# compute_mesh_movement:\n" - << "\nti0: " << this->ti0 - << "\nti0 (sf): " << tv_sf << "\nnormal " << tn0 - << "\nmovement (camframe): " << mv_camframe - << "\nInitial camera location (camloc_sf): " << camloc_sf << "\n\n"; - } // Does camloc_sf in dirn tn0 intersect the tv_sf triangle? This // returns true if camloc_sf is on the edge of the triangle or on a @@ -1266,15 +1238,13 @@ namespace mplot // boundaries and so requires that the start point is *within* the boundary. // if constexpr (debug_move) { - std::cout << "First ray_tri_intersection (raystart,-tn0): " - << (camloc_sf + (tn0 / 2.0f)) << "," << -tn0 << std::endl; + std::cout << "First ray_tri_intersection (raystart,-tn0): " << (camloc_sf + (tn0 / 2.0f)) << "," << -tn0 << std::endl; } bool isect = false; - sm::vec hov_sf = {}; std::tie (isect, hov_sf) = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf + (tn0 / 2.0f), -tn0); // Use the detected location, hov_sf to compute the surface location of the camera - its 'hover location' - sm::mat cam_to_surface = cam_to_scene; + cam_to_surface = cam_to_scene; cam_to_surface.pretranslate (hov_sf - camloc_sf); // This is now our init pose; the camera is now at the surface // Try double precision @@ -1290,7 +1260,7 @@ namespace mplot } // If that didn't work, try the triangle *vertices* - uint32_t int_vertex_hi = std::numeric_limits::max(); // intersection vertex + //uint32_t int_vertex_hi = std::numeric_limits::max(); // intersection vertex if (isect == false) { if constexpr (debug_move) { std::cout << "Try the triangle vertices...\n"; } uint32_t hi = this->ti0; @@ -1308,7 +1278,7 @@ namespace mplot // if start is vertex, need to check movement across all the triangle-neighbours of this vertex (see later use of int_vertex_hi) } hov_sf = tv_sf[i]; - int_vertex_hi = hi; + //int_vertex_hi = hi; isect = true; } ++i; @@ -1320,7 +1290,7 @@ namespace mplot if (isect == true) { if constexpr (debug_move) { std::cout << "First ray_tri_intersected. Start of move is IN triangle ti0\n"; } } else { - if constexpr (debug_move2) { + if constexpr (debug_move) { std::cout << "No intersection (at start) with triangle ti0, check neighbours (and maybe update ti0)" << std::endl; } @@ -1358,11 +1328,11 @@ namespace mplot } while (hi != this->ti0); if (isect == false) { - if constexpr (debug_move2) { std::cout << "DBG No intersection (at start) with twins" << std::endl; } + if constexpr (debug_move) { std::cout << "DBG No intersection (at start) with twins" << std::endl; } // Final test to see if we're on boundary? float closest_edge_d = sm::geometry::dist_to_tri_edge (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf - (tn0 * hoverheight)); - if constexpr (debug_move2) { + if constexpr (debug_move) { std::cout << "Closest distance from " << (camloc_sf - (tn0 * hoverheight)) << " to ti0 edge: " << closest_edge_d << std::endl; } constexpr float ced_thresh = std::numeric_limits::epsilon() * 50; @@ -1373,24 +1343,19 @@ namespace mplot throw std::runtime_error ("No intersection (at start) with triangle or neighbours"); } } else { - if constexpr (debug_move2) { + if constexpr (debug_move) { std::cout << "Found intersection (at start) with twin triangle " << this->ti0 << std::endl; } } } // rest of function assumes isect was true (exception otherwise) - +#if 0 // FIXME: Sort out what we do if we were over a vertex. Shouldn't need mv_camframe // Find component of movement that is in the current triangle plane (in the scene frame of reference) sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - camloc_sf; sm::vec mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); sm::vec mv_inplane = mv_sf - mv_orthog; // scene frame, a relative movement - if (mv_inplane.length() == 0.0f) { - if constexpr (debug_move) { std::cout << "No movement, so return unchanged camera viewmatrix\n"; } - return cam_to_scene; - } - // New section to handle the case that we started right on a vertex if (isect == true && int_vertex_hi != std::numeric_limits::max()) { // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. @@ -1427,6 +1392,29 @@ namespace mplot } } } // Now carry on with corrected mv_inplane, tn0 and ti0 +#endif + return isect; + } + + /** + * Move across triangles, until at the end of mv_inplane. + */ + void traverse_triangles (sm::vec& mv_inplane, + const sm::vec& mv_sf, + sm::vec, 3>& tv_sf, + sm::vec& tn0, + sm::vec& hov_sf, + sm::mat& cam_to_surface, + const sm::mat& model_to_scene) + { + constexpr bool debug_move = true; + + // In case we throw off-edge, we need to restore ti0's state + const uint32_t ti0_save = this->ti0; + + // Boolean state flags used in this function + enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing, end_on_boundary }; + sm::flags flags; // A 'detected crossing' is one where we had to use a secondary method (comparing the // triangle containing the start and the triangle containing the end) to determine that @@ -1434,8 +1422,8 @@ namespace mplot // (compute_crossing_location, which uses a faster, but numerically fallible approach) // failed. uint32_t detected_edge = std::numeric_limits::max(); - //sm::vec detected_edgevec = {}; - uint32_t detected_newtri = std::numeric_limits::max(); // new triangle detected as part of a vertex crossing + // new triangle detected as part of a vertex crossing (halfedge) + uint32_t detected_newtri = std::numeric_limits::max(); // Now loop while our path may traverse one or more triangles while (!flags.test (cmm_fl::done)) { @@ -1447,12 +1435,9 @@ namespace mplot << "tn0 = " << tn0 << ")\n"; } - if (mv_inplane.length() == 0) { - throw std::runtime_error ("Zero length mv_inplane so stop/freeze/crash"); - } - if (mv_inplane.has_nan()) { - throw std::runtime_error ("mv_inplane contained NaN"); - } + // zero length mv_inplane should have been tested before calling this function + if (mv_inplane.length() == 0) { throw std::runtime_error ("Zero length mv_inplane"); } + if (mv_inplane.has_nan()) { throw std::runtime_error ("mv_inplane contained NaN"); } // Apply the edge crossing algorithm crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane); @@ -1466,8 +1451,9 @@ namespace mplot // an edge (probably while moving along that edge) cd.halfedge = detected_edge; cd.tri_edge = this->edge_vector (detected_edge, model_to_scene); - if constexpr (debug_move) { std::cout << "Obtained cd.tri_edge edge_vector (detected_edge=" << detected_edge << ", model_to_scene) = " - << cd.tri_edge << std::endl; } + if constexpr (debug_move) { + std::cout << "Obtained cd.tri_edge edge_vector (detected_edge=" << detected_edge << ", model_to_scene) = " << cd.tri_edge << std::endl; + } cd.pm.mv = mv_inplane; cd.pm.end = hov_sf + mv_inplane; } else { @@ -1505,9 +1491,9 @@ namespace mplot // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals if (flags.test (cmm_fl::vertex_crossing)) { cd.tri_edge = tn0.cross (_tn); - if constexpr (debug_move) { std::cout << "Setting cd.tri_edge from tn0.cross (_tn) = " - << tn0 << ".cross (" << _tn << ") = " - << cd.tri_edge << std::endl; } + if constexpr (debug_move) { + std::cout << "Set cd.tri_edge from tn0.cross (_tn) = " << tn0 << ".cross (" << _tn << ") = " << cd.tri_edge << "\n"; + } } // Compute the reorientation due to the requested movement. @@ -1529,7 +1515,7 @@ namespace mplot reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf - if (mv_rest.length() == 0) { + if (mv_rest.length() == 0.0f) { // The first movement to edge completed the movement. We actually landed ON the edge. cam_to_surface = reorient_model * cam_to_surface; flags.set (cmm_fl::done, true); @@ -1544,16 +1530,15 @@ namespace mplot // Is endmv in newtv_sf/_ti? std::cout << "Crazy? endmv = " << endmv << std::endl; std::cout << "ray_tri_intersection with _ti: " << (endmv + (_tn / 2.0f)) << "," << -_tn << std::endl; - auto [isect2, isectpoint2] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect2 ? "" : " NOT") << " land in new triangle\n"; } - if (isect2) { + auto [isect, isectpoint] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect ? "" : " NOT") << " land in new triangle\n"; } + if (isect) { // We DID land in the neighbouring triangle. We are done. cam_to_surface = reorient_model * cam_to_surface; flags.set (cmm_fl::done, true); } else { if constexpr (debug_move) { std::cout << "did we sail past or land in a 1-neighbour?\n"; } - // Incomplete; We've sailed past newtv_sf. if (!flags.test (cmm_fl::done)) { // We need to @@ -1621,7 +1606,7 @@ namespace mplot flags.set (cmm_fl::vertex_crossing, false); uint32_t _ti_2n = std::numeric_limits::max(); - //sm::vec _tn_2n = {}; + sm::vec _tn_2n = {}; sm::vec _tn = {}; // TWO NEIGHBOURS @@ -1680,7 +1665,7 @@ namespace mplot hi = this->ti0; // to end the do-while break; // out of for } -#ifdef OLD_REORIENT_START_CODE +#ifdef OLD_REORIENT_START_CODE // Hoping that by finding boundaries, this will be unrequired if (is) { // start is in neighbour tri (will re-orient to this and re-loop) std::cout << "Start in neighbour, end NOT in neighbour exit do-while\n"; _ti_2n = twin; @@ -1696,7 +1681,7 @@ namespace mplot hi = this->halfedge[hi].next; } while (hi != this->ti0); -#if 0 + // Test one-neighbours here if necessary (that is, if the two neighbour test above failed) if (flags.test (cmm_fl::detected_crossing) == false && _ti_2n == std::numeric_limits::max()) { uint32_t hi = this->ti0; @@ -1726,12 +1711,16 @@ namespace mplot if (endis) { // End is in one-neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing over ONE-neighbour! Pass on to next loop!\n"; } + if constexpr (debug_move) { std::cout << "DETECTED crossing INTO ONE-neighbour! Pass on to next loop!\n"; } + + // HERE, we KNOW what triangle we end up in. So why go to the next loop? + flags.set (cmm_fl::vertex_crossing, true); detected_edge = hi; detected_newtri = _ti; hi = this->ti0; // to end the do-while break; // out of for + } else { // end not in one-neighbour if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) _ti_2n = _ti; @@ -1746,29 +1735,34 @@ namespace mplot } while (hi != this->ti0); } -#endif + if (_ti_2n != std::numeric_limits::max()) { // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop if constexpr (debug_move) { std::cout << "CHANGE ti0 to _ti_2n = " << _ti_2n << "\n"; } + throw std::runtime_error ("Don't want to reorient start triangles. Too complex."); +#if 0 this->ti0 = _ti_2n; tn0 = _tn; // recompute mv_inplane for this neighbour triangle - mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); - if constexpr (debug_move) { std::cout << "RECOMPUTE mv_inplane, too (think here is a problem cause)\n"; } + //sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - cam_to_scene.translation(); // Note: computed once already + sm::vec mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); + if constexpr (debug_move) { std::cout << "RECOMPUTE mv_inplane?\n"; } mv_inplane = mv_sf - mv_orthog; // sf +#endif } else if (flags.test (cmm_fl::detected_crossing)) { - // We didn't find an alternative start triangle, but we did detect an edge crossing by intersection, so continue. + // We didn't find an alternative start triangle, but we did detect an edge crossing by intersection, so continue; next loop will apply } else if (flags.test (cmm_fl::vertex_crossing)) { - // We didn't find an alternative start triangle, but we did detect a vertex crossing, so continue. + // We didn't find an alternative start triangle, but we did detect a vertex crossing, so continue; next loop will apply } else if (flags.test (cmm_fl::end_on_boundary)) { - if constexpr (debug_move2) { std::cout << "Movement complete on boundary (detected)\n"; } + if constexpr (debug_move) { std::cout << "Movement complete on boundary (detected)\n"; } cam_to_surface.pretranslate (mv_inplane); flags.set (cmm_fl::done, true); } else { - // End of move not evidently in self or neighbours, so assume it's bang on the boundary - if constexpr (debug_move2) { std::cout << "Movement complete on boundary ASSUMPTION\n"; } - cam_to_surface.pretranslate (mv_inplane); - flags.set (cmm_fl::done, true); + // End of move not evidently in self or neighbours, and not tested as on boundary + throw std::runtime_error ("Didn't find new triangle, or detected crossing. Don't make ASSUMPTION"); + //if constexpr (debug_move) { std::cout << "Movement complete on boundary ASSUMPTION\n"; } + //cam_to_surface.pretranslate (mv_inplane); + //flags.set (cmm_fl::done, true); } } // single movement if/else @@ -1776,6 +1770,62 @@ namespace mplot } // compute_crossing_location if/else } // triangle traversing while loop + } + + /** + * Compute a movement over this navigation mesh. + * + * We convert the triangle vertices from the model frame to the scene frame before computing + * reorientations, so that non-uniform scalings in the model do not fox us. + * + * \param mv_camframe A movement vector in the camera's own frame of reference (an ego-motion) + * \param cam_to_scene The transformation matrix to bring the camera coordinates to the scene frame + * \param model_to_scene The transformation matrix to convert model coordinates to the scene frame + * \param hoverheight + * + * \return The re-positioned camera transform matrix + */ + sm::mat compute_mesh_movement (const sm::vec& mv_camframe, + const sm::mat& cam_to_scene, + const sm::mat& model_to_scene, + const float hoverheight) + { + constexpr bool debug_move = true; + + // Convert indices to vertices for triangle ti0, converting to the scene frame + sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); + if (tv_sf[0][0] == std::numeric_limits::max()) { throw std::runtime_error ("ti0 is not a triangle"); } + + // Compute the triangle normal in the scene frame + sm::vec tn0 = this->triangle_normal (tv_sf); + + if constexpr (debug_move) { + std::cout << "\n# compute_mesh_movement:\n" + << "\nti0: " << this->ti0 + << "\nti0 (sf): " << tv_sf << "\nnormal " << tn0 + << "\nmovement (camframe): " << mv_camframe + << "\nInitial camera location: " << cam_to_scene.translation() << "\n\n"; + } + + // Find the intersection point with the triangle ti0: + sm::vec hov_sf = {}; + sm::mat cam_to_surface; + bool isect = find_first_intersection (tv_sf, tn0, hov_sf, cam_to_surface, mv_camframe, cam_to_scene, model_to_scene, hoverheight); + if (!isect) { + throw std::runtime_error ("No initial intersection found"); + } + + // Find component of movement that is in the current triangle plane (in the scene frame of reference) + sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - cam_to_scene.translation(); + sm::vec mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); + sm::vec mv_inplane = mv_sf - mv_orthog; // scene frame, a relative movement + if (mv_inplane.length() == 0.0f) { + if constexpr (debug_move) { std::cout << "No movement, so return unchanged camera viewmatrix\n"; } + return cam_to_scene; + } + + // Now traverse those triangles! The output of this function is cam_to_surface + traverse_triangles (mv_inplane, mv_sf, tv_sf, tn0, hov_sf, cam_to_surface, model_to_scene); // Raise cam_to_surface up by hoverheight and then return cam_to_surface.pretranslate (hoverheight * tn0); From fca514e12f82a11f3ea601ae799aee4e464cb3db Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 16 Feb 2026 15:17:40 +0000 Subject: [PATCH 70/82] WIP re-writing the mesh movement --- mplot/NavMesh.h | 462 +++++++++++++++++------------------------------- 1 file changed, 165 insertions(+), 297 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 55f43dea..e2b2efe5 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -328,6 +328,11 @@ namespace mplot return v1 - v0; } + sm::vec edge_start (uint32_t hi, const sm::mat& transform) const + { + return (transform * this->vertex[this->halfedge[hi].vi[0]].p).less_one_dim(); + } + // Find all the neighbours of triangle *vertex* index a, throwing exceptions on errors. // Returns the same vector of halfedge indices as find_neighbours, but without assuming that the navmesh is good. // \return vector of halfedge indices @@ -1328,7 +1333,8 @@ namespace mplot } while (hi != this->ti0); if (isect == false) { - if constexpr (debug_move) { std::cout << "DBG No intersection (at start) with twins" << std::endl; } + if constexpr (debug_move) { std::cout << "DBG No intersection (at start) with twins" << std::endl; + } // Final test to see if we're on boundary? float closest_edge_d = sm::geometry::dist_to_tri_edge (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf - (tn0 * hoverheight)); @@ -1396,8 +1402,128 @@ namespace mplot return isect; } + // For the two triangles t0 and t1, find if t0 has a twin in t1 and return that halfedge. + uint32_t test_twin (const uint32_t t0, const uint32_t t1) + { + uint32_t rtn = std::numeric_limits::max(); + uint32_t hi0 = t0; + do { + uint32_t hi1 = t1; + do { + if (this->halfedge[hi1].twin = hi0) { rtn = hi0; } + hi1 = this->halfedge[hi1].next; + } while (hi1 != t1 && rtn == std::numeric_limits::max()); + hi0 = this->halfedge[hi0].next; + } while (hi0 != t0 && rtn == std::numeric_limits::max()); + return rtn; + } + + // Find any vertex in t0 that shares a vertex with t1. Return the halfedge in t0 for which that vertex is vi[0] + uint32_t test_vertex_twin (const uint32_t t0, const uint32_t t1) + { + uint32_t rtn = std::numeric_limits::max(); + uint32_t hi0 = t0; + do { + uint32_t hi1 = t1; + do { + if (this->halfedge[hi0].vi[0] == this->halfedge[hi1].vi[0] + || this->halfedge[hi0].vi[0] == this->halfedge[hi1].vi[1]) { rtn = hi0; } + hi1 = this->halfedge[hi1].next; + } while (hi1 != t1 && rtn == std::numeric_limits::max()); + hi0 = this->halfedge[hi0].next; + } while (hi0 != t0 && rtn == std::numeric_limits::max()); + return rtn; + } + + // By searching neighbours, find a boundary crossing. + crossing_data find_nearest_boundary_crossing (const sm::vec& hov_sf, + const sm::vec& mv_inplane, + const sm::mat& model_to_scene) + { + constexpr bool debug_move = true; + crossing_data cd; + + // Get neighbours of ti0 + auto nbs = this->find_neighbours (this->ti0); + + // For each neighbour, test to see if start location was inside a neighbour + uint32_t nb0 = std::numeric_limits::max(); + sm::vec _tn = {}; + sm::vec mv_orthog_nb = {}; + sm::vec mv_inplane_nb = {}; + sm::vec, 3> tv_nb = {}; + for (auto nb : nbs) { + tv_nb = this->triangle_vertices (nb, model_to_scene); + if (tv_nb[0][0] == std::numeric_limits::max()) { + // tv_nb is not a triangle. Could be an off-edge triangle + continue; + } else { + // Construct the mv_inplane within this neighbour and test if it lands inside + _tn = this->triangle_normal (tv_nb); + mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); + mv_inplane_nb = mv_inplane - mv_orthog_nb; + if constexpr (debug_move) { + std::cout << "Neighbour: " << nb << ": test intersect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; + } + auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { std::cout << "End lands IN? " << (endis ? "Y" : "N") << std::endl; } + + if (endis) { + nb0 = nb; + break; + } + } + } + + // Did we get a result? + if (nb0 != std::numeric_limits::max()) { + // We got the neighbour. Now find the edge/vertex that joins them, to construct crossing_data for return + // We have ti0 and nb0. Do they have a twinned edge? + bool is_vertex = false; + uint32_t twinned = this->test_twin (this->ti0, nb0); + if (twinned == std::numeric_limits::max()) { + // If twinned is not set, do they have a twinned vertex? That's a vertex with the same index in this->vertex + is_vertex = true; + twinned = this->test_vertex_twin (this->ti0, nb0); + if (twinned == std::numeric_limits::max()) { + // Found no neighbour! Weird. + throw std::runtime_error ("Found end triangle, and know ti0, but found no twinning edge or vertex between them ?!?!"); + } + } + + // Now process twinned... + if (is_vertex) { + // Build edge vector from normals in two triangles + } else { + cd.tri_edge = this->edge_vector (twinned, model_to_scene); + sm::vec twin_s = this->edge_start (twinned, model_to_scene); + cd.halfedge = twinned; + // TO find end coord, look at mv_inplane and cd.tri_edge + // Find closest location on cd.tri_edge to mv_inplane. That allows the construction of cd.pm.mv and cd.pm.end + cd.pm.mv = ; // movement vector + cd.pm.end = ; // end coord + cd.pm.flags.reset(); + } + + } else { + // No neighbour was landed in; so find one where we hit the boundary + for (auto nb : nbs) { + // Search all edges of nb. + sm::vec endmv = hov_sf + mv_inplane; + float d2 = sm::geometry::dist_to_lineseg (tv_nb[0], tv_nb[1], endmv); + if (d2 <= std::numeric_limits::epsilon()) {} + + } + } + + return cd; + } + /** * Move across triangles, until at the end of mv_inplane. + * + * On each loop, we move either to the end of the movement, if it is within the current + * triangle (ti0) OR we move to the boundary that we cross, and adjust mv_inplane. */ void traverse_triangles (sm::vec& mv_inplane, const sm::vec& mv_sf, @@ -1408,25 +1534,11 @@ namespace mplot const sm::mat& model_to_scene) { constexpr bool debug_move = true; - // In case we throw off-edge, we need to restore ti0's state const uint32_t ti0_save = this->ti0; - - // Boolean state flags used in this function - enum class cmm_fl : uint32_t { done, detected_crossing, single_movement, vertex_crossing, end_on_boundary }; - sm::flags flags; - - // A 'detected crossing' is one where we had to use a secondary method (comparing the - // triangle containing the start and the triangle containing the end) to determine that - // a triangle edge had been crossed, because the original method - // (compute_crossing_location, which uses a faster, but numerically fallible approach) - // failed. - uint32_t detected_edge = std::numeric_limits::max(); - // new triangle detected as part of a vertex crossing (halfedge) - uint32_t detected_newtri = std::numeric_limits::max(); - + bool done = false; // Now loop while our path may traverse one or more triangles - while (!flags.test (cmm_fl::done)) { + while (!done) { if constexpr (debug_move) { std::cout << "\nWHILE LOOP, TRAVERSING TRIANGLES\n" @@ -1439,42 +1551,42 @@ namespace mplot if (mv_inplane.length() == 0) { throw std::runtime_error ("Zero length mv_inplane"); } if (mv_inplane.has_nan()) { throw std::runtime_error ("mv_inplane contained NaN"); } - // Apply the edge crossing algorithm + // 1. Apply the fast edge crossing algorithm crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane); - - if (cd.pm.flags.test (pm_fl::no_cross_point) == false || flags.test (cmm_fl::detected_crossing) || flags.test (cmm_fl::vertex_crossing)) { - std::cout << "A) Then an edge (or vertex) crossing WAS detected (by compute_crossing_location or a prev. 'detected crossing')\n"; - - if (flags.test (cmm_fl::detected_crossing)) { - if constexpr (debug_move) { std::cout << "This is a detected crossing; changing crossing_data.halfedge to " << detected_edge << std::endl; } - // We have to update our crossing data, as we detected a crossing over - // an edge (probably while moving along that edge) - cd.halfedge = detected_edge; - cd.tri_edge = this->edge_vector (detected_edge, model_to_scene); - if constexpr (debug_move) { - std::cout << "Obtained cd.tri_edge edge_vector (detected_edge=" << detected_edge << ", model_to_scene) = " << cd.tri_edge << std::endl; - } - cd.pm.mv = mv_inplane; - cd.pm.end = hov_sf + mv_inplane; - } else { - if constexpr (debug_move) { std::cout << "This IS a crossing (compute_crossing_location found it) " << std::endl; } + // If it failed to find a crossing, + if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { + // Now test if our movement stays within the triangle + sm::vec endmv = (cam_to_surface * sm::vec{}).less_one_dim() + mv_inplane; + auto [isect, isectpoint] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], endmv + (tn0 / 2.0f), -tn0); + if (isect == false) { + // Didn't find edge crossing or that the end point is within ti0, so now search neighbours for an end point or boundary crossing. + cd = find_nearest_boundary_crossing (args); } + } + // crossing_data gives us info about if there is NO cross point in the partial mv no_cross_point flag + if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { // move a bit, shorten mv_inplane + // mv_inplane moved camera inside triangle. + std::cout << "A: Movement stays inside triangle ti0\n"; + cam_to_surface.pretranslate (mv_inplane); + done = true; + } else { + std::cout << "B: Crossed a boundary/vertex\n"; // _ti, _tn are the new triangle sm::vec _tn = {}; uint32_t _ti = std::numeric_limits::max(); - if (flags.test (cmm_fl::vertex_crossing)) { - if constexpr (debug_move) { std::cout << "Setting _ti to over-the-vertex tri " << detected_newtri << std::endl; } - _ti = detected_newtri; - } else { - // new triangle is the twin of the crossed edge - _ti = this->halfedge[cd.halfedge].twin; - if constexpr (debug_move) { - std::cout << "find triangle across edge: halfedge " << cd.halfedge << " twins to: " << _ti << std::endl; - } + + // new triangle is the twin of the crossed edge. May not be the case if we crossed a vertex. + _ti = this->halfedge[cd.halfedge].twin; + if constexpr (debug_move) { + std::cout << "find triangle across edge: halfedge " << cd.halfedge << " twins to: " << _ti << std::endl; } - if (_ti != std::numeric_limits::max()) { + if (_ti == std::numeric_limits::max()) { + // We probably went off the edge of our navigation model mesh + this->ti0 = ti0_save; + throw std::runtime_error ("off-edge: The movement went off the edge of the model"); + } else { // Re-orient onto the new triangle sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); if (newtv_sf[0][0] == std::numeric_limits::max()) { @@ -1482,20 +1594,8 @@ namespace mplot throw std::runtime_error ("off-edge: The movement went off the edge of the model"); continue; } else { - // Check that _ti is a triangle, else we were on the model boundary - // BUT newtv_sf[0][0] check should already have done this?!? _tn = this->triangle_normal (newtv_sf); - if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } - - // If a vertex crossing, we have to make an edge that is the cross product of the two triangle normals - if (flags.test (cmm_fl::vertex_crossing)) { - cd.tri_edge = tn0.cross (_tn); - if constexpr (debug_move) { - std::cout << "Set cd.tri_edge from tn0.cross (_tn) = " << tn0 << ".cross (" << _tn << ") = " << cd.tri_edge << "\n"; - } - } - // Compute the reorientation due to the requested movement. float rotn_angle = tn0.angle (_tn, cd.tri_edge); // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation @@ -1518,255 +1618,23 @@ namespace mplot if (mv_rest.length() == 0.0f) { // The first movement to edge completed the movement. We actually landed ON the edge. cam_to_surface = reorient_model * cam_to_surface; - flags.set (cmm_fl::done, true); + done = true; } else { // There's additional movement to complete. if constexpr (debug_move) { std::cout << "mv_rest length is " << mv_rest.length() << std::endl; } - - // At this point, can test to see if the end point of the movement - // lands in the adjacent triangle. If so, we're done, if not, time - // for another loop. - sm::vec endmv = (reorient_model * cam_to_surface * sm::vec{}).less_one_dim(); - // Is endmv in newtv_sf/_ti? - std::cout << "Crazy? endmv = " << endmv << std::endl; - std::cout << "ray_tri_intersection with _ti: " << (endmv + (_tn / 2.0f)) << "," << -_tn << std::endl; - auto [isect, isectpoint] = sm::geometry::ray_tri_intersection (newtv_sf[0], newtv_sf[1], newtv_sf[2], endmv + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { std::cout << "endmv = " << endmv << " DOES" << (isect ? "" : " NOT") << " land in new triangle\n"; } - if (isect) { - // We DID land in the neighbouring triangle. We are done. - cam_to_surface = reorient_model * cam_to_surface; - flags.set (cmm_fl::done, true); - } else { - - if constexpr (debug_move) { std::cout << "did we sail past or land in a 1-neighbour?\n"; } - - if (!flags.test (cmm_fl::done)) { - // We need to - // set an end-point that is on newtv_sf, update hov_sf, - // then recurse. also recompute the movement encoded in - // reorient_model - reorient_model.pretranslate (-mv_rest); - cam_to_surface = reorient_model * cam_to_surface; - hov_sf = cd.pm.end; // crossing data planned movement end - // Also update planned move, which is now shorter and in a new direction - tv_sf = newtv_sf; - mv_inplane = mv_rest; - } - } + // Loop To Next. Final movement should be exclusively within a triangle. + reorient_model.pretranslate (-mv_rest); + cam_to_surface = reorient_model * cam_to_surface; + hov_sf = cd.pm.end; // crossing data planned movement end + // Also update planned move, which is now shorter and in a new direction + tv_sf = newtv_sf; + mv_inplane = mv_rest; } this->ti0 = _ti; tn0 = _tn; } - } else { - // other triangle not found?! We probably went off the edge of our navigation model mesh - this->ti0 = ti0_save; - throw std::runtime_error ("off-edge: The movement went off the edge of the model"); - continue; } - - } else { // NO triangle edge crossing was detected with compute_crossing_location - std::cout << "B) An edge (or vertex) crossing was NOT detected (by compute_crossing_location or a prev. 'detected crossing')\n"; - - // We had intersection in ti0, but no apparent crossing over its edges. - // We may have moved entirely within the starting triangle or colinear with an edge. Test for these cases. - - // Check if it was a colinear movement - if (cd.pm.flags.test (pm_fl::colinear)) { - if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { - flags.set (cmm_fl::single_movement, true); - } else { // We've moved to a vertex, should have captured this case - throw std::runtime_error ("We've moved to a vertex, should have captured this case"); - } - } else { - // Test if it was movement-within; the simplest case - if constexpr (debug_move) { - std::cout << "No cross point and not colinear.\n Testing if " << (hov_sf + mv_inplane + (tn0 / 2.0f)) << "," << -tn0 - << " intersects tv_sf (" << tv_sf << "\n"; - } - auto [single_mv, he] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], hov_sf + mv_inplane + (tn0 / 2.0f), -tn0); - flags.set (cmm_fl::single_movement, single_mv); - } - - if (flags.test (cmm_fl::single_movement)) { - // Perform simplest movement, which is just to translate by mv_inplane - if constexpr (debug_move) { std::cout << "End of movement is *still* in ti0, so move mv_inplane\n"; } - cam_to_surface.pretranslate (mv_inplane); - flags.set (cmm_fl::done, true); - - } else { - if constexpr (debug_move) { - std::cout << "End of movement is NOT in ti0 " << this->ti0 // But it might be ON the boundary - << ". Checking neighbours, but Check BOUNDARY TOO; it might have landed there?\n"; - } - - // Test neighbours, new scheme using halfedge data structures - // Test neighbours to find any for which the start location is also within-boundary - flags.set (cmm_fl::detected_crossing, false); - flags.set (cmm_fl::vertex_crossing, false); - - uint32_t _ti_2n = std::numeric_limits::max(); - sm::vec _tn_2n = {}; - sm::vec _tn = {}; - - // TWO NEIGHBOURS - std::set neighbours_tested; - uint32_t hi = this->ti0; - do { - uint32_t twin = this->halfedge[hi].twin; - if (twin != std::numeric_limits::max()) { - // Test to see if start location was inside a neighbour - sm::vec, 3> tv_nb = this->triangle_vertices (twin, model_to_scene); - std::cout << "Checking two-neighbour " << twin << ": " << tv_nb << std::endl; - if (tv_nb[0][0] == std::numeric_limits::max()) { - // tv_nb is not a triangle - std::cout << "Are we off-edge?"; - throw std::runtime_error ("off-edge: Over a two-neighbour boundary"); - } else { - _tn = this->triangle_normal (tv_nb); - if constexpr (debug_move) { - std::cout << "TN: " << twin << ": START isect vector " << (hov_sf + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; - } - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); - sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "TN: " << twin << ": END isect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "Start IN? " << (is ? "Y" : "N") << "; End IN? " << (endis ? "Y" : "N") << std::endl; - } - - neighbours_tested.insert (twin); - - // Here, start is in original, end may not be in original. This is an 'intersection detected crossing' of a triangle edge - // which wasn't picked up with compute_crossing_location - if (endis) { - // End is in neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing (end of movement intesects two-neighbour). set detected_crossing flag.\n"; } - flags.set (cmm_fl::detected_crossing, true); - detected_edge = hi; - std::cout << "Start in neighbour AND end in neighbour exit do-while\n"; - hi = this->ti0; // to end the do-while - break; // out of for - - } else { // end not in neighbour - - // Check if end is on boundary between self and twin here. - // Need the exact endmv on the triangle surface here - sm::vec endmv = hov_sf + mv_inplane; - float d2 = sm::geometry::dist_to_lineseg (tv_nb[0], tv_nb[1], endmv); - std::cout << "Dist from endmv: " << endmv << " to twin boundary: " << d2 << std::endl; - if (d2 <= std::numeric_limits::epsilon()) { - // Take appropriate action... - std::cout << "Movement ends ON that boundary: " << tv_nb[0] << "," << (tv_nb[1] - tv_nb[0]) << "\n"; - flags.set (cmm_fl::end_on_boundary); - hi = this->ti0; // to end the do-while - break; // out of for - } -#ifdef OLD_REORIENT_START_CODE // Hoping that by finding boundaries, this will be unrequired - if (is) { // start is in neighbour tri (will re-orient to this and re-loop) - std::cout << "Start in neighbour, end NOT in neighbour exit do-while\n"; - _ti_2n = twin; - _tn_2n = _tn; - hi = this->ti0; // to end the do-while - break; // out of for - } // Neither end nor start are in neighbour. This occurs if the end is ON the boundary, but precision errors -#endif - } - } - } - - hi = this->halfedge[hi].next; - - } while (hi != this->ti0); - - // Test one-neighbours here if necessary (that is, if the two neighbour test above failed) - if (flags.test (cmm_fl::detected_crossing) == false && _ti_2n == std::numeric_limits::max()) { - uint32_t hi = this->ti0; - do { // For each half edge around ti0 - std::vector nbs = this->find_neighbours (hi); - std::cout << "Got " << nbs.size() << " neighbours\n"; - if (nbs.size() > 20) { throw std::runtime_error ("HOW many neighbours??"); } - for (auto _ti : nbs) { - // Already tested? This should avoid us testing any two-neighbours here (already did them above) - if (neighbours_tested.count (_ti)) { continue; } - neighbours_tested.insert (_ti); - - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - if (tv_nb[0][0] != std::numeric_limits::max()) { - _tn = this->triangle_normal (tv_nb); - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + (_tn / 2.0f), -_tn); - sm::vec mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - sm::vec mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "endis ONE-n? ray_tri_intersection with " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << std::endl; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { - std::cout << "Start of move " << (is ? "IS" : "is NOT") << " in ONE-neighbour " << _ti << " / " << tv_nb << std::endl; - std::cout << "And End of move " << (endis ? "IS" : "is NOT") << " in that ONE-neighbour " << std::endl; - } - - if (endis) { - // End is in one-neighbour so this is a detected crossing - if constexpr (debug_move) { std::cout << "DETECTED crossing INTO ONE-neighbour! Pass on to next loop!\n"; } - - // HERE, we KNOW what triangle we end up in. So why go to the next loop? - - flags.set (cmm_fl::vertex_crossing, true); - detected_edge = hi; - detected_newtri = _ti; - hi = this->ti0; // to end the do-while - break; // out of for - - } else { // end not in one-neighbour - if (is) { // start is in one-neighbour tri (will re-orient to this and re-loop) - _ti_2n = _ti; - _tn_2n = _tn; - hi = this->ti0; // to end the do-while - break; // out of for - } // else end is not in one-neighbour, and neither is start. - } - } // else one-neighbour is not a triangle - } - hi = this->halfedge[hi].next; - - } while (hi != this->ti0); - } - - if (_ti_2n != std::numeric_limits::max()) { - // Now we know an alternative start triangle for the movement. Re-orient to this and re-loop - if constexpr (debug_move) { std::cout << "CHANGE ti0 to _ti_2n = " << _ti_2n << "\n"; } - throw std::runtime_error ("Don't want to reorient start triangles. Too complex."); -#if 0 - this->ti0 = _ti_2n; - tn0 = _tn; - // recompute mv_inplane for this neighbour triangle - //sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - cam_to_scene.translation(); // Note: computed once already - sm::vec mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); - if constexpr (debug_move) { std::cout << "RECOMPUTE mv_inplane?\n"; } - mv_inplane = mv_sf - mv_orthog; // sf -#endif - } else if (flags.test (cmm_fl::detected_crossing)) { - // We didn't find an alternative start triangle, but we did detect an edge crossing by intersection, so continue; next loop will apply - } else if (flags.test (cmm_fl::vertex_crossing)) { - // We didn't find an alternative start triangle, but we did detect a vertex crossing, so continue; next loop will apply - } else if (flags.test (cmm_fl::end_on_boundary)) { - if constexpr (debug_move) { std::cout << "Movement complete on boundary (detected)\n"; } - cam_to_surface.pretranslate (mv_inplane); - flags.set (cmm_fl::done, true); - } else { - // End of move not evidently in self or neighbours, and not tested as on boundary - throw std::runtime_error ("Didn't find new triangle, or detected crossing. Don't make ASSUMPTION"); - //if constexpr (debug_move) { std::cout << "Movement complete on boundary ASSUMPTION\n"; } - //cam_to_surface.pretranslate (mv_inplane); - //flags.set (cmm_fl::done, true); - } - - } // single movement if/else - } // compute_crossing_location if/else } // triangle traversing while loop From bad1860c5fae8efaf1b2130a31234c7beb8919de Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 16 Feb 2026 17:19:56 +0000 Subject: [PATCH 71/82] Progress --- mplot/NavMesh.h | 84 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 19 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index e2b2efe5..a6ae7b1a 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -374,7 +374,7 @@ namespace mplot // Find all the neighbours of triangle *vertex* index a. // Assumes the navmesh is good, and has passed NavMesh::test() - // \return vector of halfedge indices + // \return vector of halfedge indices, including all neighbour triangles AND self (a) std::vector find_neighbours (const uint32_t a) const { uint32_t hi = a; @@ -1341,11 +1341,12 @@ namespace mplot if constexpr (debug_move) { std::cout << "Closest distance from " << (camloc_sf - (tn0 * hoverheight)) << " to ti0 edge: " << closest_edge_d << std::endl; } - constexpr float ced_thresh = std::numeric_limits::epsilon() * 50; + constexpr float ced_thresh = std::numeric_limits::epsilon() * 50; // FIX this if (closest_edge_d < ced_thresh) { // make tiny adjustment to camloc_sf so we ARE in the triangle? OR... isect = true; // SAY we are, and proceed? <-- this if it works. } else { + // This might be my last frequently occurring bug? throw std::runtime_error ("No intersection (at start) with triangle or neighbours"); } } else { @@ -1410,7 +1411,7 @@ namespace mplot do { uint32_t hi1 = t1; do { - if (this->halfedge[hi1].twin = hi0) { rtn = hi0; } + if (this->halfedge[hi1].twin == hi0) { rtn = hi0; } hi1 = this->halfedge[hi1].next; } while (hi1 != t1 && rtn == std::numeric_limits::max()); hi0 = this->halfedge[hi0].next; @@ -1435,6 +1436,25 @@ namespace mplot return rtn; } + // Construct the plane dividing the triangles t0 and t1 which are assumed adjacent (joined + // by vertex or edge). Return the plane in transformed (i.e. scene) coordinates. The + // dividing edge_sc is supplied already transformed by model_to_scene. + sm::vec, 2> dividing_plane (const uint32_t t0, const uint32_t t1, + const sm::vec& dividing_edge_sc, + const sm::mat& model_to_scene) + { + sm::vec, 2> p0_n; // p0_n[0] is plane origin; p0_n[1] is normal + sm::vec n0 = this->triangle_normal (this->triangle_vertices (t0, model_to_scene)); + sm::vec n1 = this->triangle_normal (this->triangle_vertices (t1, model_to_scene)); + sm::vec ntm = n0 + n1; + ntm.renormalize(); // make it unit + sm::vec de = dividing_edge_sc; + de.renormalize(); + p0_n[1] = ntm.cross (de); + p0_n[0] = (model_to_scene * this->vertex[this->halfedge[t1].vi[0]].p).less_one_dim(); + return p0_n; + } + // By searching neighbours, find a boundary crossing. crossing_data find_nearest_boundary_crossing (const sm::vec& hov_sf, const sm::vec& mv_inplane, @@ -1453,6 +1473,9 @@ namespace mplot sm::vec mv_inplane_nb = {}; sm::vec, 3> tv_nb = {}; for (auto nb : nbs) { + + if (nb == ti0) { continue; } // Don't test self (i.e. ti0) + tv_nb = this->triangle_vertices (nb, model_to_scene); if (tv_nb[0][0] == std::numeric_limits::max()) { // tv_nb is not a triangle. Could be an off-edge triangle @@ -1477,6 +1500,7 @@ namespace mplot // Did we get a result? if (nb0 != std::numeric_limits::max()) { + if constexpr (debug_move) { std::cout << "Found a neighbour because endmv lands in it\n"; } // We got the neighbour. Now find the edge/vertex that joins them, to construct crossing_data for return // We have ti0 and nb0. Do they have a twinned edge? bool is_vertex = false; @@ -1488,32 +1512,49 @@ namespace mplot if (twinned == std::numeric_limits::max()) { // Found no neighbour! Weird. throw std::runtime_error ("Found end triangle, and know ti0, but found no twinning edge or vertex between them ?!?!"); + if constexpr (debug_move) { std::cout << "...across a vertex\n"; } } } + cd.halfedge = twinned; // Now process twinned... if (is_vertex) { // Build edge vector from normals in two triangles + throw std::runtime_error ("Create vertex crossing edge vector"); } else { cd.tri_edge = this->edge_vector (twinned, model_to_scene); - sm::vec twin_s = this->edge_start (twinned, model_to_scene); - cd.halfedge = twinned; - // TO find end coord, look at mv_inplane and cd.tri_edge - // Find closest location on cd.tri_edge to mv_inplane. That allows the construction of cd.pm.mv and cd.pm.end - cd.pm.mv = ; // movement vector - cd.pm.end = ; // end coord - cd.pm.flags.reset(); } + sm::vec, 2> dp = this->dividing_plane (ti0, twinned, cd.tri_edge, model_to_scene); + float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane + if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } + // To find end coord, look at mv_inplane and cd.tri_edge + sm::vec mv_start = mv_inplane; + mv_start.renormalize(); + mv_start *= pdist; + // Find closest location on cd.tri_edge to mv_inplane. That allows the construction of cd.pm.mv and cd.pm.end + cd.pm.mv = mv_start; // movement vector to the crossing + cd.pm.end = hov_sf + mv_start; // end coord of the crossing + cd.pm.flags.reset(); } else { - // No neighbour was landed in; so find one where we hit the boundary - for (auto nb : nbs) { - // Search all edges of nb. - sm::vec endmv = hov_sf + mv_inplane; - float d2 = sm::geometry::dist_to_lineseg (tv_nb[0], tv_nb[1], endmv); - if (d2 <= std::numeric_limits::epsilon()) {} - - } + // No neighbour was landed in; so do the plane test approach for each of the three edges of ti0 + uint32_t hi = ti0; + do { + sm::vec tri_edge = this->edge_vector (hi, model_to_scene); + sm::vec, 2> dp = this->dividing_plane (hi, this->halfedge[hi].twin, tri_edge, model_to_scene); + float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane + if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } + if (pdist < 0.00001f) { + std::cout << "ON BOUNDARY of " << hi << std::endl; // now do something + cd.pm.mv = mv_inplane; + cd.pm.end = hov_sf + mv_inplane; + cd.halfedge = hi; + cd.tri_edge = tri_edge; + cd.pm.flags.set (pm_fl::no_cross_point, true); + break; + } + hi = this->halfedge[hi].next; + } while (hi != ti0); } return cd; @@ -1557,10 +1598,15 @@ namespace mplot if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { // Now test if our movement stays within the triangle sm::vec endmv = (cam_to_surface * sm::vec{}).less_one_dim() + mv_inplane; + if constexpr (debug_move) { + std::cout << "endmv intersection with ti0 " << ti0 << " test vector " << (endmv + (tn0 / 2.0f)) << "," << -tn0 << " with tri " << tv_sf << std::endl;; + } auto [isect, isectpoint] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], endmv + (tn0 / 2.0f), -tn0); + if constexpr (debug_move) { std::cout << "End lands in ? " << (isect ? "Y" : "N") << std::endl; } if (isect == false) { // Didn't find edge crossing or that the end point is within ti0, so now search neighbours for an end point or boundary crossing. - cd = find_nearest_boundary_crossing (args); + cd = find_nearest_boundary_crossing (hov_sf, mv_inplane, model_to_scene); + std::cout << "find_nearest_boundary_crossing returns cd which has cd.pm.flags: " << cd.pm.flags << std::endl; } } From 61238e6638df900ceed155c4ded1afe818156d39 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 16 Feb 2026 22:09:25 +0000 Subject: [PATCH 72/82] Update the examples readme, which still referred to morph --- examples/README.md | 157 ++------------------------------------------- 1 file changed, 7 insertions(+), 150 deletions(-) diff --git a/examples/README.md b/examples/README.md index 107578fa..287716cf 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,15 +1,15 @@ # Example programs -This folder contains a set of example morphologica programs to help +This folder contains a set of example mathplot programs to help new users to get started using the library. These examples will build alongside the unit tests when you do a -morphologica build like this: +mathplot build like this: ```bash -# Clone morphologica if you didn't already -git clone git@github.com/ABRG-Models/morphologica -cd morphologica +# Clone mathplot if you didn't already +git clone git@github.com/sebsjames/mathplot --recurse-submodules +cd mathplot mkdir build cd build cmake .. @@ -18,153 +18,10 @@ cd .. ``` You'll find the example program binaries in `build/examples`. -The examples are almost all built on the morph::Visual environment, +The examples are almost all built on the mplot::Visual environment, which means you can interact with the mouse. Right-button down allows you to drag, Left-button down allows you to rotate. Press 't' to change the axis of rotations. Press 'h' and have a look at stdout to see some other key presses. 'x' exits. 'a' Resets the view. -## Computational and scientific model examples - -These are examples of models that we've re-implemented from the -literature. These examples make use of most of the basic facilities in -morphologic; morph::Config, morph::HdfData and morph::Visual. - -### convolve.cpp - -An example demonstrating the use of morph::HexGrid, its -HexGrid::convolve function, the morph::Random class and a -visualization of the input, the convolution kernel and the resulting -output. - -![Screenshot of the convolve program](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/convolve.png?raw=true) - -### logisticmap.cpp - -Computes the logistic map and displays with a morph::GraphVisual, using diamond shaped markers. - -![Screenshot of the logisticmap program](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/logisticmap.png?raw=true) - -### rosenbrock.cpp - -This program find the minimum of the Rosenbrock banana function using -the Nelder-Mead simplex optimization method (coded as the class -morph::NMSimplex). The walk of the simplex down the function surface -is animated. Note that the scaling of the colour map is set so that -only the lowest part of the surface is resolved in the green to blue -part of the map (which is Inferno) - -![Screenshot of Rosenbrock banana function with Nelder-Mead triangle](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/rosenbrock.png?raw=true) - -### Ermentrout2009 - -Contains an implementation of a Keller-Segel reaction diffusion system. - -**From the base of morphologica**, run like this (the program needs to access the file ./boundaries/whiskerbarrels.svg): - -```bash -/build/examples/Ermentrout2009/erm ./examples/Ermentrout2009/configs/erm.json -``` - -![Screenshot of the erm.cpp program, showing a bullseye mode](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/erm.png?raw=true) - -### LotkaVolterra - -Contains an implementation of the Lotka-Volterra population model cast as a reaction diffusion system. - -![Shows two surfaces for two population variables, u and v](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/lotkavolterra.png?raw=true) - -### SimpsonGoodhill - -Contains an implementation of the model described in the paper 'A -simple model can unify a broad range of phenomena in retinotectal map -development', Biological Cybernetics, 2011, by Hugh Simpson and Geoffrey Goodhill. This one is notable because it is an **agent based model**, rather than a reaction diffusion model. Proof that morphologica is useful for many different types of model! The image shows results for the wildtype model, Figs 2B to 2D in the paper, with parameters exactly as given in the paper. - -**From the base of morphologica**, run like this: - -```bash -/build/examples/SimpsonGoodhill/sg ./examples/SimpsonGoodhill/sg.json -``` -![Screenshot of the sg.cpp program, showing the wildtype model (cf. Figs 2B-2D)](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/sg.png?raw=true) - -## morph::Visual examples - -These simple examples showcase the features in morphologica's -morph::Visual code. They're a useful place to see what the code can do -for data visualization. - -### visual.cpp - -An example morph::Visual program which shows a morph::HexGrid and some -text labels. - -![Screenshot of a morph::Visual scene](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/visual.png?raw=true) - -### fps.cpp - -A dynamic HexGrid program showing an animated surface - -![Screenshot of the animated hexgrid program, fps.cpp](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/fps.png?raw=true) - -### hexgrid.cpp - -Example HexGridVisual - -![Screenshot of hexgrid.cpp showing a sinusoidal landscape in the jet colour map](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/hexgrid.png?raw=true) - -### hexgrid_image - -Creates a HexGrid, then uses `HexGrid::resampleImage` to load a picture. - -![Screenshot of hexgrid_image.cpp showing a resampled image of a touring bicycle](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/hexgrid_image.png?raw=true) - -### cartgrid.cpp - -Example Cartesian grid (CartGridVisual) - -![Screenshot of cartgrid.cpp showing a sinusoidal landscape in the jet colour map](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/cartgrid.png?raw=true) - -### quiver.cpp - -An example quiver plot using morph::QuiverVisual. - -![Screenshot of a 3D quiver plot](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/quiver.png?raw=true) - -### scatter.cpp - -An example three dimensional scatter plot of spheres using -morph::ScatterVisual. Note that in this example, the coordinate arrows -are set within the scene (and so move with the model). - -![Screenshot of a 3D scatter plot](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/scatter.png?raw=true) - -### graph1.cpp to graph4.cpp - -Various examples of the use of morph::GraphVisual. - -![Screenshot of graph3.cpp, showing some example graphs](https://github.com/ABRG-Models/morphologica/blob/main/examples/screenshots/graph3.png?raw=true) - -### twowindows.cpp - -An example to show how to create two morph::Visuals, and hence two -windows, in your program. - -### rods.cpp - -An example of the very simple VisualModel, morph::RodVisual, which -simply draws a polygonal rod. You can specify how many sides, so this -can be used to draw rods of square section, or rods which appear to be -cylindrical. - -### quads.cpp - -An example of morph::QuadsVisual. - -### pointrows.cpp - -An example of morph::PointRowsVisual, which is used to render a -surface made of adjacent rows of points. - -This program is also conditially compiled into the exectuable -pointrows_mesh, which renders the same points as a ball-and-stick -mesh. +There's also a [gallery of the example programs](https://sebsjames.github.io/mathplot/) on the reference site. \ No newline at end of file From f9ca73a1dfa12d39a0630c3e472ee837d1998fa0 Mon Sep 17 00:00:00 2001 From: Seb James Date: Tue, 17 Feb 2026 15:01:55 +0000 Subject: [PATCH 73/82] Some code to deal with colinear movements. Also chasing down zero-length triangle halfedges (or almost zero-length) --- mplot/NavMesh.h | 90 +++++++++++++++++++++++++++++++++-------- mplot/VisualModelBase.h | 6 ++- 2 files changed, 77 insertions(+), 19 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index a6ae7b1a..442f116d 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -174,7 +174,7 @@ namespace mplot * to find the indices into vertexPositions and vertexNormals that this index in the * topographic mesh relates to. * - * \param scene_coord Supplied coordinate in scene frame of referencea + * \param scene_coord Supplied coordinate in scene frame of reference * \param viewmatrix The viewmatrix of the model which converts model frame coordinates to the scene frame */ uint32_t find_vertex_nearest (const sm::vec& scene_coord, const sm::mat& viewmatrix) const @@ -218,16 +218,50 @@ namespace mplot fhi = _hi; } + bool zlen_halfedge = false; + bool zlen_halfedge_to_halfedge = false; + bool approx_zlen_halfedge_to_halfedge = false; // Each boundary should be the same forwards and backwards from every possible start halfedge do { //std::cout << "this->halfedge["<halfedge[fhi].flags << " go to next..." << std::endl; if (this->halfedge[fhi].next == fhi) { throw std::runtime_error ("self next referral!"); } + // Also make sure that the vertices are not the same (no triangles-that-are-lines) + const uint32_t fhi_nx = this->halfedge[fhi].next; + auto hlen = (this->vertex[halfedge[fhi].vi[1]].p - this->vertex[halfedge[fhi_nx].vi[1]].p).length(); + + if (halfedge[fhi].vi[0] == halfedge[fhi].vi[1]) { zlen_halfedge = true; } + + if (halfedge[fhi].vi[1] == halfedge[fhi_nx].vi[1]) { zlen_halfedge_to_halfedge = true; } + + if (hlen < (10.0f * std::numeric_limits::epsilon())) { + approx_zlen_halfedge_to_halfedge = true; + //std::cout << "Curr fhi = " << fhi << ": " << this->vertex[halfedge[fhi].vi[0]].p << "," + // << (this->vertex[halfedge[fhi].vi[1]].p - this->vertex[halfedge[fhi].vi[0]].p) + // << ", Next fhi = " << fhi_nx << ": " << this->vertex[halfedge[fhi_nx].vi[0]].p << "," + // << (this->vertex[halfedge[fhi_nx].vi[1]].p - this->vertex[halfedge[fhi_nx].vi[0]].p) + // << std::endl; + } + fhi = this->halfedge[fhi].next; ++fcount; } while (fhi != _hi && fhi != max && fcount < this->halfedge.size()); if (debug) { std::cout << "From forwards loop: fcount = " << fcount << " and fhi = " << fhi << " cf _hi = " << _hi << std::endl; + + if (zlen_halfedge == true) { + std::cout << "test fails because we have zlen_halfedge\n"; + } + if (zlen_halfedge_to_halfedge == true) { + std::cout << "test fails because we have zlen_halfedge_to_halfedge\n"; + } + if (approx_zlen_halfedge_to_halfedge == true) { + std::cout << "test fails because we have at least one zero or ~zero length halfedge\n"; + } + } + + if (zlen_halfedge || zlen_halfedge_to_halfedge || approx_zlen_halfedge_to_halfedge) { + return false; } uint32_t rcount = 0u; @@ -391,19 +425,25 @@ namespace mplot } // Test the navmesh, to make sure it is perfect - void test() + void test (const bool just_mark_bad = false) { - std::cout << __func__ << " called to test the navmesh\n"; - // 1) Verify that each halfedge is part of a face or hole (boundary) + std::cout << "NavMesh verification test running...\n"; + // 1) Verify that each halfedge is part of a face or hole (boundary) and is not a line? for (uint32_t hi = 0; hi < this->halfedge.size(); ++hi) { if ((this->halfedge[hi].flags & 0x1) == 0x1) { //std::cout << "This halfedge was marked as boundary\n"; if (this->verify_boundary (hi) == false) { - throw std::runtime_error ("Imperfect halfedge mesh (boundary hole)"); + if (just_mark_bad == false) { + throw std::runtime_error ("Imperfect halfedge mesh (boundary hole)"); + } } } else { if (this->verify_triangle (hi) == false) { - throw std::runtime_error ("Imperfect halfedge mesh (face triangle)"); + if (just_mark_bad == true) { + this->halfedge[hi].flags |= 0x2; // Do want to mark whole triangle + } else { + throw std::runtime_error ("Imperfect halfedge mesh (face triangle)"); + } } std::vector nb = this->test_neighbours (hi); @@ -477,9 +517,16 @@ namespace mplot const uint32_t _bnext = sz + j + 1; const uint32_t newi = halfedge.size(); if constexpr (debug_bnd) { std::cout << "Push-back to halfedge[" << newi - << "] with prev = " << bprev << " next = " << _bnext + << "]: " << this->vertex[this->halfedge[cur].vi[1]].p + << "," << (this->vertex[this->halfedge[cur].vi[0]].p - this->vertex[this->halfedge[cur].vi[1]].p) + << " with prev = " << bprev << " next = " << _bnext << " and twin = " << cur << std::endl; } - this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, _bnext, bprev, 1}); + uint32_t fl = 1; + if ((this->vertex[this->halfedge[cur].vi[0]].p - this->vertex[this->halfedge[cur].vi[1]].p).length() < 10.0f * std::numeric_limits::epsilon()) { + fl |= 2; + } + + this->halfedge.push_back ({{this->halfedge[cur].vi[1], this->halfedge[cur].vi[0]}, cur, _bnext, bprev, fl}); this->halfedge[cur].twin = newi; if (bcandi == i) { @@ -505,6 +552,7 @@ namespace mplot // Lastly - check through for rogues! std::cout << "Post-search for rogues\n"; + for (uint32_t i = 0; i < sz; ++i) { if (this->halfedge[i].twin == max) { bool rogue_is_tri = this->verify_triangle (i, true); @@ -994,18 +1042,18 @@ namespace mplot std::cout << "ccl: fec returned pm.colinear true for t" << a << "t" << b << "\n"; } } - if (pm.flags.test (pm_fl::no_cross_point) - && pm.flags.test (pm_fl::colinear) == false) { + if (pm.flags.test (pm_fl::no_cross_point) == true && pm.flags.test (pm_fl::colinear) == false) { inside[a] = true; if constexpr (debug) { - std::cout << "ccl: No intersection for edge t" << a << "t" << b << " " << t_verts[a] << " -- " << t_verts[b] - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; + std::cout << "ccl: No intersection for edge t" << a << "t" << b << " " << t_verts[a] << "," << (t_verts[b] - t_verts[a]) + << " and move " << mv_s << "," << mv_inplane << std::endl; } } else { if constexpr (debug) { if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t0t1\n"; } - std::cout << "ccl: Intersection for edge t" << a << "t" << b << " " << t_verts[a] << " -- " << t_verts[b] - << " and move " << mv_s << " -- " << (mv_s + mv_inplane) << std::endl; + if (pm.flags.test (pm_fl::no_cross_point)) { std::cout << "ccl: no cross point t0t1\n"; } + std::cout << "ccl: Intersection for edge t" << a << "t" << b << " " << t_verts[a] << "," << (t_verts[b] - t_verts[a]) + << " and move " << mv_s << "," << mv_inplane << std::endl; } cd.pm = pm; cd.tri_edge = edge; @@ -1439,6 +1487,7 @@ namespace mplot // Construct the plane dividing the triangles t0 and t1 which are assumed adjacent (joined // by vertex or edge). Return the plane in transformed (i.e. scene) coordinates. The // dividing edge_sc is supplied already transformed by model_to_scene. + // FIXME: Check this works as expected sm::vec, 2> dividing_plane (const uint32_t t0, const uint32_t t1, const sm::vec& dividing_edge_sc, const sm::mat& model_to_scene) @@ -1594,8 +1643,11 @@ namespace mplot // 1. Apply the fast edge crossing algorithm crossing_data cd = this->compute_crossing_location (tv_sf, this->ti0, hov_sf, mv_inplane); - // If it failed to find a crossing, - if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { + + // If it failed to find a cross point, then we test inside the triangle and neighbours + // (Also if we moved colinearly along edge past a vertex) + if ((cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::no_cross_point) == false) + || (cd.pm.flags.test (pm_fl::colinear) == false && cd.pm.flags.test (pm_fl::no_cross_point) == true)) { // Now test if our movement stays within the triangle sm::vec endmv = (cam_to_surface * sm::vec{}).less_one_dim() + mv_inplane; if constexpr (debug_move) { @@ -1611,7 +1663,11 @@ namespace mplot } // crossing_data gives us info about if there is NO cross point in the partial mv no_cross_point flag - if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { // move a bit, shorten mv_inplane + if (cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::no_cross_point) == true) { + std::cout << "A: Movement stays inside triangle ti0 (colinear within boundary\n"; + cam_to_surface.pretranslate (mv_inplane); + done = true; + } else if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { // move a bit, shorten mv_inplane // mv_inplane moved camera inside triangle. std::cout << "A: Movement stays inside triangle ti0\n"; cam_to_surface.pretranslate (mv_inplane); diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 0f6e2eb8..b190f448 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -436,22 +436,24 @@ namespace mplot std::string filename = navmesh_dir + std::string("navmesh_") + std::to_string (h); std::string filename_pre_boundary = filename + ".pre"; + constexpr bool just_mark = true; if (mplot::tools::fileExists (filename)) { this->navmesh->load (filename); + std::cout << "Full test...\n"; this->navmesh->test(); } else if (mplot::tools::fileExists (filename_pre_boundary)) { std::cout << "Pre-boundary navmesh\n"; this->navmesh->load (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); - this->navmesh->test(); + this->navmesh->test (just_mark); this->navmesh->save (filename); } else { std::cout << "Building NavMesh to save into file " << filename << std::endl; this->build_navmesh(); this->navmesh->save (filename_pre_boundary); this->navmesh->add_boundary_halfedges(); - this->navmesh->test(); + this->navmesh->test (just_mark); this->navmesh->save (filename); } } From 2626790087c5e853dafa71ccd485d6da860ccbcf Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 18 Feb 2026 16:33:19 +0000 Subject: [PATCH 74/82] Dealing with a case going through a vertex --- mplot/NavMesh.h | 291 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 214 insertions(+), 77 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 442f116d..7bb6b313 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -835,8 +835,10 @@ namespace mplot // Flags class enum class pm_fl : uint32_t { - no_cross_point, // Means 'there was no crossing' - colinear // Means the movement was colinear with an edge + crossed, // Means the partial movement crossed an edge + colinear, // Means the movement was colinear with an edge + near_vertex_0, // The partial movement crossed very close to vertex 0 of the crossed edge + near_vertex_1 // The partial movement crossed very close to vertex 0 of the crossed edge }; /* * The partial movement that takes us to the crossing point, specified as movement + endpoint @@ -848,8 +850,15 @@ namespace mplot sm::vec mv = {}; // The end coordinate of the movement sm::vec end = {}; + constexpr sm::flags default_flags() + { + sm::flags _flags; + _flags.reset(); + _flags.set (pm_fl::crossed, true); // assume crossed in new partial_movement + return _flags; + } // boolean state - sm::flags flags; + sm::flags flags = default_flags(); }; /* @@ -939,12 +948,12 @@ namespace mplot if (to_v.length() <= mv_inplane3d.length()) { if constexpr (debug) { std::cout << "fec: partial colinear move to vertex\n"; } - pm.flags.set (pm_fl::no_cross_point, false); + pm.flags.set (pm_fl::crossed, true); pm.mv = (from_triangle_frame * to_v).less_one_dim(); // need to know if we were to go over a vertex pm.end = (from_triangle_frame * edge_e_3d).less_one_dim(); } else { if constexpr (debug) { std::cout << "fec: partial colinear along/within edge\n"; } - pm.flags.set (pm_fl::no_cross_point, true); + pm.flags.set (pm_fl::crossed, false); // Compute end from mv_inplane4d pm.mv = (from_triangle_frame * mv_inplane4d).less_one_dim(); pm.end = (from_triangle_frame * (h_4d + mv_inplane4d)).less_one_dim(); @@ -957,6 +966,27 @@ namespace mplot // Now go from cross point 2d to a point in model coordinates? pm.end = (from_triangle_frame * cp2d.plus_one_dim(edge_s_4d[2])).less_one_dim(); if constexpr (debug) { std::cout << "fec: Cross point in mdl frame: " << pm.end << std::endl; } + + // Check if cross point is close to a vertex + sm::vec e1 = cp2d - edge_s_2d; + float d2_e1 = e1.length(); + if (d2_e1 < 100.0f * std::numeric_limits::epsilon()) { + if constexpr (debug) { + std::cout << "Set near_vertex flag due to end 0 (within " << 100.0f * std::numeric_limits::epsilon() << ")\n"; + } + pm.flags.set (pm_fl::near_vertex_0); + } + e1 += edge_2d; + float d2_e2 = e1.length(); + if (d2_e2 < 100.0f * std::numeric_limits::epsilon()) { + if constexpr (debug) { std::cout << "Set near_vertex flag due to end 1\n"; } + pm.flags.set (pm_fl::near_vertex_1); + } + + if constexpr (debug) { + std::cout << "fec: Distance to edge end 1: " << d2_e1 << ", and to end 2: " << d2_e2 << std::endl; + } + pm.mv = pm.end - mv_s; } else { @@ -968,7 +998,7 @@ namespace mplot << h_2d << " -- " << (h_2d + mv_inplane2d) << std::endl; } // Mark that there was no intersection - pm.flags.set (pm_fl::no_cross_point, true); + pm.flags.set (pm_fl::crossed, false); pm.mv = sm::vec{}; pm.end = mv_s; } @@ -1019,7 +1049,7 @@ namespace mplot { constexpr bool debug = true; crossing_data cd; - cd.pm.flags.set (pm_fl::no_cross_point, true); + cd.pm.flags.set (pm_fl::crossed, false); sm::vec p = mv_s + mv_inplane; sm::vec tn = this->triangle_normal (t_verts); @@ -1042,7 +1072,7 @@ namespace mplot std::cout << "ccl: fec returned pm.colinear true for t" << a << "t" << b << "\n"; } } - if (pm.flags.test (pm_fl::no_cross_point) == true && pm.flags.test (pm_fl::colinear) == false) { + if (pm.flags.test (pm_fl::crossed) == false && pm.flags.test (pm_fl::colinear) == false) { inside[a] = true; if constexpr (debug) { std::cout << "ccl: No intersection for edge t" << a << "t" << b << " " << t_verts[a] << "," << (t_verts[b] - t_verts[a]) @@ -1050,8 +1080,8 @@ namespace mplot } } else { if constexpr (debug) { - if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t0t1\n"; } - if (pm.flags.test (pm_fl::no_cross_point)) { std::cout << "ccl: no cross point t0t1\n"; } + if (pm.flags.test (pm_fl::colinear)) { std::cout << "ccl: colinear t" << a << "t" << b << "\n"; } + if (pm.flags.test (pm_fl::crossed) == false) { std::cout << "ccl: no cross point t" << a << "t" << b << "\n"; } std::cout << "ccl: Intersection for edge t" << a << "t" << b << " " << t_verts[a] << "," << (t_verts[b] - t_verts[a]) << " and move " << mv_s << "," << mv_inplane << std::endl; } @@ -1059,6 +1089,11 @@ namespace mplot cd.tri_edge = edge; cd.halfedge = hi; } + } else { + if constexpr (debug) { + std::cout << "inside[" << a << "] is true for edge t" << a << "t" << b << " " + << t_verts[a] << "," << (t_verts[b] - t_verts[a]) << "\n"; + } } ++a; @@ -1069,19 +1104,21 @@ namespace mplot // We've now tested edge crossing for three edges in the triangle. if constexpr (debug) { - if (cd.pm.flags.test (pm_fl::no_cross_point) == false) { + if (cd.pm.flags.test (pm_fl::crossed) == true) { std::cout << "ccl: Crossed over" << (inside[0] ? " " : " 0-1") - << (inside[1] ? " " : " 2-1") << (inside[2] ? " " : " 0-2") << std::endl; + << (inside[1] ? " " : " 2-1") << (inside[2] ? " " : " 0-2"); + if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1}) == true) { std::cout << " near a vertex"; } + std::cout << std::endl; // could test pairs of inside01 etc to detect crossing a vertex } else if (cd.pm.flags.test (pm_fl::colinear) == true) { // Movement was colinear. Set Crossed vertex? std::cout << "ccl: movement was colinear!\n"; - if (cd.pm.flags.test (pm_fl::no_cross_point)) { + if (cd.pm.flags.test (pm_fl::crossed) == false) { std::cout << "ccl: Colinear along edge" << std::endl; } else { std::cout << "ccl: Colinear to vertex" << std::endl; } - // cd.pm.no_cross_point will tell if there's a cross point or not + // cd.pm.crossed will tell if there's a cross point or not } else { // We have NO crossing, which can occur for a variety of reasons std::cout << "ccl: No crossings " << (inside[0] ? " " : "!!0-1") @@ -1424,7 +1461,7 @@ namespace mplot sm::vec _mv_orthog = _tn * (mv_sf.dot (_tn) / (_tn.dot (_tn))); // This tn needs to be in scene frame sm::vec _mv_inplane = mv_sf - _mv_orthog; // scene frame, a relative movement crossing_data cd = this->compute_crossing_location (tv_nb, _ti, hov_sf, _mv_inplane); - if (cd.pm.flags.test (pm_fl::no_cross_point) == false) { + if (cd.pm.flags.test (pm_fl::crossed) == true) { this->ti0 = _ti; tn0 = _tn; tv_sf = tv_nb; @@ -1552,12 +1589,11 @@ namespace mplot if constexpr (debug_move) { std::cout << "Found a neighbour because endmv lands in it\n"; } // We got the neighbour. Now find the edge/vertex that joins them, to construct crossing_data for return // We have ti0 and nb0. Do they have a twinned edge? - bool is_vertex = false; uint32_t twinned = this->test_twin (this->ti0, nb0); if (twinned == std::numeric_limits::max()) { // If twinned is not set, do they have a twinned vertex? That's a vertex with the same index in this->vertex - is_vertex = true; twinned = this->test_vertex_twin (this->ti0, nb0); + cd.pm.flags.set (pm_fl::near_vertex_0); // shared vertex of twinned will be v[0] if (twinned == std::numeric_limits::max()) { // Found no neighbour! Weird. throw std::runtime_error ("Found end triangle, and know ti0, but found no twinning edge or vertex between them ?!?!"); @@ -1567,7 +1603,7 @@ namespace mplot cd.halfedge = twinned; // Now process twinned... - if (is_vertex) { + if (cd.pm.flags.test (pm_fl::near_vertex_0)) { // Build edge vector from normals in two triangles throw std::runtime_error ("Create vertex crossing edge vector"); } else { @@ -1586,20 +1622,23 @@ namespace mplot cd.pm.flags.reset(); } else { + if constexpr (debug_move) { std::cout << "No neighbour landed in, do plane testing for ray " << hov_sf << "," << mv_inplane << "\n"; } // No neighbour was landed in; so do the plane test approach for each of the three edges of ti0 uint32_t hi = ti0; do { sm::vec tri_edge = this->edge_vector (hi, model_to_scene); sm::vec, 2> dp = this->dividing_plane (hi, this->halfedge[hi].twin, tri_edge, model_to_scene); + std::cout << "Find ray_plane_intersection for plane " << dp[0] << " -- " << dp[1] << std::endl; float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } - if (pdist < 0.00001f) { - std::cout << "ON BOUNDARY of " << hi << std::endl; // now do something + + if (pdist < 0.00001f) { // what threshold? Get case where ray_tri_intersection does not give hit for adjacent triangles, and pdist is 0.000127231 + std::cout << "CLOSE to BOUNDARY of " << hi << std::endl; // now do something cd.pm.mv = mv_inplane; cd.pm.end = hov_sf + mv_inplane; cd.halfedge = hi; cd.tri_edge = tri_edge; - cd.pm.flags.set (pm_fl::no_cross_point, true); + cd.pm.flags.set (pm_fl::crossed, false); break; } hi = this->halfedge[hi].next; @@ -1609,6 +1648,86 @@ namespace mplot return cd; } + // Find which triangle we crossed into over the vertex. Update cd with new tri_edge + uint32_t find_triangle_over_vertex (crossing_data& cd, const sm::vec& mv_inplane, const sm::mat& model_to_scene) + { + constexpr bool debug_move = true; + + uint32_t newt = std::numeric_limits::max(); + if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1}) == false) { return newt; } + + if constexpr (debug_move) { + std::cout << "Finding triangle after crossing halfedge " << cd.halfedge << " near vertex " + << (cd.pm.flags.test (pm_fl::near_vertex_0) ? "0" : "1") << "\n"; + } + + uint32_t cv = cd.pm.flags.test (pm_fl::near_vertex_0) ? cd.halfedge : this->halfedge[cd.halfedge].next; + if constexpr (debug_move) { std::cout << "Vertex is represented by halfedge " << cv << std::endl; } + + std::vector nbs = find_neighbours (cv); + + // The 'from' triangle + sm::vec, 3> tv_frm = this->triangle_vertices (cv, model_to_scene); + sm::vec tn_frm = this->triangle_normal (tv_frm); + if constexpr (debug_move) { + std::cout << "FROM triangle: " << tv_frm << std::endl; + std::cout << "FROM normal: " << tv_frm.mean() << "," << tn_frm << std::endl; + } + + for (auto _ti : nbs) { + + if (_ti == cv) { continue; } // Don't test crossing into self + + // Does mv_rest pass through this neighbour? + sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + + // Have to reorient to each neighbour to test + auto _tn = this->triangle_normal (tv_nb); + if constexpr (debug_move) { + std::cout << "Test candidate movement in _ti: " << _ti << " " << tv_nb; + std::cout << "\n norm: " << tv_nb.mean() << "," << _tn << "\n"; + } + + auto r_axis = tn_frm.cross (_tn); + std::cout << "Rotation axis: " << r_axis << std::endl; + r_axis.renormalize(); + // Compute the reorientation due to the requested movement into this neighbour + float rotn_angle = tn_frm.angle (_tn, r_axis); + // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation + if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } + sm::mat reorient_model; // reorientation transformation in sf + reorient_model.rotate (r_axis, rotn_angle); + sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); + const float rl = mv_rest.length(); + if (std::isnan (rl)) { throw std::runtime_error ("NaN in mv_rest"); } + // Now test points along mv_rest to be in + if constexpr (debug_move) { std::cout << "candidate mv_rest is " << cd.pm.end << "," << mv_rest << std::endl; } + // The far edge will be + uint32_t faredge = this->halfedge[_ti].next; + const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[_ti].vi[1]].p).less_one_dim(); + const sm::vec fee = (model_to_scene * this->vertex[this->halfedge[faredge].vi[1]].p).less_one_dim(); + if constexpr (debug_move) { std::cout << "Far edge is " << fes << "," << (fee - fes) << std::endl; } + partial_movement pm = find_edge_crossing (fes, fee, _tn, cd.pm.end, mv_rest); + if (pm.flags.test (pm_fl::crossed) == true) { + if constexpr (debug_move) { std::cout << "Break on cross point with triangle (" << _ti << ")\n"; } + newt = _ti; + cd.tri_edge = r_axis; + break; + } else { + // No crossing, did we land in the triangle? + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], cd.pm.end + mv_rest + (_tn / 2.0f), -_tn); + if (is) { // then we DID land in this neighbour tri + if constexpr (debug_move) { std::cout << "Break as we landed IN triangle (" << _ti << ")\n"; } + newt = _ti; + cd.tri_edge = r_axis; + break; + } + } + } + + return newt; + } + /** * Move across triangles, until at the end of mv_inplane. * @@ -1646,97 +1765,115 @@ namespace mplot // If it failed to find a cross point, then we test inside the triangle and neighbours // (Also if we moved colinearly along edge past a vertex) - if ((cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::no_cross_point) == false) - || (cd.pm.flags.test (pm_fl::colinear) == false && cd.pm.flags.test (pm_fl::no_cross_point) == true)) { + if ((cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::crossed) == true) + || (cd.pm.flags.test (pm_fl::colinear) == false && cd.pm.flags.test (pm_fl::crossed) == false)) { // Now test if our movement stays within the triangle sm::vec endmv = (cam_to_surface * sm::vec{}).less_one_dim() + mv_inplane; if constexpr (debug_move) { - std::cout << "endmv intersection with ti0 " << ti0 << " test vector " << (endmv + (tn0 / 2.0f)) << "," << -tn0 << " with tri " << tv_sf << std::endl;; + std::cout << "endmv intersection with ti0 " << ti0 << " test vector " << (endmv + (tn0 / 2.0f)) << "," << -tn0 << " with tri " << tv_sf << std::endl; } auto [isect, isectpoint] = sm::geometry::ray_tri_intersection (tv_sf[0], tv_sf[1], tv_sf[2], endmv + (tn0 / 2.0f), -tn0); if constexpr (debug_move) { std::cout << "End lands in ? " << (isect ? "Y" : "N") << std::endl; } if (isect == false) { // Didn't find edge crossing or that the end point is within ti0, so now search neighbours for an end point or boundary crossing. + std::cout << "About to use find_nearest_boundary_crossing function...\n"; cd = find_nearest_boundary_crossing (hov_sf, mv_inplane, model_to_scene); std::cout << "find_nearest_boundary_crossing returns cd which has cd.pm.flags: " << cd.pm.flags << std::endl; } - } + } // else We HAVE a crossing of some sort. - // crossing_data gives us info about if there is NO cross point in the partial mv no_cross_point flag - if (cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::no_cross_point) == true) { + // crossing_data gives us info about if there is NO cross point in the partial mv crossed flag + if (cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::crossed) == false) { std::cout << "A: Movement stays inside triangle ti0 (colinear within boundary\n"; cam_to_surface.pretranslate (mv_inplane); done = true; - } else if (cd.pm.flags.test (pm_fl::no_cross_point) == true) { // move a bit, shorten mv_inplane + } else if (cd.pm.flags.test (pm_fl::crossed) == false) { // move a bit, shorten mv_inplane // mv_inplane moved camera inside triangle. std::cout << "A: Movement stays inside triangle ti0\n"; cam_to_surface.pretranslate (mv_inplane); done = true; } else { - std::cout << "B: Crossed a boundary/vertex\n"; + // _ti, _tn are the new triangle sm::vec _tn = {}; uint32_t _ti = std::numeric_limits::max(); - // new triangle is the twin of the crossed edge. May not be the case if we crossed a vertex. - _ti = this->halfedge[cd.halfedge].twin; - if constexpr (debug_move) { - std::cout << "find triangle across edge: halfedge " << cd.halfedge << " twins to: " << _ti << std::endl; + if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1})) { + if (cd.pm.flags.test (pm_fl::near_vertex_0)) { + std::cout << "B: Crossed near vertex 0 of crossed edge\n"; + } else if (cd.pm.flags.test (pm_fl::near_vertex_1)) { + std::cout << "B: Crossed near vertex 1 of crossed edge\n"; + } + + // The right triangle to reorient onto may not be the twin across the crossed edge + // Check all neighbours of the crossed vertex to find out if our path travels through. + _ti = find_triangle_over_vertex (cd, mv_inplane, model_to_scene); + // FIXME: Need to set cd.tri_edge + + } else { + std::cout << "B: Crossed a boundary\n"; + // new triangle is the twin of the crossed edge. + _ti = this->halfedge[cd.halfedge].twin; + if constexpr (debug_move) { + std::cout << "find triangle across edge: halfedge " << cd.halfedge << " twins to: " << _ti << std::endl; + } } if (_ti == std::numeric_limits::max()) { // We probably went off the edge of our navigation model mesh this->ti0 = ti0_save; throw std::runtime_error ("off-edge: The movement went off the edge of the model"); - } else { - // Re-orient onto the new triangle - sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); - if (newtv_sf[0][0] == std::numeric_limits::max()) { - this->ti0 = ti0_save; - throw std::runtime_error ("off-edge: The movement went off the edge of the model"); - continue; - } else { - _tn = this->triangle_normal (newtv_sf); - if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } - // Compute the reorientation due to the requested movement. - float rotn_angle = tn0.angle (_tn, cd.tri_edge); - // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation - if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } - sm::mat reorient_model; // reorientation transformation in sf - // No good if cd.tri_edge is (0,0,0)! - reorient_model.rotate (cd.tri_edge, rotn_angle); - sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); - if (std::isnan (mv_rest.length())) { - if constexpr (debug_move) { - std::cout << "Got NaN in mv_rest. mv_inplane: " << mv_inplane << ", cd.pm.mv: " << cd.pm.mv << "reorient_model:" << std::endl; - std::cout << "reorient_model created from tri_edge " << cd.tri_edge << " and rotn_angle " << rotn_angle << std::endl; - std::cout << reorient_model << std::endl; - } - throw std::runtime_error ("NaN in mv_rest"); - } - reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); - reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf + } - if (mv_rest.length() == 0.0f) { - // The first movement to edge completed the movement. We actually landed ON the edge. - cam_to_surface = reorient_model * cam_to_surface; - done = true; - } else { - // There's additional movement to complete. - if constexpr (debug_move) { std::cout << "mv_rest length is " << mv_rest.length() << std::endl; } - // Loop To Next. Final movement should be exclusively within a triangle. - reorient_model.pretranslate (-mv_rest); - cam_to_surface = reorient_model * cam_to_surface; - hov_sf = cd.pm.end; // crossing data planned movement end - // Also update planned move, which is now shorter and in a new direction - tv_sf = newtv_sf; - mv_inplane = mv_rest; + // Re-orient onto the new triangle + sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); + if (newtv_sf[0][0] == std::numeric_limits::max()) { + this->ti0 = ti0_save; + throw std::runtime_error ("off-edge: The movement went off the edge of the model"); + continue; + } else { + _tn = this->triangle_normal (newtv_sf); + if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } + // Compute the reorientation due to the requested movement. + float rotn_angle = tn0.angle (_tn, cd.tri_edge); + // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation + if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } + sm::mat reorient_model; // reorientation transformation in sf + // No good if cd.tri_edge is (0,0,0)! + reorient_model.rotate (cd.tri_edge, rotn_angle); + sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); + const float rl = mv_rest.length(); + if (std::isnan (rl)) { + if constexpr (debug_move) { + std::cout << "Got NaN in mv_rest. mv_inplane: " << mv_inplane << ", cd.pm.mv: " << cd.pm.mv << "reorient_model:" << std::endl; + std::cout << "reorient_model created from tri_edge " << cd.tri_edge << " and rotn_angle " << rotn_angle << std::endl; + std::cout << reorient_model << std::endl; } + throw std::runtime_error ("NaN in mv_rest"); + } + reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); + reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf - this->ti0 = _ti; - tn0 = _tn; + if (rl <= std::numeric_limits::epsilon()) { + // The first movement to edge completed the movement. We actually landed ON the edge. + cam_to_surface = reorient_model * cam_to_surface; + done = true; + } else { + // There's additional movement to complete. + if constexpr (debug_move) { std::cout << "mv_rest length is " << rl << std::endl; } + // Loop To Next. Final movement should be exclusively within a triangle. + reorient_model.pretranslate (-mv_rest); + cam_to_surface = reorient_model * cam_to_surface; + hov_sf = cd.pm.end; // crossing data planned movement end + // Also update planned move, which is now shorter and in a new direction + tv_sf = newtv_sf; + mv_inplane = mv_rest; } + + this->ti0 = _ti; + tn0 = _tn; } + } // compute_crossing_location if/else } // triangle traversing while loop From 120549a2ea7fe17164e4a811e8f8b94e51963edf Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 18 Feb 2026 17:04:22 +0000 Subject: [PATCH 75/82] WIP. Unfinished work to do in find_nearest_boundary_crossing --- mplot/NavMesh.h | 97 +++++++++++++++++++++++++++++++------------------ 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 7bb6b313..f18638c2 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1549,39 +1549,53 @@ namespace mplot constexpr bool debug_move = true; crossing_data cd; - // Get neighbours of ti0 - auto nbs = this->find_neighbours (this->ti0); - - // For each neighbour, test to see if start location was inside a neighbour +#if 0 // The going-through-neighbours logic is OK, but the test is not right (see find_triangle_over_vertex) uint32_t nb0 = std::numeric_limits::max(); - sm::vec _tn = {}; - sm::vec mv_orthog_nb = {}; - sm::vec mv_inplane_nb = {}; - sm::vec, 3> tv_nb = {}; - for (auto nb : nbs) { - - if (nb == ti0) { continue; } // Don't test self (i.e. ti0) - - tv_nb = this->triangle_vertices (nb, model_to_scene); - if (tv_nb[0][0] == std::numeric_limits::max()) { - // tv_nb is not a triangle. Could be an off-edge triangle - continue; - } else { - // Construct the mv_inplane within this neighbour and test if it lands inside - _tn = this->triangle_normal (tv_nb); - mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "Neighbour: " << nb << ": test intersect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { std::cout << "End lands IN? " << (endis ? "Y" : "N") << std::endl; } - if (endis) { - nb0 = nb; - break; + // for each vertex in ti0... + uint32_t _ti = this->ti0; + + std::set tested; + tested.insert (this->ti0); // never test ti0 + + // Ok - so this testing neighbours thing is fallible. Or rather, I would need to recompute the mv_rest like I did in find_triangle_over_vertex + for (uint32_t i = 0; i < 3; ++i) { + // Get neighbours of _ti + auto nbs = this->find_neighbours (_ti); + + // For each neighbour, test to see if start location was inside a neighbour + sm::vec _tn = {}; + sm::vec mv_orthog_nb = {}; + sm::vec mv_inplane_nb = {}; + sm::vec, 3> tv_nb = {}; + for (auto nb : nbs) { + + if (tested.count(nb) > 0) { continue; } // Don't test already-tested + tested.insert (nb); // Oops: keep counting same triangle as each tri has 3 halfedges + + tv_nb = this->triangle_vertices (nb, model_to_scene); + if (tv_nb[0][0] == std::numeric_limits::max()) { + // tv_nb is not a triangle. Could be an off-edge triangle + continue; + } else { + // Construct the mv_inplane within this neighbour and test if it lands inside + _tn = this->triangle_normal (tv_nb); + mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); + mv_inplane_nb = mv_inplane - mv_orthog_nb; + if constexpr (debug_move) { + std::cout << "Neighbour: " << nb << ": test intersect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; + } + auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); + if constexpr (debug_move) { std::cout << "End lands IN? " << (endis ? "Y" : "N") << std::endl; } + + if (endis) { + nb0 = nb; + break; + } } } + + _ti = this->halfedge[_ti].next; } // Did we get a result? @@ -1622,28 +1636,41 @@ namespace mplot cd.pm.flags.reset(); } else { - if constexpr (debug_move) { std::cout << "No neighbour landed in, do plane testing for ray " << hov_sf << "," << mv_inplane << "\n"; } +#endif + if constexpr (debug_move) { std::cout << "Do plane testing for ray " << hov_sf << "," << mv_inplane << "\n"; } // No neighbour was landed in; so do the plane test approach for each of the three edges of ti0 uint32_t hi = ti0; do { sm::vec tri_edge = this->edge_vector (hi, model_to_scene); - sm::vec, 2> dp = this->dividing_plane (hi, this->halfedge[hi].twin, tri_edge, model_to_scene); - std::cout << "Find ray_plane_intersection for plane " << dp[0] << " -- " << dp[1] << std::endl; + sm::vec te_start = this->edge_start (hi, model_to_scene); + if constexpr (debug_move) { std::cout << "Test edge plane for " << te_start << "," << tri_edge << std::endl; } + + const uint32_t twin = this->halfedge[hi].twin; + sm::vec, 2> dp = this->dividing_plane (hi, twin, tri_edge, model_to_scene); + + if constexpr (debug_move) { + std::cout << "For twin triangle " << this->triangle_vertices (twin, model_to_scene) << std::endl; + std::cout << "Dividing plane: " << dp[0] << "," << (dp[0] + tri_edge) << "," << (dp[0] + tri_edge.cross (dp[1])) << std::endl; + } + float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } - if (pdist < 0.00001f) { // what threshold? Get case where ray_tri_intersection does not give hit for adjacent triangles, and pdist is 0.000127231 - std::cout << "CLOSE to BOUNDARY of " << hi << std::endl; // now do something + if (pdist > 0.0f) { // what does distance mean? If >0 then good? But will pass through two planes... + // Find distance from cross point to edge? FIXME... + if constexpr (debug_move) { std::cout << "CLOSE to BOUNDARY of " << hi << std::endl; } cd.pm.mv = mv_inplane; cd.pm.end = hov_sf + mv_inplane; cd.halfedge = hi; cd.tri_edge = tri_edge; - cd.pm.flags.set (pm_fl::crossed, false); + cd.pm.flags.set (pm_fl::crossed, true); break; } hi = this->halfedge[hi].next; } while (hi != ti0); +#if 0 } +#endif return cd; } From 48329c031a9545a0af6ffd44427c3a392f4d6f81 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 19 Feb 2026 17:15:13 +0000 Subject: [PATCH 76/82] WIP from the office --- mplot/NavMesh.h | 296 ++++++++++++++++++++++++++++-------------------- 1 file changed, 173 insertions(+), 123 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index f18638c2..dd631022 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -296,6 +296,26 @@ namespace mplot bool verify_triangle (const uint32_t tri_hi, const bool debug = false) const { return verify_halfedge_chain (tri_hi, 3, debug); } + /** + * Return the three indices in the triangle containing halfedge hi + * + * A single max value in the set indicates an error + */ + std::set triangle_indices (uint32_t hi) const + { + std::set indices; + if (hi == std::numeric_limits::max()) { + indices.insert (hi); + return indices; + } + for (uint32_t i = 0; i < 3; ++i) { + indices.insert (hi); + hi = this->halfedge[hi].next; + if (hi >= this->halfedge.size()) { break; } + } + return indices; + } + // Return the three vertices for the triangle specified as three indices into NavMesh::vertex sm::vec, 3> triangle_vertices (uint32_t tri_hi) const { @@ -322,7 +342,7 @@ namespace mplot } // Return the three vertices for the triangle specified as three indices into NavMesh::vertex transformed by transform - sm::vec, 3> triangle_vertices (uint32_t tri_hi, const sm::mat& transform) const + sm::vec, 3> triangle_vertices (const uint32_t tri_hi, const sm::mat& transform) const { sm::vec, 3> trivert = {}; if (tri_hi == std::numeric_limits::max()) { @@ -602,8 +622,8 @@ namespace mplot [[maybe_unused]] uint32_t eb = i + band < sz ? i + band : sz; uint32_t wider = 0; - -#if 0 // Simplest code +#if 0 + // The Simplest code would be a single loop for (uint32_t j = 0; j < sz; ++j) { if (j == i) { continue; } const sm::vec& vij = this->halfedge[j].vi; @@ -613,7 +633,8 @@ namespace mplot break; } } -#else // Optimize +#endif + // But it's worth optimizing: // First sb to eb, which we hope is most likely to find a twin for (uint32_t j = sb; j < eb; ++j) { if (j == i) { continue; } @@ -652,7 +673,7 @@ namespace mplot } } } -#endif + wider_searches += wider; if (this->halfedge[i].twin != std::numeric_limits::max()) { @@ -1541,7 +1562,7 @@ namespace mplot return p0_n; } - // By searching neighbours, find a boundary crossing. + // By searching over-the-vertex neighbours, find a boundary crossing. crossing_data find_nearest_boundary_crossing (const sm::vec& hov_sf, const sm::vec& mv_inplane, const sm::mat& model_to_scene) @@ -1549,133 +1570,201 @@ namespace mplot constexpr bool debug_move = true; crossing_data cd; -#if 0 // The going-through-neighbours logic is OK, but the test is not right (see find_triangle_over_vertex) uint32_t nb0 = std::numeric_limits::max(); + // Get the 'from' triangle normal + sm::vec, 3> tv_frm = this->triangle_vertices (this->ti0, model_to_scene); + sm::vec tn_frm = this->triangle_normal (tv_frm); + // for each vertex in ti0... uint32_t _ti = this->ti0; - std::set tested; - tested.insert (this->ti0); // never test ti0 + std::set> tested; + tested.insert (this->triangle_indices(this->ti0)); // never test ti0 - // Ok - so this testing neighbours thing is fallible. Or rather, I would need to recompute the mv_rest like I did in find_triangle_over_vertex for (uint32_t i = 0; i < 3; ++i) { + + const sm::vec vtx_loc = this->edge_start (_ti, model_to_scene); + const sm::vec to_vtx = vtx_loc - hov_sf; + // to_vtx.cross (mv_inplane) should be very short + auto cp = to_vtx.cross (mv_inplane); + std::cout << "i = " << i << ", cp.length(): " << cp.length() + << " and to_vtx.dot (mv_inplane) / to_vtx.length() = " + << (to_vtx.dot (mv_inplane) / to_vtx.length()) << std::endl; + + // Check to see if vtx_loc is in the direction mv_inplane... + if (to_vtx.dot (mv_inplane) < 0.0f) { + // Then mv_inplane points away from this vtx + _ti = this->halfedge[_ti].next; + continue; + } + // Get neighbours of _ti auto nbs = this->find_neighbours (_ti); + const sm::vec mv_rest = mv_inplane - to_vtx; // For each neighbour, test to see if start location was inside a neighbour - sm::vec _tn = {}; - sm::vec mv_orthog_nb = {}; - sm::vec mv_inplane_nb = {}; - sm::vec, 3> tv_nb = {}; for (auto nb : nbs) { - - if (tested.count(nb) > 0) { continue; } // Don't test already-tested - tested.insert (nb); // Oops: keep counting same triangle as each tri has 3 halfedges - - tv_nb = this->triangle_vertices (nb, model_to_scene); - if (tv_nb[0][0] == std::numeric_limits::max()) { - // tv_nb is not a triangle. Could be an off-edge triangle - continue; - } else { - // Construct the mv_inplane within this neighbour and test if it lands inside - _tn = this->triangle_normal (tv_nb); - mv_orthog_nb = _tn * (mv_inplane.dot (_tn) / (_tn.dot(_tn))); - mv_inplane_nb = mv_inplane - mv_orthog_nb; - if constexpr (debug_move) { - std::cout << "Neighbour: " << nb << ": test intersect vector " << (hov_sf + mv_inplane_nb + (_tn / 2.0f)) << "," << -_tn << " with tri " << tv_nb << std::endl;; - } - auto [endis, endh] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + mv_inplane_nb + (_tn / 2.0f), -_tn); - if constexpr (debug_move) { std::cout << "End lands IN? " << (endis ? "Y" : "N") << std::endl; } - - if (endis) { - nb0 = nb; - break; - } + std::set ind = this->triangle_indices(nb); + if (tested.count (ind) > 0) { continue; } // Don't test already-tested + tested.insert (ind); + auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (nb, tn_frm, mv_rest, vtx_loc, model_to_scene); + if (found_in) { + nb0 = nb; + cd.halfedge = _ti; // the vtx halfedge + cd.tri_edge = r_axis; + cd.pm.mv = _mv_rest; + cd.pm.end = vtx_loc; + cd.pm.flags.set (pm_fl::crossed); + break; // also need to break out of outer loop } } - + if (nb0 != std::numeric_limits::max()) { break; } _ti = this->halfedge[_ti].next; } // Did we get a result? if (nb0 != std::numeric_limits::max()) { - if constexpr (debug_move) { std::cout << "Found a neighbour because endmv lands in it\n"; } + if constexpr (debug_move) { std::cout << "detected a neighbour with detect_movement_in_neighbour()\n"; } +#if 0 // We got the neighbour. Now find the edge/vertex that joins them, to construct crossing_data for return // We have ti0 and nb0. Do they have a twinned edge? uint32_t twinned = this->test_twin (this->ti0, nb0); if (twinned == std::numeric_limits::max()) { // If twinned is not set, do they have a twinned vertex? That's a vertex with the same index in this->vertex twinned = this->test_vertex_twin (this->ti0, nb0); + cd.pm.flags.set (pm_fl::near_vertex_0); // shared vertex of twinned will be v[0] if (twinned == std::numeric_limits::max()) { // Found no neighbour! Weird. throw std::runtime_error ("Found end triangle, and know ti0, but found no twinning edge or vertex between them ?!?!"); if constexpr (debug_move) { std::cout << "...across a vertex\n"; } } + } else { + std::cout << "We have a twin edge joining ti0 " << ti0 << " and nb " << nb0 << std::endl; } cd.halfedge = twinned; // Now process twinned... +#endif + // cd should have been set up... + +#if 0 // will have set tri_edge already if (cd.pm.flags.test (pm_fl::near_vertex_0)) { - // Build edge vector from normals in two triangles - throw std::runtime_error ("Create vertex crossing edge vector"); + // Have set cd.tri_edge = r_axis, above + std::cout << "cd.tri_edge should have been set to " << cd.tri_edge << std::endl; } else { cd.tri_edge = this->edge_vector (twinned, model_to_scene); } + sm::vec, 2> dp = this->dividing_plane (ti0, twinned, cd.tri_edge, model_to_scene); float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane + + // Think this logic is flawed if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } + // To find end coord, look at mv_inplane and cd.tri_edge - sm::vec mv_start = mv_inplane; - mv_start.renormalize(); + sm::vec mv_start = mv_inplane; // No - this needs to be the mv_rest inside detect_movement_in_neighbour + //mv_start.renormalize(); mv_start *= pdist; + // Find closest location on cd.tri_edge to mv_inplane. That allows the construction of cd.pm.mv and cd.pm.end - cd.pm.mv = mv_start; // movement vector to the crossing - cd.pm.end = hov_sf + mv_start; // end coord of the crossing - cd.pm.flags.reset(); + //cd.pm.mv = mv_start; // movement vector to the crossing + std::cout << "movement to the crossing: " << cd.pm.mv << std::endl; + //cd.pm.end = hov_sf + mv_start; // end coord of the crossing + std::cout << "end (the crossing): " << cd.pm.end << std::endl; - } else { + cd.pm.flags.reset(); // ? #endif - if constexpr (debug_move) { std::cout << "Do plane testing for ray " << hov_sf << "," << mv_inplane << "\n"; } - // No neighbour was landed in; so do the plane test approach for each of the three edges of ti0 - uint32_t hi = ti0; - do { - sm::vec tri_edge = this->edge_vector (hi, model_to_scene); - sm::vec te_start = this->edge_start (hi, model_to_scene); - if constexpr (debug_move) { std::cout << "Test edge plane for " << te_start << "," << tri_edge << std::endl; } + } else { - const uint32_t twin = this->halfedge[hi].twin; - sm::vec, 2> dp = this->dividing_plane (hi, twin, tri_edge, model_to_scene); + if constexpr (debug_move) { std::cout << "Do some other kind of test for ray " << hov_sf << "," << mv_inplane << "\n"; } + throw std::runtime_error ("Out of ideas"); // Had a plane-testing approach, but it didn't work + } - if constexpr (debug_move) { - std::cout << "For twin triangle " << this->triangle_vertices (twin, model_to_scene) << std::endl; - std::cout << "Dividing plane: " << dp[0] << "," << (dp[0] + tri_edge) << "," << (dp[0] + tri_edge.cross (dp[1])) << std::endl; - } + return cd; + } - float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane - if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } + /** + * Did the movement pass through the given neighbour triangle, which is probably a + * neighbour-over-a-vertex? + * + * \param _ti The triangle index of the 'from' triangle. + * + * \param tn_frm The normal of the 'from' triangle. + * + * \param mv is the movement, assumed to start in the boundary from triangle, in the plane + * of the from triangle + * + * \param start is the start the movement in the new triangle (and the end in the 'from' + * triangle). I.e. it's on the border + * + * \param model_to_scene Transform required to obtain triangle vertices in scene frame + * + * \return tuple containing true/false was a crossing found; the rotation axis and the rest of the movement + */ + std::tuple, sm::vec> + detect_movement_in_neighbour (const uint32_t _ti, + const sm::vec& tn_frm, + const sm::vec& mv, + const sm::vec& start, + const sm::mat& model_to_scene) + { + constexpr bool debug_move = true; - if (pdist > 0.0f) { // what does distance mean? If >0 then good? But will pass through two planes... - // Find distance from cross point to edge? FIXME... - if constexpr (debug_move) { std::cout << "CLOSE to BOUNDARY of " << hi << std::endl; } - cd.pm.mv = mv_inplane; - cd.pm.end = hov_sf + mv_inplane; - cd.halfedge = hi; - cd.tri_edge = tri_edge; - cd.pm.flags.set (pm_fl::crossed, true); - break; - } - hi = this->halfedge[hi].next; - } while (hi != ti0); -#if 0 + bool found = false; + + // Does mv_rest pass through this neighbour? + sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + + // Have to reorient to each neighbour to test + auto _tn = this->triangle_normal (tv_nb); + if constexpr (debug_move) { + std::cout << "Test candidate movement in _ti: " << _ti << " " << tv_nb; + std::cout << "\n norm: " << tv_nb.mean() << "," << (_tn * 0.05f) << "\n"; + std::cout << "start: " << start << " mv : " << mv << "\n"; } -#endif - return cd; + sm::vec r_axis = tn_frm.cross (_tn); + std::cout << "Rotation axis: " << start << "," << r_axis << std::endl; + r_axis.renormalize(); + // Compute the reorientation due to the requested movement into this neighbour + float rotn_angle = tn_frm.angle (_tn, r_axis); + // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation + if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } + sm::mat reorient_model; // reorientation transformation in sf between these two triangles + reorient_model.rotate (r_axis, rotn_angle); + // The 'new' mv_rest + sm::vec _mv_rest = (reorient_model * mv).less_one_dim(); + const float rl = _mv_rest.length(); + if (std::isnan (rl)) { return {found, r_axis, _mv_rest}; } + // Now test points along mv_rest to be in + if constexpr (debug_move) { std::cout << "candidate _mv_rest is " << start << "," << _mv_rest << std::endl; } + // The far edge will be + uint32_t faredge = this->halfedge[_ti].next; + const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[_ti].vi[1]].p).less_one_dim(); + const sm::vec fee = (model_to_scene * this->vertex[this->halfedge[faredge].vi[1]].p).less_one_dim(); + if constexpr (debug_move) { std::cout << "Far edge is " << fes << "," << (fee - fes) << std::endl; } + partial_movement pm = find_edge_crossing (fes, fee, _tn, start, _mv_rest); + if (pm.flags.test (pm_fl::crossed) == true) { + if constexpr (debug_move) { std::cout << "Found cross point with triangle (" << _ti << ")\n"; } + found = true; + } else { + // No crossing, did we land in the triangle? + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], start + _mv_rest + (_tn / 2.0f), -_tn); + if (is) { // then we DID land in this neighbour tri + if constexpr (debug_move) { std::cout << "Found, as we landed IN triangle (" << _ti << ")\n"; } + found = true; + } + } + + return {found, r_axis, _mv_rest}; } - // Find which triangle we crossed into over the vertex. Update cd with new tri_edge + /** + * Find which triangle we crossed into over the vertex. Update cd with new + * tri_edge. crossing_data required here for + */ uint32_t find_triangle_over_vertex (crossing_data& cd, const sm::vec& mv_inplane, const sm::mat& model_to_scene) { constexpr bool debug_move = true; @@ -1693,7 +1782,7 @@ namespace mplot std::vector nbs = find_neighbours (cv); - // The 'from' triangle + // Get the 'from' triangle normal sm::vec, 3> tv_frm = this->triangle_vertices (cv, model_to_scene); sm::vec tn_frm = this->triangle_normal (tv_frm); if constexpr (debug_move) { @@ -1701,54 +1790,15 @@ namespace mplot std::cout << "FROM normal: " << tv_frm.mean() << "," << tn_frm << std::endl; } + const sm::vec mv_rest = mv_inplane - cd.pm.mv; + const sm::vec end_at_border = cd.pm.end; for (auto _ti : nbs) { - if (_ti == cv) { continue; } // Don't test crossing into self + auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (_ti, tn_frm, mv_rest, end_at_border, model_to_scene); - // Does mv_rest pass through this neighbour? - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - - // Have to reorient to each neighbour to test - auto _tn = this->triangle_normal (tv_nb); - if constexpr (debug_move) { - std::cout << "Test candidate movement in _ti: " << _ti << " " << tv_nb; - std::cout << "\n norm: " << tv_nb.mean() << "," << _tn << "\n"; - } - - auto r_axis = tn_frm.cross (_tn); - std::cout << "Rotation axis: " << r_axis << std::endl; - r_axis.renormalize(); - // Compute the reorientation due to the requested movement into this neighbour - float rotn_angle = tn_frm.angle (_tn, r_axis); - // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation - if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } - sm::mat reorient_model; // reorientation transformation in sf - reorient_model.rotate (r_axis, rotn_angle); - sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); - const float rl = mv_rest.length(); - if (std::isnan (rl)) { throw std::runtime_error ("NaN in mv_rest"); } - // Now test points along mv_rest to be in - if constexpr (debug_move) { std::cout << "candidate mv_rest is " << cd.pm.end << "," << mv_rest << std::endl; } - // The far edge will be - uint32_t faredge = this->halfedge[_ti].next; - const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[_ti].vi[1]].p).less_one_dim(); - const sm::vec fee = (model_to_scene * this->vertex[this->halfedge[faredge].vi[1]].p).less_one_dim(); - if constexpr (debug_move) { std::cout << "Far edge is " << fes << "," << (fee - fes) << std::endl; } - partial_movement pm = find_edge_crossing (fes, fee, _tn, cd.pm.end, mv_rest); - if (pm.flags.test (pm_fl::crossed) == true) { - if constexpr (debug_move) { std::cout << "Break on cross point with triangle (" << _ti << ")\n"; } + if (found_in) { newt = _ti; cd.tri_edge = r_axis; - break; - } else { - // No crossing, did we land in the triangle? - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], cd.pm.end + mv_rest + (_tn / 2.0f), -_tn); - if (is) { // then we DID land in this neighbour tri - if constexpr (debug_move) { std::cout << "Break as we landed IN triangle (" << _ti << ")\n"; } - newt = _ti; - cd.tri_edge = r_axis; - break; - } } } @@ -1936,7 +1986,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "\n# compute_mesh_movement:\n" << "\nti0: " << this->ti0 - << "\nti0 (sf): " << tv_sf << "\nnormal " << tn0 + << "\nti0 (sf): " << tv_sf << "\nnormal " << tv_sf.mean() << "," << tn0 << "\nmovement (camframe): " << mv_camframe << "\nInitial camera location: " << cam_to_scene.translation() << "\n\n"; } From 9686a8811d8a4f1a42709d395a8d583f13a4e37b Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 20 Feb 2026 14:44:39 +0000 Subject: [PATCH 77/82] Sorts the 'over the vertex' problem file antpov_find_nearest_boundary_crossing_problem.h5 --- mplot/NavMesh.h | 208 +++++++++++++++++++++++------------------------- 1 file changed, 99 insertions(+), 109 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index dd631022..2ad45bef 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1035,10 +1035,14 @@ namespace mplot struct crossing_data { // The crossed halfedge - uint32_t halfedge = std::numeric_limits::max(); + uint32_t crossed = std::numeric_limits::max(); + // A halfedge in the triangle that we cross into. Usually set to this->halfedge[halfedge].twin + uint32_t into = std::numeric_limits::max(); // The crossed edge as a vector sm::vec tri_edge = {}; - // The partial movement. pm.mv is the movement, pm.end is the crossing point + // The remaining movement after the crossing, with direction rotated about tri_edge. max means unset + sm::vec mv_rest = { std::numeric_limits::max() }; + // The partial movement. pm.mv is the movement up to the crossing point, pm.end is the crossing point partial_movement pm = {}; }; @@ -1108,7 +1112,7 @@ namespace mplot } cd.pm = pm; cd.tri_edge = edge; - cd.halfedge = hi; + cd.crossed = hi; } } else { if constexpr (debug) { @@ -1570,17 +1574,19 @@ namespace mplot constexpr bool debug_move = true; crossing_data cd; - uint32_t nb0 = std::numeric_limits::max(); - // Get the 'from' triangle normal sm::vec, 3> tv_frm = this->triangle_vertices (this->ti0, model_to_scene); sm::vec tn_frm = this->triangle_normal (tv_frm); + if constexpr (debug_move) { + std::cout << "FROM triangle: " << tv_frm << std::endl; + std::cout << "FROM normal: " << tv_frm.mean() << "," << tn_frm << std::endl; + } // for each vertex in ti0... uint32_t _ti = this->ti0; std::set> tested; - tested.insert (this->triangle_indices(this->ti0)); // never test ti0 + tested.insert (this->triangle_indices (this->ti0)); // never test ti0 for (uint32_t i = 0; i < 3; ++i) { @@ -1605,79 +1611,33 @@ namespace mplot // For each neighbour, test to see if start location was inside a neighbour for (auto nb : nbs) { - std::set ind = this->triangle_indices(nb); + std::set ind = this->triangle_indices (nb); if (tested.count (ind) > 0) { continue; } // Don't test already-tested tested.insert (ind); + std::cout << __func__ << " calling detect_movement_in_neighbour\n"; auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (nb, tn_frm, mv_rest, vtx_loc, model_to_scene); if (found_in) { - nb0 = nb; - cd.halfedge = _ti; // the vtx halfedge + cd.crossed = _ti; // the vtx halfedge + cd.into = nb; cd.tri_edge = r_axis; - cd.pm.mv = _mv_rest; + cd.mv_rest = _mv_rest; + cd.pm.mv = to_vtx; cd.pm.end = vtx_loc; cd.pm.flags.set (pm_fl::crossed); + //cd.pm.flags.set (pm_fl::near_vertex_0); // of _ti. If I set this, then it triggers find_triangle_over... + break; // also need to break out of outer loop } } - if (nb0 != std::numeric_limits::max()) { break; } + if (cd.into != std::numeric_limits::max()) { break; } _ti = this->halfedge[_ti].next; } // Did we get a result? - if (nb0 != std::numeric_limits::max()) { + if (cd.into != std::numeric_limits::max()) { if constexpr (debug_move) { std::cout << "detected a neighbour with detect_movement_in_neighbour()\n"; } -#if 0 - // We got the neighbour. Now find the edge/vertex that joins them, to construct crossing_data for return - // We have ti0 and nb0. Do they have a twinned edge? - uint32_t twinned = this->test_twin (this->ti0, nb0); - if (twinned == std::numeric_limits::max()) { - // If twinned is not set, do they have a twinned vertex? That's a vertex with the same index in this->vertex - twinned = this->test_vertex_twin (this->ti0, nb0); - - cd.pm.flags.set (pm_fl::near_vertex_0); // shared vertex of twinned will be v[0] - if (twinned == std::numeric_limits::max()) { - // Found no neighbour! Weird. - throw std::runtime_error ("Found end triangle, and know ti0, but found no twinning edge or vertex between them ?!?!"); - if constexpr (debug_move) { std::cout << "...across a vertex\n"; } - } - } else { - std::cout << "We have a twin edge joining ti0 " << ti0 << " and nb " << nb0 << std::endl; - } - - cd.halfedge = twinned; - // Now process twinned... -#endif // cd should have been set up... - -#if 0 // will have set tri_edge already - if (cd.pm.flags.test (pm_fl::near_vertex_0)) { - // Have set cd.tri_edge = r_axis, above - std::cout << "cd.tri_edge should have been set to " << cd.tri_edge << std::endl; - } else { - cd.tri_edge = this->edge_vector (twinned, model_to_scene); - } - - sm::vec, 2> dp = this->dividing_plane (ti0, twinned, cd.tri_edge, model_to_scene); - float pdist = sm::geometry::ray_plane_intersection (dp[0], dp[1], hov_sf, mv_inplane); // returns distance along mv_inplane - - // Think this logic is flawed - if constexpr (debug_move) { std::cout << "pdist = " << pdist << std::endl; } - - // To find end coord, look at mv_inplane and cd.tri_edge - sm::vec mv_start = mv_inplane; // No - this needs to be the mv_rest inside detect_movement_in_neighbour - //mv_start.renormalize(); - mv_start *= pdist; - - // Find closest location on cd.tri_edge to mv_inplane. That allows the construction of cd.pm.mv and cd.pm.end - //cd.pm.mv = mv_start; // movement vector to the crossing - std::cout << "movement to the crossing: " << cd.pm.mv << std::endl; - //cd.pm.end = hov_sf + mv_start; // end coord of the crossing - std::cout << "end (the crossing): " << cd.pm.end << std::endl; - - cd.pm.flags.reset(); // ? -#endif } else { - if constexpr (debug_move) { std::cout << "Do some other kind of test for ray " << hov_sf << "," << mv_inplane << "\n"; } throw std::runtime_error ("Out of ideas"); // Had a plane-testing approach, but it didn't work } @@ -1689,7 +1649,7 @@ namespace mplot * Did the movement pass through the given neighbour triangle, which is probably a * neighbour-over-a-vertex? * - * \param _ti The triangle index of the 'from' triangle. + * \param nb The triangle index of the 'to' or neighbour triangle. * * \param tn_frm The normal of the 'from' triangle. * @@ -1701,10 +1661,11 @@ namespace mplot * * \param model_to_scene Transform required to obtain triangle vertices in scene frame * - * \return tuple containing true/false was a crossing found; the rotation axis and the rest of the movement + * \return tuple containing true/false was a crossing found; the rotation axis and the rest + * of the movement, reoriented to the neighbour */ std::tuple, sm::vec> - detect_movement_in_neighbour (const uint32_t _ti, + detect_movement_in_neighbour (const uint32_t nb, const sm::vec& tn_frm, const sm::vec& mv, const sm::vec& start, @@ -1715,14 +1676,15 @@ namespace mplot bool found = false; // Does mv_rest pass through this neighbour? - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); + sm::vec, 3> tv_nb = this->triangle_vertices (nb, model_to_scene); // Have to reorient to each neighbour to test auto _tn = this->triangle_normal (tv_nb); if constexpr (debug_move) { - std::cout << "Test candidate movement in _ti: " << _ti << " " << tv_nb; + std::cout << __func__ << " called\n"; + std::cout << "Test candidate movement in nb: " << nb << " " << tv_nb; std::cout << "\n norm: " << tv_nb.mean() << "," << (_tn * 0.05f) << "\n"; - std::cout << "start: " << start << " mv : " << mv << "\n"; + std::cout << "start/mv: " << start << "," << mv << "\n"; } sm::vec r_axis = tn_frm.cross (_tn); @@ -1735,37 +1697,47 @@ namespace mplot sm::mat reorient_model; // reorientation transformation in sf between these two triangles reorient_model.rotate (r_axis, rotn_angle); // The 'new' mv_rest - sm::vec _mv_rest = (reorient_model * mv).less_one_dim(); - const float rl = _mv_rest.length(); - if (std::isnan (rl)) { return {found, r_axis, _mv_rest}; } + sm::vec mv_reoriented = (reorient_model * mv).less_one_dim(); + const float rl = mv_reoriented.length(); + if (std::isnan (rl)) { return {found, r_axis, mv_reoriented}; } // Now test points along mv_rest to be in - if constexpr (debug_move) { std::cout << "candidate _mv_rest is " << start << "," << _mv_rest << std::endl; } - // The far edge will be - uint32_t faredge = this->halfedge[_ti].next; - const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[_ti].vi[1]].p).less_one_dim(); + if constexpr (debug_move) { std::cout << "candidate mv_reoriented is " << start << "," << mv_reoriented << std::endl; } + // The far edge will be the next edge + uint32_t faredge = this->halfedge[nb].next; + const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[nb].vi[1]].p).less_one_dim(); const sm::vec fee = (model_to_scene * this->vertex[this->halfedge[faredge].vi[1]].p).less_one_dim(); if constexpr (debug_move) { std::cout << "Far edge is " << fes << "," << (fee - fes) << std::endl; } - partial_movement pm = find_edge_crossing (fes, fee, _tn, start, _mv_rest); + partial_movement pm = find_edge_crossing (fes, fee, _tn, start, mv_reoriented); if (pm.flags.test (pm_fl::crossed) == true) { - if constexpr (debug_move) { std::cout << "Found cross point with triangle (" << _ti << ")\n"; } + if constexpr (debug_move) { std::cout << "Return 'found'; cross point over faredge (" << faredge << ") of nb " << nb << "\n"; } found = true; } else { + if constexpr (debug_move) { + std::cout << "NO cross point with faredge (" << faredge << ") of " << nb << ", did we land in nb " << nb << "?\n"; + } // No crossing, did we land in the triangle? - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], start + _mv_rest + (_tn / 2.0f), -_tn); + // A couple of times? + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], start + mv_reoriented + (_tn / 2.0f), -_tn); if (is) { // then we DID land in this neighbour tri - if constexpr (debug_move) { std::cout << "Found, as we landed IN triangle (" << _ti << ")\n"; } + if constexpr (debug_move) { std::cout << "Return found; landed IN nb " << nb << "\n"; } found = true; + } else { + if constexpr (debug_move) { std::cout << "NO ray_tri_intersection in nb " << nb << "\n"; } } } - return {found, r_axis, _mv_rest}; + std::cout << __func__ << " returning\n"; + + return {found, r_axis, mv_reoriented}; } /** * Find which triangle we crossed into over the vertex. Update cd with new - * tri_edge. crossing_data required here for + * tri_edge. crossing_data required here for in itial halfedge and also to be populated. */ - uint32_t find_triangle_over_vertex (crossing_data& cd, const sm::vec& mv_inplane, const sm::mat& model_to_scene) + uint32_t find_triangle_over_vertex (crossing_data& cd, + const sm::vec& mv_inplane, + const sm::mat& model_to_scene) { constexpr bool debug_move = true; @@ -1773,11 +1745,11 @@ namespace mplot if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1}) == false) { return newt; } if constexpr (debug_move) { - std::cout << "Finding triangle after crossing halfedge " << cd.halfedge << " near vertex " + std::cout << "Finding triangle after crossing halfedge " << cd.crossed << " near vertex " << (cd.pm.flags.test (pm_fl::near_vertex_0) ? "0" : "1") << "\n"; } - uint32_t cv = cd.pm.flags.test (pm_fl::near_vertex_0) ? cd.halfedge : this->halfedge[cd.halfedge].next; + uint32_t cv = cd.pm.flags.test (pm_fl::near_vertex_0) ? cd.crossed : this->halfedge[cd.crossed].next; if constexpr (debug_move) { std::cout << "Vertex is represented by halfedge " << cv << std::endl; } std::vector nbs = find_neighbours (cv); @@ -1792,13 +1764,21 @@ namespace mplot const sm::vec mv_rest = mv_inplane - cd.pm.mv; const sm::vec end_at_border = cd.pm.end; - for (auto _ti : nbs) { - if (_ti == cv) { continue; } // Don't test crossing into self - auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (_ti, tn_frm, mv_rest, end_at_border, model_to_scene); + for (auto nb : nbs) { + if (nb == cv) { continue; } // Don't test crossing into self + std::cout << __func__ << " calling detect_movement_in_neighbour\n"; + auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (nb, tn_frm, mv_rest, end_at_border, model_to_scene); if (found_in) { - newt = _ti; + if constexpr (debug_move) { std::cout << "Found movement into neighbour " << nb << "\n"; } + newt = nb; + cd.into = nb; + cd.crossed = cv; // Correct to update? cd.tri_edge = r_axis; + cd.mv_rest = _mv_rest; + // Don't update cd.pm.mv/cd.pm.end here, they're already set + cd.pm.flags.set (pm_fl::crossed); + break; } } @@ -1871,46 +1851,47 @@ namespace mplot done = true; } else { - // _ti, _tn are the new triangle - sm::vec _tn = {}; - uint32_t _ti = std::numeric_limits::max(); - if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1})) { if (cd.pm.flags.test (pm_fl::near_vertex_0)) { std::cout << "B: Crossed near vertex 0 of crossed edge\n"; } else if (cd.pm.flags.test (pm_fl::near_vertex_1)) { std::cout << "B: Crossed near vertex 1 of crossed edge\n"; } - // The right triangle to reorient onto may not be the twin across the crossed edge // Check all neighbours of the crossed vertex to find out if our path travels through. - _ti = find_triangle_over_vertex (cd, mv_inplane, model_to_scene); - // FIXME: Need to set cd.tri_edge + find_triangle_over_vertex (cd, mv_inplane, model_to_scene); // This could set cd itself } else { std::cout << "B: Crossed a boundary\n"; - // new triangle is the twin of the crossed edge. - _ti = this->halfedge[cd.halfedge].twin; + + // If compute_crossing_location found boundary, then new triangle is the twin of the crossed edge. + if (cd.into == std::numeric_limits::max()) { + cd.into = this->halfedge[cd.crossed].twin; + } // else: BUT if find_nearest_boundary_crossing found boundary, then new triangle _ti has been set into cd.into + if constexpr (debug_move) { - std::cout << "find triangle across edge: halfedge " << cd.halfedge << " twins to: " << _ti << std::endl; + std::cout << "find triangle across edge: halfedge " << cd.crossed << " twins/crosses to: " << cd.into << std::endl; } } - if (_ti == std::numeric_limits::max()) { + if (cd.into == std::numeric_limits::max()) { // We probably went off the edge of our navigation model mesh this->ti0 = ti0_save; throw std::runtime_error ("off-edge: The movement went off the edge of the model"); } // Re-orient onto the new triangle - sm::vec, 3> newtv_sf = this->triangle_vertices (_ti, model_to_scene); + sm::vec, 3> newtv_sf = this->triangle_vertices (cd.into, model_to_scene); if (newtv_sf[0][0] == std::numeric_limits::max()) { this->ti0 = ti0_save; throw std::runtime_error ("off-edge: The movement went off the edge of the model"); continue; } else { - _tn = this->triangle_normal (newtv_sf); - if constexpr (debug_move) { std::cout << "RE-ORIENT to _ti: " << _ti << " " << newtv_sf << " norm: " << _tn << "\n"; } + + sm::vec _tn = this->triangle_normal (newtv_sf); + if constexpr (debug_move) { + std::cout << "RE-ORIENT to cd.into: " << cd.into << " " << newtv_sf << " norm: " << newtv_sf.mean() << "," << _tn << "\n"; + } // Compute the reorientation due to the requested movement. float rotn_angle = tn0.angle (_tn, cd.tri_edge); // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation @@ -1918,8 +1899,17 @@ namespace mplot sm::mat reorient_model; // reorientation transformation in sf // No good if cd.tri_edge is (0,0,0)! reorient_model.rotate (cd.tri_edge, rotn_angle); - sm::vec mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); - const float rl = mv_rest.length(); + + // cd.mv_rest may have been set, or it may need setting + if (cd.mv_rest[0] == std::numeric_limits::max()) { + if constexpr (debug_move) { std::cout << "cd.mv_rest was unset; setting it from mv_inplane and cd.pm.mv\n"; } + cd.mv_rest = (reorient_model * (mv_inplane - cd.pm.mv)).less_one_dim(); + } else { + if constexpr (debug_move) { std::cout << "cd.mv_rest was set up already\n"; } + } + if constexpr (debug_move) { std::cout << "mv_rest: " << cd.pm.end << "," << cd.mv_rest << std::endl; } + + const float rl = cd.mv_rest.length(); if (std::isnan (rl)) { if constexpr (debug_move) { std::cout << "Got NaN in mv_rest. mv_inplane: " << mv_inplane << ", cd.pm.mv: " << cd.pm.mv << "reorient_model:" << std::endl; @@ -1928,7 +1918,7 @@ namespace mplot } throw std::runtime_error ("NaN in mv_rest"); } - reorient_model.pretranslate (hov_sf + cd.pm.mv + mv_rest); + reorient_model.pretranslate (hov_sf + cd.pm.mv + cd.mv_rest); reorient_model.translate (-hov_sf); // r_t_to + r_t1 = -(hov_sf + cd.pm.mv) + cd.pm.mv = -hov_sf if (rl <= std::numeric_limits::epsilon()) { @@ -1937,17 +1927,17 @@ namespace mplot done = true; } else { // There's additional movement to complete. - if constexpr (debug_move) { std::cout << "mv_rest length is " << rl << std::endl; } + if constexpr (debug_move) { std::cout << "cd.mv_rest length is " << rl << std::endl; } // Loop To Next. Final movement should be exclusively within a triangle. - reorient_model.pretranslate (-mv_rest); + reorient_model.pretranslate (-cd.mv_rest); cam_to_surface = reorient_model * cam_to_surface; hov_sf = cd.pm.end; // crossing data planned movement end // Also update planned move, which is now shorter and in a new direction tv_sf = newtv_sf; - mv_inplane = mv_rest; + mv_inplane = cd.mv_rest; } - this->ti0 = _ti; + this->ti0 = cd.into; tn0 = _tn; } From 20e3b956a5b44c84327cdbe5980aca46f75dc626 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 20 Feb 2026 16:19:43 +0000 Subject: [PATCH 78/82] Seems pretty stable now! --- mplot/NavMesh.h | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 2ad45bef..a57557e1 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1637,10 +1637,7 @@ namespace mplot if (cd.into != std::numeric_limits::max()) { if constexpr (debug_move) { std::cout << "detected a neighbour with detect_movement_in_neighbour()\n"; } // cd should have been set up... - } else { - if constexpr (debug_move) { std::cout << "Do some other kind of test for ray " << hov_sf << "," << mv_inplane << "\n"; } - throw std::runtime_error ("Out of ideas"); // Had a plane-testing approach, but it didn't work - } + } // else cd.into is max, that means we're off-edge. return cd; } @@ -1733,16 +1730,19 @@ namespace mplot /** * Find which triangle we crossed into over the vertex. Update cd with new - * tri_edge. crossing_data required here for in itial halfedge and also to be populated. + * tri_edge. crossing_data required here for initial halfedge and also to be populated. + * The triangle into which we cross is placed into cd.into */ - uint32_t find_triangle_over_vertex (crossing_data& cd, - const sm::vec& mv_inplane, - const sm::mat& model_to_scene) + void find_triangle_over_vertex (crossing_data& cd, + const sm::vec& mv_inplane, + const sm::mat& model_to_scene) { constexpr bool debug_move = true; - uint32_t newt = std::numeric_limits::max(); - if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1}) == false) { return newt; } + if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1}) == false) { + if constexpr (debug_move) { std::cout << "Crossing cd does not go near a vertex, returning\n"; } + return; + } if constexpr (debug_move) { std::cout << "Finding triangle after crossing halfedge " << cd.crossed << " near vertex " @@ -1771,7 +1771,6 @@ namespace mplot if (found_in) { if constexpr (debug_move) { std::cout << "Found movement into neighbour " << nb << "\n"; } - newt = nb; cd.into = nb; cd.crossed = cv; // Correct to update? cd.tri_edge = r_axis; @@ -1781,8 +1780,6 @@ namespace mplot break; } } - - return newt; } /** @@ -1792,7 +1789,6 @@ namespace mplot * triangle (ti0) OR we move to the boundary that we cross, and adjust mv_inplane. */ void traverse_triangles (sm::vec& mv_inplane, - const sm::vec& mv_sf, sm::vec, 3>& tv_sf, sm::vec& tn0, sm::vec& hov_sf, @@ -1836,6 +1832,10 @@ namespace mplot std::cout << "About to use find_nearest_boundary_crossing function...\n"; cd = find_nearest_boundary_crossing (hov_sf, mv_inplane, model_to_scene); std::cout << "find_nearest_boundary_crossing returns cd which has cd.pm.flags: " << cd.pm.flags << std::endl; + if (cd.into == std::numeric_limits::max()) { + this->ti0 = ti0_save; + throw std::runtime_error ("off-edge: The movement went off the edge of the model over a vertex"); + } } } // else We HAVE a crossing of some sort. @@ -1999,7 +1999,7 @@ namespace mplot } // Now traverse those triangles! The output of this function is cam_to_surface - traverse_triangles (mv_inplane, mv_sf, tv_sf, tn0, hov_sf, cam_to_surface, model_to_scene); + traverse_triangles (mv_inplane, tv_sf, tn0, hov_sf, cam_to_surface, model_to_scene); // Raise cam_to_surface up by hoverheight and then return cam_to_surface.pretranslate (hoverheight * tn0); From 367211a905bb61b7d8bc850bb579e3f5feb2f612 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 20 Feb 2026 16:29:10 +0000 Subject: [PATCH 79/82] Turning debug messages off --- mplot/NavMesh.h | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index a57557e1..02174278 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -594,7 +594,7 @@ namespace mplot */ void compute_neighbour_relations() { - constexpr bool debug_nr = true; + constexpr bool debug_nr = false; uint32_t sz = this->halfedge.size(); if constexpr (debug_nr) { std::cout << "Finding twins for " << sz << " halfedge\n"; } @@ -1072,7 +1072,7 @@ namespace mplot const sm::vec& mv_s, const sm::vec& mv_inplane) { - constexpr bool debug = true; + constexpr bool debug = false; crossing_data cd; cd.pm.flags.set (pm_fl::crossed, false); @@ -1338,7 +1338,7 @@ namespace mplot const sm::mat& model_to_scene, const float hoverheight) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; // Camera location, scene frame const sm::vec camloc_sf = cam_to_scene.translation(); @@ -1571,7 +1571,7 @@ namespace mplot const sm::vec& mv_inplane, const sm::mat& model_to_scene) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; crossing_data cd; // Get the 'from' triangle normal @@ -1668,7 +1668,7 @@ namespace mplot const sm::vec& start, const sm::mat& model_to_scene) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; bool found = false; @@ -1737,7 +1737,7 @@ namespace mplot const sm::vec& mv_inplane, const sm::mat& model_to_scene) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1}) == false) { if constexpr (debug_move) { std::cout << "Crossing cd does not go near a vertex, returning\n"; } @@ -1795,7 +1795,7 @@ namespace mplot sm::mat& cam_to_surface, const sm::mat& model_to_scene) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; // In case we throw off-edge, we need to restore ti0's state const uint32_t ti0_save = this->ti0; bool done = false; @@ -1829,9 +1829,7 @@ namespace mplot if constexpr (debug_move) { std::cout << "End lands in ? " << (isect ? "Y" : "N") << std::endl; } if (isect == false) { // Didn't find edge crossing or that the end point is within ti0, so now search neighbours for an end point or boundary crossing. - std::cout << "About to use find_nearest_boundary_crossing function...\n"; cd = find_nearest_boundary_crossing (hov_sf, mv_inplane, model_to_scene); - std::cout << "find_nearest_boundary_crossing returns cd which has cd.pm.flags: " << cd.pm.flags << std::endl; if (cd.into == std::numeric_limits::max()) { this->ti0 = ti0_save; throw std::runtime_error ("off-edge: The movement went off the edge of the model over a vertex"); @@ -1841,36 +1839,36 @@ namespace mplot // crossing_data gives us info about if there is NO cross point in the partial mv crossed flag if (cd.pm.flags.test (pm_fl::colinear) == true && cd.pm.flags.test (pm_fl::crossed) == false) { - std::cout << "A: Movement stays inside triangle ti0 (colinear within boundary\n"; + if constexpr (debug_move) { std::cout << "A: Movement stays inside triangle ti0 (colinear within boundary\n"; } cam_to_surface.pretranslate (mv_inplane); done = true; } else if (cd.pm.flags.test (pm_fl::crossed) == false) { // move a bit, shorten mv_inplane // mv_inplane moved camera inside triangle. - std::cout << "A: Movement stays inside triangle ti0\n"; + if constexpr (debug_move) { std::cout << "A: Movement stays inside triangle ti0\n"; } cam_to_surface.pretranslate (mv_inplane); done = true; } else { if (cd.pm.flags.any_of ({pm_fl::near_vertex_0, pm_fl::near_vertex_1})) { - if (cd.pm.flags.test (pm_fl::near_vertex_0)) { - std::cout << "B: Crossed near vertex 0 of crossed edge\n"; - } else if (cd.pm.flags.test (pm_fl::near_vertex_1)) { - std::cout << "B: Crossed near vertex 1 of crossed edge\n"; + if constexpr (debug_move) { + if (cd.pm.flags.test (pm_fl::near_vertex_0)) { + std::cout << "B: Crossed near vertex 0 of crossed edge\n"; + } else if (cd.pm.flags.test (pm_fl::near_vertex_1)) { + std::cout << "B: Crossed near vertex 1 of crossed edge\n"; + } } // The right triangle to reorient onto may not be the twin across the crossed edge // Check all neighbours of the crossed vertex to find out if our path travels through. find_triangle_over_vertex (cd, mv_inplane, model_to_scene); // This could set cd itself } else { - std::cout << "B: Crossed a boundary\n"; - // If compute_crossing_location found boundary, then new triangle is the twin of the crossed edge. if (cd.into == std::numeric_limits::max()) { cd.into = this->halfedge[cd.crossed].twin; } // else: BUT if find_nearest_boundary_crossing found boundary, then new triangle _ti has been set into cd.into if constexpr (debug_move) { - std::cout << "find triangle across edge: halfedge " << cd.crossed << " twins/crosses to: " << cd.into << std::endl; + std::cout << "B: Crossed a boundary halfedge " << cd.crossed << " which twins/crosses to: " << cd.into << std::endl; } } @@ -1964,7 +1962,7 @@ namespace mplot const sm::mat& model_to_scene, const float hoverheight) { - constexpr bool debug_move = true; + constexpr bool debug_move = false; // Convert indices to vertices for triangle ti0, converting to the scene frame sm::vec, 3> tv_sf = this->triangle_vertices (this->ti0, model_to_scene); From cc7d7bc93b5e1462de30355bf632617a52dca3bf Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 20 Feb 2026 16:42:03 +0000 Subject: [PATCH 80/82] More debug message hiding --- mplot/NavMesh.h | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 02174278..cac54aee 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -1594,9 +1594,12 @@ namespace mplot const sm::vec to_vtx = vtx_loc - hov_sf; // to_vtx.cross (mv_inplane) should be very short auto cp = to_vtx.cross (mv_inplane); - std::cout << "i = " << i << ", cp.length(): " << cp.length() - << " and to_vtx.dot (mv_inplane) / to_vtx.length() = " - << (to_vtx.dot (mv_inplane) / to_vtx.length()) << std::endl; + + if constexpr (debug_move) { + std::cout << "i = " << i << ", cp.length(): " << cp.length() + << " and to_vtx.dot (mv_inplane) / to_vtx.length() = " + << (to_vtx.dot (mv_inplane) / to_vtx.length()) << std::endl; + } // Check to see if vtx_loc is in the direction mv_inplane... if (to_vtx.dot (mv_inplane) < 0.0f) { @@ -1614,7 +1617,6 @@ namespace mplot std::set ind = this->triangle_indices (nb); if (tested.count (ind) > 0) { continue; } // Don't test already-tested tested.insert (ind); - std::cout << __func__ << " calling detect_movement_in_neighbour\n"; auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (nb, tn_frm, mv_rest, vtx_loc, model_to_scene); if (found_in) { cd.crossed = _ti; // the vtx halfedge @@ -1678,14 +1680,14 @@ namespace mplot // Have to reorient to each neighbour to test auto _tn = this->triangle_normal (tv_nb); if constexpr (debug_move) { - std::cout << __func__ << " called\n"; + std::cout << __func__ << " called:\n"; std::cout << "Test candidate movement in nb: " << nb << " " << tv_nb; std::cout << "\n norm: " << tv_nb.mean() << "," << (_tn * 0.05f) << "\n"; std::cout << "start/mv: " << start << "," << mv << "\n"; } sm::vec r_axis = tn_frm.cross (_tn); - std::cout << "Rotation axis: " << start << "," << r_axis << std::endl; + if constexpr (debug_move) { std::cout << "Rotation axis: " << start << "," << r_axis << std::endl; } r_axis.renormalize(); // Compute the reorientation due to the requested movement into this neighbour float rotn_angle = tn_frm.angle (_tn, r_axis); @@ -1723,8 +1725,6 @@ namespace mplot } } - std::cout << __func__ << " returning\n"; - return {found, r_axis, mv_reoriented}; } @@ -1766,7 +1766,6 @@ namespace mplot const sm::vec end_at_border = cd.pm.end; for (auto nb : nbs) { if (nb == cv) { continue; } // Don't test crossing into self - std::cout << __func__ << " calling detect_movement_in_neighbour\n"; auto[found_in, r_axis, _mv_rest] = detect_movement_in_neighbour (nb, tn_frm, mv_rest, end_at_border, model_to_scene); if (found_in) { From 9b30c5327a38e68a879df9437cc74e72a5a4f924 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 23 Feb 2026 12:12:02 +0000 Subject: [PATCH 81/82] Improved first-hit code --- maths | 2 +- mplot/NavMesh.h | 63 ++++++++++++++++--------------------------------- 2 files changed, 21 insertions(+), 44 deletions(-) diff --git a/maths b/maths index 62c82eea..ecc1c258 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 62c82eeac6d56892fb143e49d645bd8ac49a4437 +Subproject commit ecc1c2581272c51b102c9e3c6c668d3a82b671fa diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index cac54aee..9f19a1d2 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -743,35 +743,29 @@ namespace mplot // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml != std::numeric_limits::max()) { - test_tri (tested, ti_ml, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); - if (isect_d != std::numeric_limits::max()) { // we found it in the first triangle! - //std::cout << "Got a first-tri hit!\n"; + if constexpr (debug_ftc) { std::cout << "Got a first-tri hit!\n"; } return { isect_p, isect_ti }; } // Next, test the neighbours of ti_ml std::vector nbs = this->find_neighbours (ti_ml); - //std::cout << "Testing " << nbs.size() << " neighbours of ti_ml for a hit\n"; + if constexpr (debug_ftc) { std::cout << "Testing " << nbs.size() << " neighbours of ti_ml for a hit\n"; } for (uint32_t nb : nbs) { - test_tri (tested, nb, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); - if (isect_d != std::numeric_limits::max()) { - //std::cout << "Got a neighbour hit!\n"; + if constexpr (debug_ftc) { std::cout << "Got a neighbour hit!\n"; } return { isect_p, isect_ti }; } } - // Do neighbours of neighbours? + // Do neighbours of neighbours... for (uint32_t nb : nbs) { std::vector nbs2 = this->find_neighbours (nb); - for (uint32_t nb2 : nbs2) { test_tri (tested, nb2, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); - if (isect_d != std::numeric_limits::max()) { std::cout << "Got a neighbour-neighbour hit!\n"; return { isect_p, isect_ti }; @@ -781,47 +775,30 @@ namespace mplot } // Fall back to testing ALL the triangles... - //std::cout << "Oh, no have to test ALL triangles now...\n"; + if constexpr (debug_ftc) { std::cout << "Oh, no have to test ALL triangles now...\n"; } for (auto tri : this->triangles) { - - if constexpr (debug_ftc) { - std::cout << "this->halfedge["<< 0 << "] " << (&this->halfedge[0]) << " contains: vi:" - << this->halfedge[0].vi - << ", twin:" << this->halfedge[0].twin - << ", next:" << this->halfedge[0].next - << ", prev:" << this->halfedge[0].prev << std::endl; - std::cout << "CF. Passing tri.he " << tri.hi << " to triangle_vertices(): with he->vi = " << this->halfedge[tri.hi].vi << std::endl; - } - sm::vec, 3> v = this->triangle_vertices (tri.hi); - auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); - // What if the triangle is one on the *other side of the model*?? Have to use - // vdir.sos() to exclude those that are too far and the distance^2 to find the - // closest one that isn't. + auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { - float d = (p - vstart).sos(); - if (d < isect_d && d < vdsos) { - isect_p = p; - isect_ti = tri.hi; - isect_d = d; - } + isect_p = p; + isect_ti = tri.hi; + break; } } + if (isect_p[0] == fmax) { // Found no triangle intersection; check vertices, in case vdir points perfectly at a vertex. - std::cout << "Finally, check vertices...\n"; - for (uint32_t vi = 0; vi < this->vertex.size(); ++vi) { - sm::vec vertex_n = this->find_vertex_normal (this->vertex[vi].hi, model_to_scene); - vertex_n.renormalize(); - vstart = coord_mf + (vertex_n / 2.0f); - if (sm::geometry::ray_point_intersection (this->vertex[vi].p, vstart, -vertex_n)) { - float d = (this->vertex[vi].p - vstart).sos(); - if (d < isect_d && d < vdir.sos()) { - if constexpr (debug_ftc) { std::cout << "Register vertex triangle_crossing\n"; } - isect_p = this->vertex[vi].p; - isect_ti = this->vertex[vi].hi; - isect_d = d; + if constexpr (debug_ftc) { std::cout << "Finally, check vertices...\n"; } + + constexpr float dist_thresh = 2.0f * std::numeric_limits::epsilon(); + for (auto tri : this->triangles) { + sm::vec, 3> v = this->triangle_vertices (tri.hi); + for (uint32_t vi = 0; vi < 3; ++vi) { + if (sm::geometry::ray_point_intersection ((model_to_scene * v[vi]).less_one_dim(), vstart, vdir, dist_thresh)) { + isect_p = v[vi]; + isect_ti = tri.hi; + break; } } } From d8ea26b14a35b52f8b72b86afdc649f2fa369825 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 23 Feb 2026 15:32:13 +0000 Subject: [PATCH 82/82] Mostly code comments. One function changed a bit (the need for distance testing is no longer relevant with the halfedge mesh) --- mplot/NavMesh.h | 561 +++++++++++++++++++++++------------------------- 1 file changed, 271 insertions(+), 290 deletions(-) diff --git a/mplot/NavMesh.h b/mplot/NavMesh.h index 9f19a1d2..5a103745 100644 --- a/mplot/NavMesh.h +++ b/mplot/NavMesh.h @@ -31,7 +31,7 @@ namespace mplot { namespace mesh { - /* + /*! * Half-edge data structures for ordered meshes */ template requires std::is_integral_v @@ -75,14 +75,10 @@ namespace mplot */ std::vector> vertex = {}; - /*! - * The vector of half edges in the mesh - */ + //! The vector of half edges in the mesh std::vector> halfedge = {}; - /*! - * Triangle mesh faces. populated by VisualModel::make_navmesh() - */ + //! Triangle mesh faces. populated by VisualModel::make_navmesh() std::vector> triangles = {}; //! Holds a copy of the bb of the parent model @@ -91,6 +87,9 @@ namespace mplot //! When navigating, this is the 'current triangle' that you're located over/near uint32_t ti0 = std::numeric_limits::max(); + /*! + * Save this navmesh into a binary file. + */ void save (const std::string& filename) const { std::cout << "Save NavMesh to " << filename << std::endl; @@ -131,6 +130,9 @@ namespace mplot for (auto t : this->triangles) { sm::util::binary_write (fout, t.hi); } } + /*! + * Load the navmesh from the binary format produced by NavMesh::save() + */ void load (const std::string& filename) { std::cout << "Load NavMesh from " << filename << std::endl; @@ -193,6 +195,9 @@ namespace mplot return i; } + /*! + * Performs the work required to verify a triangle or a boundary made of halfedges. + */ bool verify_halfedge_chain (const uint32_t _hi, const uint32_t chain_length = std::numeric_limits::max(), const bool debug = false) const @@ -223,32 +228,24 @@ namespace mplot bool approx_zlen_halfedge_to_halfedge = false; // Each boundary should be the same forwards and backwards from every possible start halfedge do { - //std::cout << "this->halfedge["<halfedge[fhi].flags << " go to next..." << std::endl; + // No self-referrals please if (this->halfedge[fhi].next == fhi) { throw std::runtime_error ("self next referral!"); } - // Also make sure that the vertices are not the same (no triangles-that-are-lines) + // Make sure that the vertices are not the same (no triangles-that-are-lines) const uint32_t fhi_nx = this->halfedge[fhi].next; + // Check the length of the edge auto hlen = (this->vertex[halfedge[fhi].vi[1]].p - this->vertex[halfedge[fhi_nx].vi[1]].p).length(); - if (halfedge[fhi].vi[0] == halfedge[fhi].vi[1]) { zlen_halfedge = true; } - if (halfedge[fhi].vi[1] == halfedge[fhi_nx].vi[1]) { zlen_halfedge_to_halfedge = true; } - if (hlen < (10.0f * std::numeric_limits::epsilon())) { approx_zlen_halfedge_to_halfedge = true; - //std::cout << "Curr fhi = " << fhi << ": " << this->vertex[halfedge[fhi].vi[0]].p << "," - // << (this->vertex[halfedge[fhi].vi[1]].p - this->vertex[halfedge[fhi].vi[0]].p) - // << ", Next fhi = " << fhi_nx << ": " << this->vertex[halfedge[fhi_nx].vi[0]].p << "," - // << (this->vertex[halfedge[fhi_nx].vi[1]].p - this->vertex[halfedge[fhi_nx].vi[0]].p) - // << std::endl; } - + // Move to the next one fhi = this->halfedge[fhi].next; ++fcount; } while (fhi != _hi && fhi != max && fcount < this->halfedge.size()); if (debug) { std::cout << "From forwards loop: fcount = " << fcount << " and fhi = " << fhi << " cf _hi = " << _hi << std::endl; - if (zlen_halfedge == true) { std::cout << "test fails because we have zlen_halfedge\n"; } @@ -264,10 +261,10 @@ namespace mplot return false; } + // Check continuity in the reverse direction now uint32_t rcount = 0u; uint32_t rhi = _hi; do { - //std::cout << "this->halfedge["<halfedge[rhi].flags << " go to prev..." << std::endl; if (this->halfedge[rhi].prev == rhi) { throw std::runtime_error ("self prev referral!"); } rhi = this->halfedge[rhi].prev; ++rcount; @@ -291,12 +288,24 @@ namespace mplot return false; } } + /*! + * Verify a boundary that is not expected to be a triangle (its loop is likely to contain >3 + * halfedges) + */ bool verify_boundary (const uint32_t boundary_hi, const bool debug = false) const - { return verify_halfedge_chain (boundary_hi, std::numeric_limits::max(), debug); } + { + return this->verify_halfedge_chain (boundary_hi, std::numeric_limits::max(), debug); + } + + /*! + * Verify a halfedge loop that is expected to be a triangle with 3 halfedges + */ bool verify_triangle (const uint32_t tri_hi, const bool debug = false) const - { return verify_halfedge_chain (tri_hi, 3, debug); } + { + return this->verify_halfedge_chain (tri_hi, 3, debug); + } - /** + /*! * Return the three indices in the triangle containing halfedge hi * * A single max value in the set indicates an error @@ -316,7 +325,10 @@ namespace mplot return indices; } - // Return the three vertices for the triangle specified as three indices into NavMesh::vertex + /*! + * Return the three vertex coordinates, in the NavMesh model frame for the triangle of which + * the halfedge index tri_hi is a part. + */ sm::vec, 3> triangle_vertices (uint32_t tri_hi) const { sm::vec, 3> trivert = {}; @@ -341,7 +353,10 @@ namespace mplot return trivert; } - // Return the three vertices for the triangle specified as three indices into NavMesh::vertex transformed by transform + /*! + * Return the three vertex coordinates, transformed from the NavMesh model frame by + * 'transform' for the triangle of which the halfedge index tri_hi is a part. + */ sm::vec, 3> triangle_vertices (const uint32_t tri_hi, const sm::mat& transform) const { sm::vec, 3> trivert = {}; @@ -366,7 +381,9 @@ namespace mplot return trivert; } - // Compute the triangle normal for the ordered triplet of triangle vertices, tverts + /*! + * Compute the triangle normal for the ordered triplet of triangle vertices, tverts + */ sm::vec triangle_normal (const sm::vec, 3>& tverts) const { sm::vec n = (tverts[1] - tverts[0]).cross (tverts[2] - tverts[0]); @@ -374,7 +391,9 @@ namespace mplot return n; } - // Retrieve the halfedge as a vector, transformed by the given transform + /*! + * Retrieve the halfedge as a vector, transformed by the given transform + */ sm::vec edge_vector (uint32_t hi, const sm::mat& transform) const { const sm::vec v0 = (transform * this->vertex[this->halfedge[hi].vi[0]].p).less_one_dim(); @@ -382,16 +401,24 @@ namespace mplot return v1 - v0; } + /*! + * Retrieve the coordinate of the start of the halfedge, transformed by the given transform + */ sm::vec edge_start (uint32_t hi, const sm::mat& transform) const { return (transform * this->vertex[this->halfedge[hi].vi[0]].p).less_one_dim(); } - // Find all the neighbours of triangle *vertex* index a, throwing exceptions on errors. - // Returns the same vector of halfedge indices as find_neighbours, but without assuming that the navmesh is good. - // \return vector of halfedge indices - std::vector - test_neighbours (const uint32_t a) const + /*! + * Find all the neighbours of the triangle *vertex* found at the start (position 0) of the + * halfedge index a, throwing exceptions on errors. + * + * Returns the same vector of halfedge indices as find_neighbours, but without assuming that + * the navmesh is good. + * + * \return vector of halfedge indices + */ + std::vector test_neighbours (const uint32_t a) const { uint32_t hi = a; std::vector rtn = {}; @@ -426,9 +453,13 @@ namespace mplot return rtn; } - // Find all the neighbours of triangle *vertex* index a. - // Assumes the navmesh is good, and has passed NavMesh::test() - // \return vector of halfedge indices, including all neighbour triangles AND self (a) + /*! + * Find all the neighbours of triangle *vertex* index a. + * + * Assumes the navmesh is good, and has passed NavMesh::test() + * + * \return vector of halfedge indices, including all neighbour triangles AND self (a) + */ std::vector find_neighbours (const uint32_t a) const { uint32_t hi = a; @@ -444,14 +475,15 @@ namespace mplot return rtn; } - // Test the navmesh, to make sure it is perfect + /*! + * Test the navmesh, to make sure it is perfect + */ void test (const bool just_mark_bad = false) { std::cout << "NavMesh verification test running...\n"; // 1) Verify that each halfedge is part of a face or hole (boundary) and is not a line? for (uint32_t hi = 0; hi < this->halfedge.size(); ++hi) { - if ((this->halfedge[hi].flags & 0x1) == 0x1) { - //std::cout << "This halfedge was marked as boundary\n"; + if ((this->halfedge[hi].flags & 0x1) == 0x1) { // 0x1 flag means 'on boundary' if (this->verify_boundary (hi) == false) { if (just_mark_bad == false) { throw std::runtime_error ("Imperfect halfedge mesh (boundary hole)"); @@ -460,20 +492,17 @@ namespace mplot } else { if (this->verify_triangle (hi) == false) { if (just_mark_bad == true) { - this->halfedge[hi].flags |= 0x2; // Do want to mark whole triangle + this->halfedge[hi].flags |= 0x2; // 0x2 means 'rogue halfedge' } else { throw std::runtime_error ("Imperfect halfedge mesh (face triangle)"); } } std::vector nb = this->test_neighbours (hi); - if (nb.size() > 100) { - for (auto n : nb) { - std::cout << n << std::endl; - } - throw std::runtime_error ("too many neighbours?"); - } + // Don't expect a very large number of neighbours + if (nb.size() > 100) { throw std::runtime_error ("too many neighbours?"); } } + // Make sure twin is set for every halfedge if (this->halfedge[hi].twin == std::numeric_limits::max()) { throw std::runtime_error ("Contains an untwinned halfedge"); @@ -481,10 +510,12 @@ namespace mplot } } - /* - * After making the neighbour relations from the OpenGL mesh, the last step is to fill in - * the boundary halfedges. Find all halfedges with an unset twin and then start creating the - * new half edges to fill in. + /*! + * During the construction of the halfedge mesh, after making the neighbour relations from + * the OpenGL mesh, the last step is to fill in the *boundary* halfedges. + * + * Find all halfedges with an unset twin and then start creating the new half edges to fill + * in. */ void add_boundary_halfedges() { @@ -571,23 +602,21 @@ namespace mplot } // Lastly - check through for rogues! - std::cout << "Post-search for rogues\n"; + if constexpr (debug_bnd) { std::cout << "Post-search for rogues\n"; } for (uint32_t i = 0; i < sz; ++i) { if (this->halfedge[i].twin == max) { bool rogue_is_tri = this->verify_triangle (i, true); std::cout << "Identified a rogue, which " << (rogue_is_tri ? "IS" : "ISN'T") << " a triangle.\n"; - // How to remove? + // How to remove? Answer: Preprocess the model in Blender (or similar) } } - - if constexpr (debug_bnd) { std::cout << __func__ << " returning\n"; } } - /* + /*! * Determine neighbour relations. That means populating a halfedge data structure. Don't - * think there's any way around the at-worst O(N^2) computation, so save results into an h5 - * file that can be loaded at startup. + * think there's any way around the at-worst O(N^2) computation, so save results into a + * binary file that can be re-loaded at each startup. * * The key is the half-edge data structure. * See: https://jerryyin.info/geometry-processing-algorithms/half-edge/ @@ -601,9 +630,7 @@ namespace mplot // Search a 'band' either side of i first, assuming that neighbour faces are likely // to have been nearby in the indices array const uint32_t band = 3 * 1000; - uint32_t wider_searches = 0; // Count how many times we make a wider search - uint64_t twin_meandist = 0; // See how far a search has to search for a twin uint32_t twins = 0; @@ -691,29 +718,27 @@ namespace mplot } } - // A subroutine for find_triangle_crossing - void test_tri (std::set& tested, const uint32_t ontest, - const sm::vec& vstart, const sm::vec& vdir, const float vdsos, - sm::vec& isect_p, uint32_t& isect_ti, float & isect_d) const + /*! + * A subroutine for find_triangle_crossing + */ + bool test_tri (std::set& tested, const uint32_t ontest, + const sm::vec& vstart, const sm::vec& vdir, + sm::vec& isect_p, uint32_t& isect_ti) const { - if (tested.count (ontest)) { return; } + if (tested.count (ontest)) { return false; } tested.insert (ontest); sm::vec, 3> v = this->triangle_vertices (ontest); - auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); + auto [isect, p] = sm::geometry::ray_tri_intersection (v[0], v[1], v[2], vstart, vdir); if (isect) { - float d = (p - vstart).sos(); - if (d < vdsos) { - isect_p = p; - isect_ti = ontest; - isect_d = d; - } + isect_p = p; + isect_ti = ontest; } + return isect; } - /* + /*! * Find the location, and the triangle indices at which a ray starting from coord with - * direction vdir - the 'penetration point' intersects with this NavMesh model. The length - * of vdir is used to avoid finding the intersection at the 'back' of the model. + * direction vdir - the 'penetration point' intersects with this NavMesh model. * * \param model_to_scene Transform that is only passed to find_vertex_normal. May in future be unnecessary. * @@ -735,16 +760,11 @@ namespace mplot sm::vec isect_p = { fmax, fmax, fmax }; uint32_t isect_ti = std::numeric_limits::max(); - float isect_d = std::numeric_limits::max(); // distance to intersect - - const float vdsos = vdir.sos(); - std::set tested; // Have we been passed a 'most likely triangle' to test first? If so, test it. if (ti_ml != std::numeric_limits::max()) { - test_tri (tested, ti_ml, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); - if (isect_d != std::numeric_limits::max()) { + if (test_tri (tested, ti_ml, vstart, vdir, isect_p, isect_ti)) { // we found it in the first triangle! if constexpr (debug_ftc) { std::cout << "Got a first-tri hit!\n"; } return { isect_p, isect_ti }; @@ -754,8 +774,7 @@ namespace mplot std::vector nbs = this->find_neighbours (ti_ml); if constexpr (debug_ftc) { std::cout << "Testing " << nbs.size() << " neighbours of ti_ml for a hit\n"; } for (uint32_t nb : nbs) { - test_tri (tested, nb, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); - if (isect_d != std::numeric_limits::max()) { + if (test_tri (tested, nb, vstart, vdir, isect_p, isect_ti)) { if constexpr (debug_ftc) { std::cout << "Got a neighbour hit!\n"; } return { isect_p, isect_ti }; } @@ -765,8 +784,7 @@ namespace mplot for (uint32_t nb : nbs) { std::vector nbs2 = this->find_neighbours (nb); for (uint32_t nb2 : nbs2) { - test_tri (tested, nb2, vstart, vdir, vdsos, isect_p, isect_ti, isect_d); - if (isect_d != std::numeric_limits::max()) { + if (test_tri (tested, nb2, vstart, vdir, isect_p, isect_ti)) { std::cout << "Got a neighbour-neighbour hit!\n"; return { isect_p, isect_ti }; } @@ -807,8 +825,10 @@ namespace mplot return { isect_p, isect_ti }; } - // Find the location, and the triangle indices (by means of a halfedge index) at which a ray - // between coord (in model frame) and the model centroid cross - the 'penetration point'. + /*! + * Find the location, and the triangle indices (by means of a halfedge index) at which a ray + * between coord (in model frame) and the model centroid cross - the 'penetration point'. + */ std::tuple, uint32_t> find_triangle_crossing (const sm::vec& coord_mf, const sm::mat& model_to_scene) const { @@ -817,7 +837,9 @@ namespace mplot return this->find_triangle_crossing (coord_mf, vdir, model_to_scene); } - // Find the normal of the vertex specified by halfedge vhe + /*! + * Find the normal of the vertex specified by halfedge ti + */ sm::vec find_vertex_normal (const uint32_t ti, const sm::mat& transform) const { auto neighbs = this->find_neighbours (ti); @@ -830,7 +852,9 @@ namespace mplot return (vn / neighbs.size()); } - // Flags class + /*! + * Flags class for partial_movement + */ enum class pm_fl : uint32_t { crossed, // Means the partial movement crossed an edge @@ -838,7 +862,8 @@ namespace mplot near_vertex_0, // The partial movement crossed very close to vertex 0 of the crossed edge near_vertex_1 // The partial movement crossed very close to vertex 0 of the crossed edge }; - /* + + /*! * The partial movement that takes us to the crossing point, specified as movement + endpoint * (rather than startpoint + movement) */ @@ -859,7 +884,7 @@ namespace mplot sm::flags flags = default_flags(); }; - /* + /*! * Find the part of mv_inplane that gets us to the triangle boundary defined by edge_s and * edge_e * @@ -1005,7 +1030,7 @@ namespace mplot return pm; } - /* + /*! * After testing up to all three edges of a triangle, we return information about the crossing * location and the indices of the triangle that form the crossed edge in this structure. */ @@ -1023,7 +1048,7 @@ namespace mplot partial_movement pm = {}; }; - /* + /*! * Find the location at which a movement from mv_s in the direction mv_inplane crosses one of * the edges of the triangle specified by the three vertices in t_verts/t_indices. * @@ -1142,21 +1167,13 @@ namespace mplot * \param camloc_mf The camera location in the model frame. This gives us the start location * for the ray. * - * \param vdir The direction of the ray. - * - * \param search_dist_mult a multiplier on the search distance. The length of vdir in this - * function should cross the landscape model. By default it's the vector from the camera - * location in the model frame of reference to the middle of the bounding box. If the vector - * is too long when finding the surface of a convex hull, such as a model of a rock, it is - * possible to mis-identify the back side of the model. However, for finding a location on a - * large, flat, one-sided landscape, we want to make vdir long. search_dist_mult is applied - * to vdir. + * \param vdir The direction of the ray (its length is also significant). * * \param ti_ml The most likely triangle, if you know what it probably is, to reduce the * search time. * - * \return tuple containing: the hit point in scene coordinates; --the triangle normal of the - * triangle we hit;-- and the indices of the triangle we hit. + * \return tuple containing: the hit point in scene coordinates; the index of the triangle + * we hit (also set into this->ti0). */ std::tuple, uint32_t> find_triangle_hit (const sm::mat& model_to_scene, @@ -1209,7 +1226,8 @@ namespace mplot * large, flat, one-sided landscape, we want to make vdir long. search_dist_mult is applied * to vdir. * - * \return tuple containing: the hit point in scene coordinates and the mesh::face we hit. + * \return tuple containing: the hit point in scene coordinates and halfedge referring to + * the triangle we hit. */ std::tuple, uint32_t> find_triangle_hit (const sm::mat& camspace, const sm::mat& model_to_scene, @@ -1225,8 +1243,11 @@ namespace mplot } /*! - * Position the camera hoverheight above the location hp_scene, with its forward direction - * _z and its 'x' axis in direction _x. + * Position the camera a distance hoverheight above the location hp_scene, with its forward + * direction _z and its 'x' axis in direction _x. _x, _y and _z are used to form a basis + * that creates a coordinate transform matrix. + * + * \return the transformation matrix that positions the camera */ sm::mat position_camera (const sm::vec& hp_scene, const sm::mat& model_to_scene, const sm::vec& _x, const sm::vec& _y, const sm::vec& _z, @@ -1303,14 +1324,28 @@ namespace mplot return this->position_camera (hp_scene, model_to_scene, _x, tn, _z, hoverheight); } - /** - * Return true: intersection found; false: no intersection found + /*! + * Return true: intersection found; false: no intersection found. Also returns outputs in + * the args hov_sf and cam_to_surface. + * + * \param tv_sf triangle vertices as coordinates. Input, may be modified? + * + * \param tn0 Triangle normal of tv_sf. In/out? + * + * \param hov_sf An output. Hit location on triangle + * + * \param cam_to_surface An output. pose matrix for hov_sf (is hov_sf in the end just cam_to_surface.translation()?) + * + * \param cam_to_scene Transforms camera's ego-frame to scene coordinate frame + * + * \param model_to_scene Transforms landscape/model space to scene coordinate frame + * + * \param hoverheight Camera height-above-model-surface */ - bool find_first_intersection (sm::vec, 3>& tv_sf, // triangle vertices as coordinates. Input, may be modified? - sm::vec& tn0, // Triangle normal of tv_sf. In/out? - sm::vec& hov_sf, // Hit location on triangle, output - sm::mat& cam_to_surface, // output. pose matrix for hov_sf (is hov_sf in the end just cam_to_surface.translation()?) - [[maybe_unused]] const sm::vec& mv_camframe, // FIXME: Don't think we should need mv_camframe to find first intersection. + bool find_first_intersection (sm::vec, 3>& tv_sf, + sm::vec& tn0, + sm::vec& hov_sf, + sm::mat& cam_to_surface, const sm::mat& cam_to_scene, const sm::mat& model_to_scene, const float hoverheight) @@ -1320,15 +1355,13 @@ namespace mplot // Camera location, scene frame const sm::vec camloc_sf = cam_to_scene.translation(); - // Does camloc_sf in dirn tn0 intersect the tv_sf triangle? This - // returns true if camloc_sf is on the edge of the triangle or on a - // vertex. Assumes we're above the model and within the length of tn0 of the - // surface. + // Does camloc_sf in dirn tn0 intersect the tv_sf triangle? This returns true if + // camloc_sf is on the edge of the triangle or on a vertex. Assumes we're above the + // model and within the length of tn0 of the surface. // // IF we're on an edge, then this intersection algo may disagree with // compute_crossing_location, which currently looks for crossing each of the three - // boundaries and so requires that the start point is *within* the boundary. - // + // boundaries and so expects that the start point is *within* the boundary. if constexpr (debug_move) { std::cout << "First ray_tri_intersection (raystart,-tn0): " << (camloc_sf + (tn0 / 2.0f)) << "," << -tn0 << std::endl; } @@ -1352,7 +1385,6 @@ namespace mplot } // If that didn't work, try the triangle *vertices* - //uint32_t int_vertex_hi = std::numeric_limits::max(); // intersection vertex if (isect == false) { if constexpr (debug_move) { std::cout << "Try the triangle vertices...\n"; } uint32_t hi = this->ti0; @@ -1361,16 +1393,12 @@ namespace mplot // We need to use the *vertex* normal for this test - the average of all the adjacent triangle normals! sm::vec vertex_n = this->find_vertex_normal (hi, model_to_scene); vertex_n.renormalize(); - // How to figure out tv_sf[i]? - // sm::vec v_sf = (model_to_scane * this->vertex[halfedge[hi].vi[0]]).less_one_dim(); - // or, if tv_sf[0] is the start of ti0, then we can do this: if (sm::geometry::ray_point_intersection (tv_sf[i], camloc_sf + (vertex_n / 2.0f), -vertex_n)) { if constexpr (debug_move) { std::cout << "A VERTEX intersection is the start at " << tv_sf[i] << ", compare this with hov_sf = " << hov_sf << "\n"; - // if start is vertex, need to check movement across all the triangle-neighbours of this vertex (see later use of int_vertex_hi) + // if start is vertex, need to check movement across all the triangle-neighbours of this vertex } hov_sf = tv_sf[i]; - //int_vertex_hi = hi; isect = true; } ++i; @@ -1385,7 +1413,6 @@ namespace mplot if constexpr (debug_move) { std::cout << "No intersection (at start) with triangle ti0, check neighbours (and maybe update ti0)" << std::endl; } - // When very close to the boundary, ray_tri_intersection may fail. This triggers a // search for a neighbouring triangle which the camera may instead be hovering over // (this can occur when moving along an edge) @@ -1420,9 +1447,7 @@ namespace mplot } while (hi != this->ti0); if (isect == false) { - if constexpr (debug_move) { std::cout << "DBG No intersection (at start) with twins" << std::endl; - } - + if constexpr (debug_move) { std::cout << "DBG No intersection (at start) with twins" << std::endl; } // Final test to see if we're on boundary? float closest_edge_d = sm::geometry::dist_to_tri_edge (tv_sf[0], tv_sf[1], tv_sf[2], camloc_sf - (tn0 * hoverheight)); if constexpr (debug_move) { @@ -1433,7 +1458,7 @@ namespace mplot // make tiny adjustment to camloc_sf so we ARE in the triangle? OR... isect = true; // SAY we are, and proceed? <-- this if it works. } else { - // This might be my last frequently occurring bug? + // If we still have no intersection, throw an exception throw std::runtime_error ("No intersection (at start) with triangle or neighbours"); } } else { @@ -1443,54 +1468,12 @@ namespace mplot } } - // rest of function assumes isect was true (exception otherwise) -#if 0 // FIXME: Sort out what we do if we were over a vertex. Shouldn't need mv_camframe - // Find component of movement that is in the current triangle plane (in the scene frame of reference) - sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - camloc_sf; - sm::vec mv_orthog = tn0 * (mv_sf.dot (tn0) / (tn0.dot (tn0))); - sm::vec mv_inplane = mv_sf - mv_orthog; // scene frame, a relative movement - - // New section to handle the case that we started right on a vertex - if (isect == true && int_vertex_hi != std::numeric_limits::max()) { - // We HAVE a vertex intersection. Check if we either cross, or land in one of this vertex's neighbours to correct our starting triangle and normal. - auto onens = this->find_neighbours (int_vertex_hi); - for (auto _ti : onens) { - sm::vec, 3> tv_nb = this->triangle_vertices (_ti, model_to_scene); - if (tv_nb[0][0] == std::numeric_limits::max()) { - throw std::runtime_error ("_ti is not a triangle"); - } - auto _tn = this->triangle_normal (tv_nb); // gets normal in *scene frame* - sm::vec _mv_orthog = _tn * (mv_sf.dot (_tn) / (_tn.dot (_tn))); // This tn needs to be in scene frame - sm::vec _mv_inplane = mv_sf - _mv_orthog; // scene frame, a relative movement - crossing_data cd = this->compute_crossing_location (tv_nb, _ti, hov_sf, _mv_inplane); - if (cd.pm.flags.test (pm_fl::crossed) == true) { - this->ti0 = _ti; - tn0 = _tn; - tv_sf = tv_nb; - mv_orthog = _mv_orthog; - mv_inplane = _mv_inplane; - if constexpr (debug_move) { std::cout << "Break on cross point with triangle (" << _ti << ")\n"; } - break; - } else { - // No crossing, did we land in the triangle? - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], hov_sf + _mv_inplane + (_tn / 2.0f), -_tn); - if (is) { // then we DID land in this neighbour tri - this->ti0 = _ti; - tn0 = _tn; - tv_sf = tv_nb; - mv_orthog = _mv_orthog; - mv_inplane = _mv_inplane; - if constexpr (debug_move) { std::cout << "Break as we landed IN triangle (" << _ti << ")\n"; } - break; - } - } - } - } // Now carry on with corrected mv_inplane, tn0 and ti0 -#endif return isect; } - // For the two triangles t0 and t1, find if t0 has a twin in t1 and return that halfedge. + /*! + * For the two triangles t0 and t1, find if t0 has a twin in t1 and return that halfedge. + */ uint32_t test_twin (const uint32_t t0, const uint32_t t1) { uint32_t rtn = std::numeric_limits::max(); @@ -1506,7 +1489,10 @@ namespace mplot return rtn; } - // Find any vertex in t0 that shares a vertex with t1. Return the halfedge in t0 for which that vertex is vi[0] + /*! + * Find any vertex in t0 that shares a vertex with t1. Return the halfedge in t0 for which + * that vertex is vi[0] + */ uint32_t test_vertex_twin (const uint32_t t0, const uint32_t t1) { uint32_t rtn = std::numeric_limits::max(); @@ -1523,27 +1509,95 @@ namespace mplot return rtn; } - // Construct the plane dividing the triangles t0 and t1 which are assumed adjacent (joined - // by vertex or edge). Return the plane in transformed (i.e. scene) coordinates. The - // dividing edge_sc is supplied already transformed by model_to_scene. - // FIXME: Check this works as expected - sm::vec, 2> dividing_plane (const uint32_t t0, const uint32_t t1, - const sm::vec& dividing_edge_sc, - const sm::mat& model_to_scene) + /*! + * Did the movement pass through the given neighbour triangle, which is probably a + * neighbour-over-a-vertex? + * + * \param nb The halfedge index of the 'to' or neighbour triangle. + * + * \param tn_frm The normal of the 'from' triangle. + * + * \param mv is the movement, assumed to start in the boundary of the from triangle, and to + * lie in the plane of the from triangle + * + * \param start is the start the movement in the new triangle (and the end in the 'from' + * triangle). i.e. it's on the border between the two. + * + * \param model_to_scene Transform required to obtain triangle vertices in scene frame + * + * \return tuple containing true/false 'was a crossing found?'; the rotation axis and the rest + * of the movement, reoriented to the neighbour + */ + std::tuple, sm::vec> + detect_movement_in_neighbour (const uint32_t nb, + const sm::vec& tn_frm, + const sm::vec& mv, + const sm::vec& start, + const sm::mat& model_to_scene) { - sm::vec, 2> p0_n; // p0_n[0] is plane origin; p0_n[1] is normal - sm::vec n0 = this->triangle_normal (this->triangle_vertices (t0, model_to_scene)); - sm::vec n1 = this->triangle_normal (this->triangle_vertices (t1, model_to_scene)); - sm::vec ntm = n0 + n1; - ntm.renormalize(); // make it unit - sm::vec de = dividing_edge_sc; - de.renormalize(); - p0_n[1] = ntm.cross (de); - p0_n[0] = (model_to_scene * this->vertex[this->halfedge[t1].vi[0]].p).less_one_dim(); - return p0_n; + constexpr bool debug_move = false; + + bool found = false; + + // Does mv_rest pass through this neighbour? + sm::vec, 3> tv_nb = this->triangle_vertices (nb, model_to_scene); + + // Have to reorient to each neighbour to test + auto _tn = this->triangle_normal (tv_nb); + if constexpr (debug_move) { + std::cout << __func__ << " called:\n"; + std::cout << "Test candidate movement in nb: " << nb << " " << tv_nb; + std::cout << "\n norm: " << tv_nb.mean() << "," << (_tn * 0.05f) << "\n"; + std::cout << "start/mv: " << start << "," << mv << "\n"; + } + + sm::vec r_axis = tn_frm.cross (_tn); + if constexpr (debug_move) { std::cout << "Rotation axis: " << start << "," << r_axis << std::endl; } + r_axis.renormalize(); + // Compute the reorientation due to the requested movement into this neighbour + float rotn_angle = tn_frm.angle (_tn, r_axis); + // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation + if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } + sm::mat reorient_model; // reorientation transformation in sf between these two triangles + reorient_model.rotate (r_axis, rotn_angle); + // The 'new' mv_rest + sm::vec mv_reoriented = (reorient_model * mv).less_one_dim(); + const float rl = mv_reoriented.length(); + if (std::isnan (rl)) { return {found, r_axis, mv_reoriented}; } + // Now test points along mv_rest to be in + if constexpr (debug_move) { std::cout << "candidate mv_reoriented is " << start << "," << mv_reoriented << std::endl; } + // The far edge will be the next edge + uint32_t faredge = this->halfedge[nb].next; + const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[nb].vi[1]].p).less_one_dim(); + const sm::vec fee = (model_to_scene * this->vertex[this->halfedge[faredge].vi[1]].p).less_one_dim(); + if constexpr (debug_move) { std::cout << "Far edge is " << fes << "," << (fee - fes) << std::endl; } + partial_movement pm = find_edge_crossing (fes, fee, _tn, start, mv_reoriented); + if (pm.flags.test (pm_fl::crossed) == true) { + if constexpr (debug_move) { std::cout << "Return 'found'; cross point over faredge (" << faredge << ") of nb " << nb << "\n"; } + found = true; + } else { + if constexpr (debug_move) { + std::cout << "NO cross point with faredge (" << faredge << ") of " << nb << ", did we land in nb " << nb << "?\n"; + } + // No crossing, did we land in the triangle? + auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], start + mv_reoriented + (_tn / 2.0f), -_tn); + if (is) { // then we DID land in this neighbour tri + if constexpr (debug_move) { std::cout << "Return found; landed IN nb " << nb << "\n"; } + found = true; + } else { + if constexpr (debug_move) { std::cout << "NO ray_tri_intersection in nb " << nb << "\n"; } + } + } + + return {found, r_axis, mv_reoriented}; } - // By searching over-the-vertex neighbours, find a boundary crossing. + /*! + * By searching all over-the-vertex neighbours (i.e. neighbours over all 3 vertices), find a + * boundary crossing. + * + * Sub-calls detect_movement_in_neighbour. + */ crossing_data find_nearest_boundary_crossing (const sm::vec& hov_sf, const sm::vec& mv_inplane, const sm::mat& model_to_scene) @@ -1621,94 +1675,13 @@ namespace mplot return cd; } - /** - * Did the movement pass through the given neighbour triangle, which is probably a - * neighbour-over-a-vertex? - * - * \param nb The triangle index of the 'to' or neighbour triangle. - * - * \param tn_frm The normal of the 'from' triangle. - * - * \param mv is the movement, assumed to start in the boundary from triangle, in the plane - * of the from triangle - * - * \param start is the start the movement in the new triangle (and the end in the 'from' - * triangle). I.e. it's on the border - * - * \param model_to_scene Transform required to obtain triangle vertices in scene frame - * - * \return tuple containing true/false was a crossing found; the rotation axis and the rest - * of the movement, reoriented to the neighbour - */ - std::tuple, sm::vec> - detect_movement_in_neighbour (const uint32_t nb, - const sm::vec& tn_frm, - const sm::vec& mv, - const sm::vec& start, - const sm::mat& model_to_scene) - { - constexpr bool debug_move = false; - - bool found = false; - - // Does mv_rest pass through this neighbour? - sm::vec, 3> tv_nb = this->triangle_vertices (nb, model_to_scene); - - // Have to reorient to each neighbour to test - auto _tn = this->triangle_normal (tv_nb); - if constexpr (debug_move) { - std::cout << __func__ << " called:\n"; - std::cout << "Test candidate movement in nb: " << nb << " " << tv_nb; - std::cout << "\n norm: " << tv_nb.mean() << "," << (_tn * 0.05f) << "\n"; - std::cout << "start/mv: " << start << "," << mv << "\n"; - } - - sm::vec r_axis = tn_frm.cross (_tn); - if constexpr (debug_move) { std::cout << "Rotation axis: " << start << "," << r_axis << std::endl; } - r_axis.renormalize(); - // Compute the reorientation due to the requested movement into this neighbour - float rotn_angle = tn_frm.angle (_tn, r_axis); - // If tn0 and _tn are identical, then rotn_angle will be NaN, but in that case we want no rotation - if (std::isnan (rotn_angle)) { rotn_angle = 0.0f; } - sm::mat reorient_model; // reorientation transformation in sf between these two triangles - reorient_model.rotate (r_axis, rotn_angle); - // The 'new' mv_rest - sm::vec mv_reoriented = (reorient_model * mv).less_one_dim(); - const float rl = mv_reoriented.length(); - if (std::isnan (rl)) { return {found, r_axis, mv_reoriented}; } - // Now test points along mv_rest to be in - if constexpr (debug_move) { std::cout << "candidate mv_reoriented is " << start << "," << mv_reoriented << std::endl; } - // The far edge will be the next edge - uint32_t faredge = this->halfedge[nb].next; - const sm::vec fes = (model_to_scene * this->vertex[this->halfedge[nb].vi[1]].p).less_one_dim(); - const sm::vec fee = (model_to_scene * this->vertex[this->halfedge[faredge].vi[1]].p).less_one_dim(); - if constexpr (debug_move) { std::cout << "Far edge is " << fes << "," << (fee - fes) << std::endl; } - partial_movement pm = find_edge_crossing (fes, fee, _tn, start, mv_reoriented); - if (pm.flags.test (pm_fl::crossed) == true) { - if constexpr (debug_move) { std::cout << "Return 'found'; cross point over faredge (" << faredge << ") of nb " << nb << "\n"; } - found = true; - } else { - if constexpr (debug_move) { - std::cout << "NO cross point with faredge (" << faredge << ") of " << nb << ", did we land in nb " << nb << "?\n"; - } - // No crossing, did we land in the triangle? - // A couple of times? - auto [is, h] = sm::geometry::ray_tri_intersection (tv_nb[0], tv_nb[1], tv_nb[2], start + mv_reoriented + (_tn / 2.0f), -_tn); - if (is) { // then we DID land in this neighbour tri - if constexpr (debug_move) { std::cout << "Return found; landed IN nb " << nb << "\n"; } - found = true; - } else { - if constexpr (debug_move) { std::cout << "NO ray_tri_intersection in nb " << nb << "\n"; } - } - } - - return {found, r_axis, mv_reoriented}; - } - - /** + /*! * Find which triangle we crossed into over the vertex. Update cd with new * tri_edge. crossing_data required here for initial halfedge and also to be populated. + * * The triangle into which we cross is placed into cd.into + * + * Sub-calls detect_movement_in_neighbour. */ void find_triangle_over_vertex (crossing_data& cd, const sm::vec& mv_inplane, @@ -1748,7 +1721,7 @@ namespace mplot if (found_in) { if constexpr (debug_move) { std::cout << "Found movement into neighbour " << nb << "\n"; } cd.into = nb; - cd.crossed = cv; // Correct to update? + cd.crossed = cv; cd.tri_edge = r_axis; cd.mv_rest = _mv_rest; // Don't update cd.pm.mv/cd.pm.end here, they're already set @@ -1758,9 +1731,11 @@ namespace mplot } } - /** + /*! * Move across triangles, until at the end of mv_inplane. * + * This is the main subroutine of compute_mesh_movement. + * * On each loop, we move either to the end of the movement, if it is within the current * triangle (ti0) OR we move to the boundary that we cross, and adjust mv_inplane. */ @@ -1920,16 +1895,23 @@ namespace mplot } // triangle traversing while loop } - /** + /*! * Compute a movement over this navigation mesh. * * We convert the triangle vertices from the model frame to the scene frame before computing * reorientations, so that non-uniform scalings in the model do not fox us. * - * \param mv_camframe A movement vector in the camera's own frame of reference (an ego-motion) - * \param cam_to_scene The transformation matrix to bring the camera coordinates to the scene frame - * \param model_to_scene The transformation matrix to convert model coordinates to the scene frame - * \param hoverheight + * \param mv_camframe A movement vector in the camera's own frame of reference (an + * ego-motion) + * + * \param cam_to_scene The transformation matrix to bring the camera coordinates to the + * scene frame + * + * \param model_to_scene The transformation matrix to convert model coordinates to the scene + * frame + * + * \param hoverheight The height at which we want the camera to 'hover' over the + * model/landscape surface * * \return The re-positioned camera transform matrix */ @@ -1958,10 +1940,9 @@ namespace mplot // Find the intersection point with the triangle ti0: sm::vec hov_sf = {}; sm::mat cam_to_surface; - bool isect = find_first_intersection (tv_sf, tn0, hov_sf, cam_to_surface, mv_camframe, cam_to_scene, model_to_scene, hoverheight); - if (!isect) { - throw std::runtime_error ("No initial intersection found"); - } + bool isect = this->find_first_intersection (tv_sf, tn0, hov_sf, cam_to_surface, + cam_to_scene, model_to_scene, hoverheight); + if (!isect) { throw std::runtime_error ("No initial intersection found"); } // Find component of movement that is in the current triangle plane (in the scene frame of reference) sm::vec mv_sf = (cam_to_scene * mv_camframe).less_one_dim() - cam_to_scene.translation(); @@ -1973,7 +1954,7 @@ namespace mplot } // Now traverse those triangles! The output of this function is cam_to_surface - traverse_triangles (mv_inplane, tv_sf, tn0, hov_sf, cam_to_surface, model_to_scene); + this->traverse_triangles (mv_inplane, tv_sf, tn0, hov_sf, cam_to_surface, model_to_scene); // Raise cam_to_surface up by hoverheight and then return cam_to_surface.pretranslate (hoverheight * tn0);