From 667b0199316e877fc4aee7352067b4cd7251f8cd Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 15 Oct 2025 11:51:16 +0100 Subject: [PATCH 01/16] Adds a VisualModel to show the normals in another model --- mplot/NormalsVisual.h | 73 +++++++++++++++++++++++++++++++++++++++++ mplot/VisualModelBase.h | 6 ++++ 2 files changed, 79 insertions(+) create mode 100644 mplot/NormalsVisual.h diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h new file mode 100644 index 00000000..b402c44f --- /dev/null +++ b/mplot/NormalsVisual.h @@ -0,0 +1,73 @@ +#pragma once + +/*! + * \file Declares VectorVisual to visualize the normals of another VisualModel + */ + +#include +#include +#include +#include + +namespace mplot { + + //! A class to visualize normals for another model + template + class NormalsVisual : public VisualModel + { + public: + NormalsVisual(mplot::VisualModel* _mymodel) + { + this->mymodel = _mymodel; + this->viewmatrix = _mymodel->getViewMatrix(); + } + + void initializeVertices() + { + if (mymodel == nullptr) { + std::cout << "NormalsVisual: I have no model; returning\n"; + return; + } + + // Copy data out of my model... + std::vector mymodelPositions = mymodel->getVertexPositions(); + std::vector mymodelNormals = mymodel->getVertexNormals(); + std::vector mymodelColors = {}; + if (!singlecolour) { 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) { + sm::vec end = (*vp)[ii] + (*vn)[ii] * scale_factor; + sm::vec arrow_line = (*vn)[ii] * scale_factor; + float len = arrow_line.length(); + sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); + cone_start += (*vp)[ii]; + std::array _clr = clr; + if (!singlecolour) { _clr = (*vc)[ii]; } + this->computeTube ((*vp)[ii], cone_start, _clr, _clr, thickness * scale_factor, shapesides); + float conelen = (end - cone_start).length(); + if (arrow_line.length() > conelen) { + this->computeCone (cone_start, end, 0.0f, _clr, thickness * scale_factor * 2.0f, shapesides); + } + } + }; + + // The model for which we will plot normal vectors + mplot::VisualModel* mymodel = nullptr; + // How many sides to each normal vector + int shapesides = 12; + // thickness for the normal vectors + float thickness = 0.025f; + // What proportion of the arrow length should the arrowhead length be? + float arrowhead_prop = 0.25f; + // How much to linearly scale the size of the vector + float scale_factor = 0.1f; + // Vector single colour + bool singlecolour = false; + std::array clr = mplot::colour::grey20; + }; + +} // namespace mplot diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 6d69191f..60d184ea 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -869,6 +869,12 @@ namespace mplot { void twodimensional (const bool val) { this->flags.set (vm_bools::twodimensional, val); } bool twodimensional() const { return this->flags.test (vm_bools::twodimensional); } + //! Getter for vertex positions (for mplot::NormalsVisual) + std::vector getVertexPositions() { return this->vertexPositions; } + //! Getter for vertex normals (for mplot::NormalsVisual) + std::vector getVertexNormals() { return this->vertexNormals; } + std::vector getVertexColors() { return this->vertexColors; } + protected: //! The model-specific view matrix. Used to transform the pose of the model in the scene. From 0002698c7f0c026cc080805981705769a727e2b9 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 15 Oct 2025 15:51:24 +0100 Subject: [PATCH 02/16] Temporary changes to triangles, holding triangle edges that were used to compute normals --- mplot/NormalsVisual.h | 64 ++++++++++++++++++++++++++++++++++++++--- mplot/VisualModelBase.h | 54 ++++++++++++++++++++-------------- 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index b402c44f..de8c5596 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -40,19 +40,73 @@ namespace mplot { auto vc = reinterpret_cast>*>(&mymodelColors); for (uint32_t ii = 0; ii < vn->size(); ++ii) { - sm::vec end = (*vp)[ii] + (*vn)[ii] * scale_factor; - sm::vec arrow_line = (*vn)[ii] * scale_factor; + sm::vec pos = (*vp)[ii]; + sm::vec nv = (*vn)[ii]; + + sm::vec end = pos + nv * scale_factor; + sm::vec arrow_line = nv * scale_factor; float len = arrow_line.length(); sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); - cone_start += (*vp)[ii]; + cone_start += pos; std::array _clr = clr; if (!singlecolour) { _clr = (*vc)[ii]; } - this->computeTube ((*vp)[ii], cone_start, _clr, _clr, thickness * scale_factor, shapesides); + this->computeTube (pos, cone_start, _clr, _clr, thickness * scale_factor, shapesides); float conelen = (end - cone_start).length(); if (arrow_line.length() > conelen) { this->computeCone (cone_start, end, 0.0f, _clr, thickness * scale_factor * 2.0f, shapesides); } } + + std::array ti = {}; + // We also have vp1 (public) and triangles (also public) + for (auto t : mymodel->triangles) { + sm::vec nv = {}; + sm::vec nvc = {}; + sm::vec nvd = {}; + std::tie(ti, nv, nvc, nvd) = t; + // Plot tn at mean location of ti + sm::vec pos = mymodel->vp1[ti[0]] + mymodel->vp1[ti[1]] + mymodel->vp1[ti[2]]; + pos /= 3.0f; + + // Mesh triangle normals + { + sm::vec end = pos + nv * scale_factor; + sm::vec arrow_line = nv * scale_factor; + float len = arrow_line.length(); + sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); + cone_start += pos; + this->computeTube (pos, cone_start, clr, clr, thickness * scale_factor, shapesides); + float conelen = (end - cone_start).length(); + if (arrow_line.length() > conelen) { + this->computeCone (cone_start, end, 0.0f, clr, thickness * scale_factor * 2.0f, shapesides); + } + } + // Computed triangle normals + { + sm::vec end = pos + nvc * scale_factor; + sm::vec arrow_line = nvc * scale_factor; + float len = arrow_line.length(); + sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); + cone_start += pos; + this->computeTube (pos, cone_start, clrnc, clrnc, thickness * scale_factor, shapesides); + float conelen = (end - cone_start).length(); + if (arrow_line.length() > conelen) { + this->computeCone (cone_start, end, 0.0f, clrnc, thickness * scale_factor * 2.0f, shapesides); + } + } + { + sm::vec end = pos + nvd * scale_factor; + sm::vec arrow_line = nvd * scale_factor; + float len = arrow_line.length(); + sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); + cone_start += pos; + this->computeTube (pos, cone_start, clrnd, clrnd, thickness * scale_factor, shapesides); + float conelen = (end - cone_start).length(); + if (arrow_line.length() > conelen) { + this->computeCone (cone_start, end, 0.0f, clrnd, thickness * scale_factor * 2.0f, shapesides); + } + } + } }; // The model for which we will plot normal vectors @@ -68,6 +122,8 @@ namespace mplot { // 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 }; } // namespace mplot diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 60d184ea..f686e44b 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -227,8 +227,8 @@ namespace mplot { // The edges that make up the same triangles as are shown with this->indices, but in terms of vp1. // Each edge must be two indices in *ascending numerical order* std::set> edges; - // Triangles too. Might be more useful than edges. - sm::vvec, sm::vec>> triangles; + // Triangles too. Might be more useful than edges. Triangle given as indices into vp1 + sm::vvec, sm::vec, sm::vec, sm::vec>> triangles; // Return index of vp1 that is closest to scene_coord. Can use vp1_to_indices to find the indices // into vertexPositions and vertexNormals that this index in the topographic mesh relates to. @@ -259,16 +259,16 @@ namespace mplot { return trivert; } - sm::vvec neighbours (const uint32_t idx) const + 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 + // 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) { + if (e[0] == _idx) { // neighb is e[1] rtn.push_back (e[1]); - } else if (e[1] == idx) { + } else if (e[1] == _idx) { // neighb is e[0] rtn.push_back (e[0]); } @@ -276,13 +276,13 @@ namespace mplot { return rtn; } - sm::vvec> neighbour_triangles (const uint32_t idx) const + sm::vvec> neighbour_triangles (const uint32_t _idx) const { sm::vvec> rtn; for (auto t: this->triangles) { - auto [ti, tn] = t; - // If it includes idx, add it to rtn - if (ti[0] == idx || ti[1] == idx || ti[2] == idx) { + 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); } } @@ -310,7 +310,7 @@ namespace mplot { find_triangle_crossing (const sm::vec& coord, const sm::vec& vdir) const { for (auto tri : triangles) { - auto [ti, tn] = tri; + auto [ti, tn, tnc, tnd] = tri; auto [isect, p] = sm::algo::ray_tri_intersection (this->vp1[ti[0]], this->vp1[ti[1]], this->vp1[ti[2]], coord, vdir); if (isect) { return {p, ti, tn}; } } @@ -335,7 +335,7 @@ namespace mplot { sm::vec other_n = {fmax, fmax, fmax}; sm::vec my_n = {fmax, fmax, fmax}; // debug for (auto tri : triangles) { - auto [ti, tn] = tri; + auto [ti, tn, tnc, tnd] = tri; if (ti == not_this) { if constexpr (debug_normals) { my_n = tn; } continue; @@ -374,7 +374,8 @@ namespace mplot { */ void vertex_postprocess() // make_neighbour_mesh() ? { - constexpr bool debug = true; + constexpr bool debug = false; + constexpr bool debug_reorder = true; if constexpr (debug) { std::cout << __func__ << " called\n"; } // For each vertex, search for other vertices that have the same or almost the same location @@ -457,23 +458,34 @@ namespace mplot { // Direct population of triangles std::array t = { equiv_top[indices[i]], equiv_top[indices[i+1]], equiv_top[indices[i+2]] }; - // The normal vector for this triangle is easy to get, as we're dealing with indices already + // 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(); - if constexpr (debug) { std::cout << "Triangle normal: " << trinorm << std::endl; } - // Check rotational sense of triangles - sm::vec n = (vp1[t[1]] - vp1[t[0]]).cross (vp1[t[2]] - vp1[t[0]]); + // 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 = vp1[t[0]]; + const sm::vec& tv1 = vp1[t[1]]; + const sm::vec& tv2 = vp1[t[2]]; + 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) { // need to swap order in t: - if constexpr (debug) { std::cout << "Triangle reordered (corners 1 and 2 switched)\n"; } + if constexpr (debug_reorder) { std::cout << "Triangle reordered (corners 1 and 2 switched)\n"; } uint32_t ti = t[2]; t[2] = t[1]; t[1] = ti; + n = -n; // Also reverse n } - this->triangles.push_back ({t, trinorm}); - // If required, can store trinorm into a container like triangle_normals with a push_back. + this->triangles.push_back ({t, n, nx, ny}); // n is computed normal } if constexpr (debug) { @@ -481,7 +493,7 @@ namespace mplot { std::cout << "Edge: " << e[0] << "," << e[1] << std::endl; } for (auto t : this->triangles) { - auto [ti, tn] = t; + auto [ti, tn, tnc, tnd] = t; std::cout << "Tri: " << ti[0] << "," << ti[1] << "," << ti[2] << ", norm " << tn << std::endl; } } From b21bb33ab94c1d212ace5643ab26f58ad0d0b577 Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 16 Oct 2025 14:27:13 +0100 Subject: [PATCH 03/16] Allows this function to have 4d vecs passed to it --- mplot/RodVisual.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mplot/RodVisual.h b/mplot/RodVisual.h index 510ec9f8..bc09e2fa 100644 --- a/mplot/RodVisual.h +++ b/mplot/RodVisual.h @@ -66,10 +66,13 @@ namespace mplot { } } - void update (const sm::vec& s, const sm::vec& e) + template requires (N == 3) || (N == 4) + void update (const sm::vec& s, const sm::vec& e) { - this->start_coord = s; - this->end_coord = e; + for (std::size_t i = 0; i < 3; ++i) { + this->start_coord[i] = s[i]; + this->end_coord[i] = e[i]; + } this->reinit(); } From 0280e39901ae78b162d35fca7ccde66df777edaf Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 16 Oct 2025 17:12:47 +0100 Subject: [PATCH 04/16] Fix typo --- mplot/NormalsVisual.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index de8c5596..16778238 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -1,7 +1,7 @@ #pragma once /*! - * \file Declares VectorVisual to visualize the normals of another VisualModel + * \file Declares NormalsVisual to visualize the normals of another VisualModel */ #include From b3e1d7a01a1217e0a01312e3d3ebd248f973be6b Mon Sep 17 00:00:00 2001 From: Seb James Date: Thu, 16 Oct 2025 17:14:16 +0100 Subject: [PATCH 05/16] Declare this file will be installled --- mplot/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/mplot/CMakeLists.txt b/mplot/CMakeLists.txt index 83286a28..c5388ab1 100644 --- a/mplot/CMakeLists.txt +++ b/mplot/CMakeLists.txt @@ -76,6 +76,7 @@ install( HSVWheelVisual.h IcosaVisual.h LengthscaleVisual.h + NormalsVisual.h PointRowsMeshVisual.h PointRowsVisual.h PolarVisual.h From 88497963378acf7837ff3f7da9792281b90b489d Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 17 Oct 2025 17:11:50 +0100 Subject: [PATCH 06/16] Adds new example --- examples/CMakeLists.txt | 3 ++ examples/geodesic_with_normals.cpp | 71 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 examples/geodesic_with_normals.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index eff61951..e73f606a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -367,6 +367,9 @@ if(NOT APPLE) add_executable(geodesic geodesic.cpp) target_link_libraries(geodesic OpenGL::GL glfw Freetype::Freetype) + add_executable(geodesic_with_normals geodesic_with_normals.cpp) + target_link_libraries(geodesic_with_normals OpenGL::GL glfw Freetype::Freetype) + add_executable(geodesic_ce geodesic_ce.cpp) target_link_libraries(geodesic_ce OpenGL::GL glfw Freetype::Freetype) endif() diff --git a/examples/geodesic_with_normals.cpp b/examples/geodesic_with_normals.cpp new file mode 100644 index 00000000..d2321278 --- /dev/null +++ b/examples/geodesic_with_normals.cpp @@ -0,0 +1,71 @@ +/* + * Visualize a sequence of icosahedral geodesics, showing their normals + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +int main() +{ + int rtn = -1; + + mplot::Visual v(1024, 768, "Geodesic Polyhedra with normals"); + v.showCoordArrows (true); + // Set the Visual to rotate about the nearest VisualModel (Change at runtime with Ctrl-k) + v.rotateAboutNearest (true); + // In this example, use the 'rotate about a scene vertical axis' mode + v.rotateAboutVertical (true); + + try { + sm::vec offset = {}; + sm::vec step = { 2.2f }; + + mplot::ColourMap cm (mplot::ColourMapType::Jet); + int imax = 4; + for (int i = 0; i < imax; ++i) { + auto cl = cm.convert (i / static_cast(imax - 1)); + auto gv1 = std::make_unique> (offset + step * i, 0.9f); + v.bindmodel (gv1); + gv1->iterations = i; + std::string lbl = std::string("iterations = ") + std::to_string(i); + gv1->addLabel (lbl, {0, -1, 0}, mplot::TextFeatures(0.06f)); + gv1->cm.setType (mplot::ColourMapType::Jet); + gv1->colour_bb = cl; + gv1->vertex_postprocess(); // creates the triangles and normals required for NormalsVisual + gv1->finalize(); + + // re-colour after construction + auto gv1p = v.addVisualModel (gv1); + float imax_mult = 1.0f / static_cast(imax); + // sequential colouring: + size_t sz1 = gv1p->data.size(); + gv1p->data.linspace (0.0f, 1+i * imax_mult, sz1); + gv1p->reinitColours(); + + // Create an associate normals model + auto nrm = std::make_unique> (gv1p); + v.bindmodel (nrm); + nrm->finalize(); + v.addVisualModel (nrm); + } + + v.keepOpen(); + + } catch (const std::exception& e) { + std::cerr << "Caught exception: " << e.what() << std::endl; + rtn = -1; + } + + return rtn; +} From eb4869ea1ab28b3f0ef4296cfb30a2e1b12a49d9 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 17 Oct 2025 17:12:15 +0100 Subject: [PATCH 07/16] Use latest maths --- maths | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths b/maths index 548e7a03..de5a7954 160000 --- a/maths +++ b/maths @@ -1 +1 @@ -Subproject commit 548e7a03193e24f875756a7957e4efbb3beaef30 +Subproject commit de5a7954bbbb0cfcb885c7f82a16693ff44eb51c From 83a66ca3d1bcf2986f824d45d6947babe465cf76 Mon Sep 17 00:00:00 2001 From: Seb James Date: Fri, 17 Oct 2025 17:20:52 +0100 Subject: [PATCH 08/16] Another example --- examples/CMakeLists.txt | 3 ++ examples/geodesic_with_normals.cpp | 3 +- examples/rod_with_normals.cpp | 66 ++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 examples/rod_with_normals.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e73f606a..e049ebe7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -336,6 +336,9 @@ target_link_libraries(threewindows OpenGL::GL glfw Freetype::Freetype) add_executable(rod rod.cpp) target_link_libraries(rod OpenGL::GL glfw Freetype::Freetype) +add_executable(rod_with_normals rod_with_normals.cpp) +target_link_libraries(rod_with_normals OpenGL::GL glfw Freetype::Freetype) + add_executable(rod_pan rod_pan.cpp) target_link_libraries(rod_pan OpenGL::GL glfw Freetype::Freetype) diff --git a/examples/geodesic_with_normals.cpp b/examples/geodesic_with_normals.cpp index d2321278..3bca7161 100644 --- a/examples/geodesic_with_normals.cpp +++ b/examples/geodesic_with_normals.cpp @@ -42,7 +42,6 @@ int main() gv1->addLabel (lbl, {0, -1, 0}, mplot::TextFeatures(0.06f)); gv1->cm.setType (mplot::ColourMapType::Jet); gv1->colour_bb = cl; - gv1->vertex_postprocess(); // creates the triangles and normals required for NormalsVisual gv1->finalize(); // re-colour after construction @@ -53,6 +52,8 @@ int main() gv1p->data.linspace (0.0f, 1+i * imax_mult, sz1); gv1p->reinitColours(); + gv1p->vertex_postprocess(); // creates the triangles and normals required for NormalsVisual + // Create an associate normals model auto nrm = std::make_unique> (gv1p); v.bindmodel (nrm); diff --git a/examples/rod_with_normals.cpp b/examples/rod_with_normals.cpp new file mode 100644 index 00000000..fad26f42 --- /dev/null +++ b/examples/rod_with_normals.cpp @@ -0,0 +1,66 @@ +/* + * Visualize a Rod + */ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +int main() +{ + int rtn = -1; + + mplot::Visual v(1024, 768, "Visualization"); + v.zNear = 0.001; + //v.showCoordArrows (true); + //v.coordArrowsInScene (true); + // For a white background: + v.backgroundWhite(); + // Switch on a mix of diffuse/ambient lighting + v.lightingEffects(true); + + try { + constexpr sm::vec colour1 = { 1.0, 0.0, 0.0 }; + constexpr sm::vec colour2 = { 0.0, 0.9, 0.4 }; + + sm::vec offset = { 0.0, 0.0, 0.0 }; + sm::vec start = { 0, 0, 0 }; + sm::vec end = { 0.25, 0, 0 }; + std::unique_ptr> rvm = std::make_unique> (offset, start, end, 0.1f, colour1, colour1); + v.bindmodel (rvm); + rvm->finalize(); + v.addVisualModel (rvm); + + sm::vec start2 = { -0.1, 0.2, 0.6 }; + sm::vec end2 = { 0.2, 0.4, 0.6 }; + // You can reuse the unique_ptr rvm, once you've transferred ownership with v.addVisualModel (rvm) + rvm = std::make_unique>(offset, start2, end2, 0.05f, colour2); + v.bindmodel (rvm); + rvm->finalize(); + auto rvmp = v.addVisualModel (rvm); + rvmp->vertex_postprocess(); + + // Create an associate normals model + auto nrm = std::make_unique> (rvmp); + v.bindmodel (nrm); + nrm->finalize(); + v.addVisualModel (nrm); + + v.keepOpen(); + + } catch (const std::exception& e) { + std::cerr << "Caught exception: " << e.what() << std::endl; + rtn = -1; + } + + return rtn; +} From 7ef5c90a4841daabac62d91eb6d9a54f05ad7014 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 20 Oct 2025 15:37:01 +0100 Subject: [PATCH 09/16] Corrects the order of the cross product in computeTube --- mplot/RodVisual.h | 3 ++- mplot/VisualModelBase.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mplot/RodVisual.h b/mplot/RodVisual.h index bc09e2fa..8bf95b7d 100644 --- a/mplot/RodVisual.h +++ b/mplot/RodVisual.h @@ -84,7 +84,8 @@ namespace mplot { float radius = 1.0f; //! If true, use face_uz and face_uy to draw the tube, else get a square-ended tube bool use_oriented_tube = true; - //! Face directions for the oriented tube + //! Face directions for the oriented tube's *end* cap. Choose carefully so that face_uy ^ + //! face_uz gives the normal for the end cap. sm::vec face_uy = sm::vec::uy(); sm::vec face_uz = sm::vec::uz(); diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index f686e44b..5818d816 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -1014,7 +1014,7 @@ namespace mplot { * \param start The start of the tube * \param end The end of the tube * \param _ux a vector in the x axis direction for the end face - * \param _uy a vector in the y axis direction + * \param _uy a vector in the y axis direction. _ux ^ _uy gives the end cap normal * \param colStart The tube starting colour * \param colEnd The tube's ending colour * \param r Radius of the tube @@ -1033,7 +1033,7 @@ namespace mplot { sm::vec vend = end; // v is a face normal - sm::vec v = _uy.cross(_ux); + sm::vec v = _ux.cross(_uy); v.renormalize(); // If bounding box, populate different buffers: From f6fbc78a88bbc5e419235dd661ff550433a97930 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 20 Oct 2025 15:45:35 +0100 Subject: [PATCH 10/16] Corrects computeSphereGeoFaces primitive (to create the right normals) --- mplot/VisualModelBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index 5818d816..f4e812b4 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -1773,7 +1773,7 @@ namespace mplot { for (int i = 0; i < n_faces; ++i) { // For each face in the geodesic... sm::vec norm = { F{0}, F{0}, F{0} }; for (auto vtx : geo.poly.faces[i]) { // For each vertex in face... - norm += vtx; // Add to the face norm + norm += geo.poly.vertices[vtx]; // Add to the face norm this->vertex_push (geo.poly.vertices[vtx].as_float() * r + so, this->vertexPositions); } sm::vec nf = (norm / F{3}).as_float(); From f6c2b4ade8d105795e3640cc0bb2343910887b76 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 20 Oct 2025 15:52:48 +0100 Subject: [PATCH 11/16] vertex_postprocess debug reduction --- mplot/VisualModelBase.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index f4e812b4..d41ad7f2 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -375,7 +375,7 @@ namespace mplot { void vertex_postprocess() // make_neighbour_mesh() ? { constexpr bool debug = false; - constexpr bool debug_reorder = true; + constexpr bool debug_reorder = false; if constexpr (debug) { std::cout << __func__ << " called\n"; } // For each vertex, search for other vertices that have the same or almost the same location @@ -496,9 +496,8 @@ namespace mplot { auto [ti, tn, tnc, tnd] = t; std::cout << "Tri: " << ti[0] << "," << ti[1] << "," << ti[2] << ", norm " << tn << std::endl; } + std::cout << this->edges.size() << " edges and " << this->triangles.size() << " triangles in model '" << this->name << "'\n"; } - - std::cout << this->edges.size() << " edges and " << this->triangles.size() << " triangles in " << this->name << "\n"; } /** From a56a2f5345b31663cdf61ea960f3d43936fbdc30 Mon Sep 17 00:00:00 2001 From: Seb James Date: Mon, 20 Oct 2025 15:53:57 +0100 Subject: [PATCH 12/16] Show normals for both rods --- examples/rod_with_normals.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/rod_with_normals.cpp b/examples/rod_with_normals.cpp index fad26f42..aff1d57b 100644 --- a/examples/rod_with_normals.cpp +++ b/examples/rod_with_normals.cpp @@ -30,27 +30,33 @@ int main() try { constexpr sm::vec colour1 = { 1.0, 0.0, 0.0 }; - constexpr sm::vec colour2 = { 0.0, 0.9, 0.4 }; - sm::vec offset = { 0.0, 0.0, 0.0 }; sm::vec start = { 0, 0, 0 }; sm::vec end = { 0.25, 0, 0 }; - std::unique_ptr> rvm = std::make_unique> (offset, start, end, 0.1f, colour1, colour1); + auto rvm = std::make_unique> (offset, start, end, 0.1f, colour1, colour1); v.bindmodel (rvm); + rvm->use_oriented_tube = false; rvm->finalize(); - v.addVisualModel (rvm); + auto rvmp1 = v.addVisualModel (rvm); + rvmp1->vertex_postprocess(); + auto nrm = std::make_unique> (rvmp1); + v.bindmodel (nrm); + nrm->finalize(); + v.addVisualModel (nrm); + + constexpr sm::vec colour2 = { 0.0, 0.9, 0.4 }; sm::vec start2 = { -0.1, 0.2, 0.6 }; sm::vec end2 = { 0.2, 0.4, 0.6 }; // You can reuse the unique_ptr rvm, once you've transferred ownership with v.addVisualModel (rvm) rvm = std::make_unique>(offset, start2, end2, 0.05f, colour2); v.bindmodel (rvm); rvm->finalize(); - auto rvmp = v.addVisualModel (rvm); - rvmp->vertex_postprocess(); + auto rvmp2 = v.addVisualModel (rvm); + rvmp2->vertex_postprocess(); // Create an associate normals model - auto nrm = std::make_unique> (rvmp); + nrm = std::make_unique> (rvmp2); v.bindmodel (nrm); nrm->finalize(); v.addVisualModel (nrm); From decb14609c6be1eadeedd808588b18269e5618e1 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 22 Oct 2025 09:49:05 +0100 Subject: [PATCH 13/16] Slight changes in tri mesh post processing --- mplot/VisualModelBase.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mplot/VisualModelBase.h b/mplot/VisualModelBase.h index d41ad7f2..1287342f 100644 --- a/mplot/VisualModelBase.h +++ b/mplot/VisualModelBase.h @@ -311,7 +311,7 @@ namespace mplot { { for (auto tri : triangles) { auto [ti, tn, tnc, tnd] = tri; - auto [isect, p] = sm::algo::ray_tri_intersection (this->vp1[ti[0]], this->vp1[ti[1]], this->vp1[ti[2]], coord, vdir); + auto [isect, p] = sm::algo::ray_tri_intersection (this->vp1[ti[0]], this->vp1[ti[1]], this->vp1[ti[2]], coord - (vdir / 2.0f), vdir); if (isect) { return {p, ti, tn}; } } @@ -360,7 +360,9 @@ namespace mplot { std::tuple, std::array, sm::vec> find_triangle_crossing (const sm::vec& coord) const { - return this->find_triangle_crossing (coord, (this->bb.mid() - coord)); + sm::vec vdir = this->bb.mid() - coord; + vdir.renormalize(); + return this->find_triangle_crossing (coord, vdir); } /*! From e72749613d78bf19f68b8079768c0a9c4d05eaf4 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 22 Oct 2025 10:00:59 +0100 Subject: [PATCH 14/16] Use our arrow primitive --- mplot/NormalsVisual.h | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 16778238..88382bf6 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -67,45 +67,14 @@ namespace mplot { // Plot tn at mean location of ti sm::vec pos = mymodel->vp1[ti[0]] + mymodel->vp1[ti[1]] + mymodel->vp1[ti[2]]; pos /= 3.0f; - // Mesh triangle normals - { - sm::vec end = pos + nv * scale_factor; - sm::vec arrow_line = nv * scale_factor; - float len = arrow_line.length(); - sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); - cone_start += pos; - this->computeTube (pos, cone_start, clr, clr, thickness * scale_factor, shapesides); - float conelen = (end - cone_start).length(); - if (arrow_line.length() > conelen) { - this->computeCone (cone_start, end, 0.0f, clr, thickness * scale_factor * 2.0f, shapesides); - } - } + this->computeArrow (pos, (pos + nv * scale_factor), clr, thickness * scale_factor, + arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); // Computed triangle normals - { - sm::vec end = pos + nvc * scale_factor; - sm::vec arrow_line = nvc * scale_factor; - float len = arrow_line.length(); - sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); - cone_start += pos; - this->computeTube (pos, cone_start, clrnc, clrnc, thickness * scale_factor, shapesides); - float conelen = (end - cone_start).length(); - if (arrow_line.length() > conelen) { - this->computeCone (cone_start, end, 0.0f, clrnc, thickness * scale_factor * 2.0f, shapesides); - } - } - { - sm::vec end = pos + nvd * scale_factor; - sm::vec arrow_line = nvd * scale_factor; - float len = arrow_line.length(); - sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); - cone_start += pos; - this->computeTube (pos, cone_start, clrnd, clrnd, thickness * scale_factor, shapesides); - float conelen = (end - cone_start).length(); - if (arrow_line.length() > conelen) { - this->computeCone (cone_start, end, 0.0f, clrnd, thickness * scale_factor * 2.0f, shapesides); - } - } + this->computeArrow (pos, (pos + nvc * scale_factor), clrnc, thickness * scale_factor, + arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); + this->computeArrow (pos, (pos + nvd * scale_factor), clrnd, thickness * scale_factor, + arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); } }; From 3c879b80b4bb1e018cac6c6aa0205b7fe5761f7a Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 22 Oct 2025 10:03:49 +0100 Subject: [PATCH 15/16] Completes transition to use of computeArrow --- mplot/NormalsVisual.h | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index 88382bf6..fdc3e974 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -42,19 +42,10 @@ namespace mplot { for (uint32_t ii = 0; ii < vn->size(); ++ii) { sm::vec pos = (*vp)[ii]; sm::vec nv = (*vn)[ii]; - - sm::vec end = pos + nv * scale_factor; - sm::vec arrow_line = nv * scale_factor; - float len = arrow_line.length(); - sm::vec cone_start = arrow_line.shorten (len * arrowhead_prop); - cone_start += pos; std::array _clr = clr; if (!singlecolour) { _clr = (*vc)[ii]; } - this->computeTube (pos, cone_start, _clr, _clr, thickness * scale_factor, shapesides); - float conelen = (end - cone_start).length(); - if (arrow_line.length() > conelen) { - this->computeCone (cone_start, end, 0.0f, _clr, thickness * scale_factor * 2.0f, shapesides); - } + this->computeArrow (pos, (pos + nv * scale_factor), _clr, thickness * scale_factor, + arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); } std::array ti = {}; From 1a8274e95b11ecae30cd16766353f8ba46f417a9 Mon Sep 17 00:00:00 2001 From: Seb James Date: Wed, 22 Oct 2025 10:12:35 +0100 Subject: [PATCH 16/16] Completes tidy up --- mplot/NormalsVisual.h | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/mplot/NormalsVisual.h b/mplot/NormalsVisual.h index fdc3e974..cdac03e2 100644 --- a/mplot/NormalsVisual.h +++ b/mplot/NormalsVisual.h @@ -29,6 +29,8 @@ namespace mplot { return; } + 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(); @@ -40,32 +42,31 @@ namespace mplot { auto vc = reinterpret_cast>*>(&mymodelColors); for (uint32_t ii = 0; ii < vn->size(); ++ii) { - sm::vec pos = (*vp)[ii]; - sm::vec nv = (*vn)[ii]; + // (*vp)[ii] is position, (*vn)[ii] is normal std::array _clr = clr; if (!singlecolour) { _clr = (*vc)[ii]; } - this->computeArrow (pos, (pos + nv * scale_factor), _clr, thickness * scale_factor, - arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); + this->computeArrow ((*vp)[ii], ((*vp)[ii] + (*vn)[ii] * this->scale_factor), + _clr, tube_r, this->arrowhead_prop, cone_r, this->shapesides); } std::array ti = {}; + sm::vec nv = {}; + sm::vec nvc = {}; + sm::vec nvd = {}; + sm::vec pos = {}; // We also have vp1 (public) and triangles (also public) for (auto t : mymodel->triangles) { - sm::vec nv = {}; - sm::vec nvc = {}; - sm::vec nvd = {}; std::tie(ti, nv, nvc, nvd) = t; // Plot tn at mean location of ti - sm::vec pos = mymodel->vp1[ti[0]] + mymodel->vp1[ti[1]] + mymodel->vp1[ti[2]]; - pos /= 3.0f; + pos = (mymodel->vp1[ti[0]] + mymodel->vp1[ti[1]] + mymodel->vp1[ti[2]]) / 3.0f; // Mesh triangle normals - this->computeArrow (pos, (pos + nv * scale_factor), clr, thickness * scale_factor, - arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); + 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 * scale_factor), clrnc, thickness * scale_factor, - arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); - this->computeArrow (pos, (pos + nvd * scale_factor), clrnd, thickness * scale_factor, - arrowhead_prop, thickness * scale_factor * 2.0f, shapesides); + 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); } };