From f711a196a228ac3110c51f6f240d47b91af9a421 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 30 Jan 2026 17:48:05 +0000 Subject: [PATCH 01/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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/54] 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.