From be66936dadf166e71e5bd2553f2231089869e505 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Mon, 3 Aug 2026 22:27:46 +0530 Subject: [PATCH 01/15] Basic_viewer: colour faces by value (distance to the clipping plane) --- Basic_viewer/include/CGAL/Basic_shaders.h | 30 +++++++++++++++++++-- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 22 +++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Basic_viewer/include/CGAL/Basic_shaders.h b/Basic_viewer/include/CGAL/Basic_shaders.h index 97c8aa359223..84746a5f8233 100644 --- a/Basic_viewer/include/CGAL/Basic_shaders.h +++ b/Basic_viewer/include/CGAL/Basic_shaders.h @@ -75,6 +75,23 @@ uniform highp vec4 u_PointPlane; uniform mediump float u_RenderingMode; uniform mediump float u_RenderingTransparency; +// Colour by value: 0 keeps the vertex colour, otherwise a palette index. The +// value shown is the signed distance from the fragment to the clipping plane, +// normalised to [u_ValueMin, u_ValueMax]. +uniform mediump float u_ColorMapMode; +uniform mediump float u_ValueMin; +uniform mediump float u_ValueMax; + +vec3 colour_palette(float t, float mode) +{ + if (mode < 1.5) + { return clamp(vec3(t*3.0, t*3.0-1.0, t*3.0-2.0), 0.0, 1.0); } // heat + if (mode < 2.5) + { return clamp(vec3(1.5-abs(4.0*t-3.0), 1.5-abs(4.0*t-2.0), 1.5-abs(4.0*t-1.0)), + 0.0, 1.0); } // jet + return vec3(t); // grey ramp +} + void main(void) { highp vec3 L = u_LightPos.xyz - vs_fP.xyz; @@ -84,9 +101,18 @@ void main(void) L = normalize(L); V = normalize(V); + // Base colour is the vertex colour, or a palette applied to the value. + vec3 base = fColor.rgb; + if (u_ColorMapMode > 0.5) + { + float value = dot(ls_fP.xyz-u_PointPlane.xyz, normalize(u_ClipPlane.xyz)); + float t = clamp((value-u_ValueMin)/max(u_ValueMax-u_ValueMin, 1e-6), 0.0, 1.0); + base = colour_palette(t, u_ColorMapMode); + } + highp vec3 R = reflect(-L, a_Normal); - highp vec4 diffuse = vec4(max(dot(a_Normal,L), 0.0) * u_LightDiff.rgb * fColor.rgb, 1.0); - highp vec4 ambient = vec4(u_LightAmb.rgb * fColor.rgb, 1.0); + highp vec4 diffuse = vec4(max(dot(a_Normal,L), 0.0) * u_LightDiff.rgb * base, 1.0); + highp vec4 ambient = vec4(u_LightAmb.rgb * base, 1.0); highp vec4 specular = pow(max(dot(R,V), 0.0), u_SpecPower) * u_LightSpec; // onPlane == 1: inside clipping plane, should be solid; diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index ed584d50cb76..d4032e79a961 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -124,6 +124,7 @@ class Basic_viewer : public CGAL::QGLViewer setKeyDescription(::Qt::Key_U, "Move camera direction upside down"); setKeyDescription(::Qt::Key_V, "Toggles vertices display"); setKeyDescription(::Qt::Key_W, "Toggles faces display"); + setKeyDescription(::Qt::Key_D, "Cycle colouring faces by value (distance to the plane)"); setKeyDescription(::Qt::Key_Plus, "Increase size of edges"); setKeyDescription(::Qt::Key_Minus, "Decrease size of edges"); setKeyDescription(::Qt::ControlModifier, ::Qt::Key_Plus, "Increase size of vertices"); @@ -733,6 +734,11 @@ class Basic_viewer : public CGAL::QGLViewer rendering_program_face.setUniformValue("u_RenderingTransparency", clipping_plane_rendering_transparency); rendering_program_face.setUniformValue("u_ClipPlane", clipPlane); rendering_program_face.setUniformValue("u_PointPlane", plane_point); + // Colour by value: the value is the distance to the clipping plane, scaled + // by the scene radius each side of it. + rendering_program_face.setUniformValue("u_ColorMapMode", static_cast(m_color_map)); + rendering_program_face.setUniformValue("u_ValueMin", static_cast(-sceneRadius())); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(sceneRadius())); vao[VAO_FACES].bind(); glDrawArrays(GL_TRIANGLES, 0, static_cast(m_scene.number_of_elements(GS::POS_FACES))); @@ -2291,6 +2297,21 @@ class Basic_viewer : public CGAL::QGLViewer displayMessage(QString("Draw faces=%1.").arg(m_draw_faces?"true":"false")); update(); } + else if ((e->key()==::Qt::Key_D) && (modifiers==::Qt::NoButton)) + { + // Colour the faces by a value (here the distance to the clipping plane): + // off, then the heat, jet and grey palettes. + m_color_map=(m_color_map+1)%4; + switch(m_color_map) + { + case 0: displayMessage(QString("Colour by value = off")); break; + case 1: displayMessage(QString("Colour by value = heat (distance to plane)")); break; + case 2: displayMessage(QString("Colour by value = jet (distance to plane)")); break; + case 3: displayMessage(QString("Colour by value = grey (distance to plane)")); break; + default: break; + } + update(); + } else if ((e->key()==::Qt::Key_Plus) && (!modifiers.testFlag(::Qt::ControlModifier))) // No ctrl { m_size_edges+=.5; @@ -2546,6 +2567,7 @@ class Basic_viewer : public CGAL::QGLViewer std::vector> m_edge_owners; // whole-volume clip: per edge, owning volumes std::vector> m_point_owners; // whole-volume clip: per vertex, owning volumes bool m_clip_owners_valid = false; // whole-volume clip: are the owner lists up to date + int m_color_map=0; // colour by value: 0 off, 1 heat, 2 jet, 3 grey ramp CGAL::qglviewer::ManipulatedFrame* m_frame_plane=nullptr; // Buffer for clipping plane is not stored in the scene because it is not From 274459d787bf60eb8035801cb63c09fdfb2bc512 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Wed, 5 Aug 2026 23:00:04 +0530 Subject: [PATCH 02/15] Basic_viewer: colour by value per cell (one flat colour per cell) --- Basic_viewer/include/CGAL/Basic_shaders.h | 7 +++- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 37 ++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Basic_viewer/include/CGAL/Basic_shaders.h b/Basic_viewer/include/CGAL/Basic_shaders.h index 84746a5f8233..fbd25dc4000d 100644 --- a/Basic_viewer/include/CGAL/Basic_shaders.h +++ b/Basic_viewer/include/CGAL/Basic_shaders.h @@ -81,6 +81,10 @@ uniform mediump float u_RenderingTransparency; uniform mediump float u_ColorMapMode; uniform mediump float u_ValueMin; uniform mediump float u_ValueMax; +// Per cell: use the cell centre instead of the fragment position, so a whole cell +// takes one flat colour and neighbouring cells do not melt together. +uniform int u_ColorPerCell; +uniform highp vec3 u_CellCentroid; vec3 colour_palette(float t, float mode) { @@ -105,7 +109,8 @@ void main(void) vec3 base = fColor.rgb; if (u_ColorMapMode > 0.5) { - float value = dot(ls_fP.xyz-u_PointPlane.xyz, normalize(u_ClipPlane.xyz)); + vec3 p = (u_ColorPerCell != 0) ? u_CellCentroid : ls_fP.xyz; + float value = dot(p-u_PointPlane.xyz, normalize(u_ClipPlane.xyz)); float t = clamp((value-u_ValueMin)/max(u_ValueMax-u_ValueMin, 1e-6), 0.0, 1.0); base = colour_palette(t, u_ColorMapMode); } diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index d4032e79a961..d38830cb9598 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -125,6 +125,7 @@ class Basic_viewer : public CGAL::QGLViewer setKeyDescription(::Qt::Key_V, "Toggles vertices display"); setKeyDescription(::Qt::Key_W, "Toggles faces display"); setKeyDescription(::Qt::Key_D, "Cycle colouring faces by value (distance to the plane)"); + setKeyDescription(::Qt::ShiftModifier, ::Qt::Key_D, "Colour by value: per cell (flat) or smooth"); setKeyDescription(::Qt::Key_Plus, "Increase size of edges"); setKeyDescription(::Qt::Key_Minus, "Decrease size of edges"); setKeyDescription(::Qt::ControlModifier, ::Qt::Key_Plus, "Increase size of vertices"); @@ -741,7 +742,32 @@ class Basic_viewer : public CGAL::QGLViewer rendering_program_face.setUniformValue("u_ValueMax", static_cast(sceneRadius())); vao[VAO_FACES].bind(); - glDrawArrays(GL_TRIANGLES, 0, static_cast(m_scene.number_of_elements(GS::POS_FACES))); + const std::vector> &vols=m_scene.get_volume_faces(); + if (m_color_map!=0 && m_color_per_cell && !vols.empty()) + { + // Colour by value, per cell: one flat colour per volume, taken from its + // centre, so neighbouring cells do not melt into one. + rendering_program_face.setUniformValue("u_ColorPerCell", static_cast(1)); + const std::vector &bb=m_scene.get_volume_bboxes(); + for (std::size_t v=0; v &r=m_scene.face_range(fi); + glDrawArrays(GL_TRIANGLES, static_cast(r.first), + static_cast(r.second)); + } + } + } + else + { + rendering_program_face.setUniformValue("u_ColorPerCell", static_cast(0)); + glDrawArrays(GL_TRIANGLES, 0, static_cast(m_scene.number_of_elements(GS::POS_FACES))); + } glDisable(GL_POLYGON_OFFSET_FILL); }; @@ -2312,6 +2338,14 @@ class Basic_viewer : public CGAL::QGLViewer } update(); } + else if ((e->key()==::Qt::Key_D) && (modifiers==::Qt::ShiftModifier)) + { + // Colour by value: one flat colour per cell instead of a smooth gradient, + // so neighbouring cells with a close value do not melt together. + m_color_per_cell=!m_color_per_cell; + displayMessage(QString("Colour by value per cell=%1.").arg(m_color_per_cell?"true":"false")); + update(); + } else if ((e->key()==::Qt::Key_Plus) && (!modifiers.testFlag(::Qt::ControlModifier))) // No ctrl { m_size_edges+=.5; @@ -2568,6 +2602,7 @@ class Basic_viewer : public CGAL::QGLViewer std::vector> m_point_owners; // whole-volume clip: per vertex, owning volumes bool m_clip_owners_valid = false; // whole-volume clip: are the owner lists up to date int m_color_map=0; // colour by value: 0 off, 1 heat, 2 jet, 3 grey ramp + bool m_color_per_cell=false; // colour by value: one flat colour per cell (Shift+D) CGAL::qglviewer::ManipulatedFrame* m_frame_plane=nullptr; // Buffer for clipping plane is not stored in the scene because it is not From fc92e4c5b69450890357de30d6dea9b36ef864eb Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Thu, 6 Aug 2026 14:52:46 +0530 Subject: [PATCH 03/15] Basic_viewer: add cell size as a colour-by-value source --- Basic_viewer/include/CGAL/Basic_shaders.h | 10 +-- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 76 +++++++++++++++++---- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/Basic_viewer/include/CGAL/Basic_shaders.h b/Basic_viewer/include/CGAL/Basic_shaders.h index fbd25dc4000d..d889b9ee3ce4 100644 --- a/Basic_viewer/include/CGAL/Basic_shaders.h +++ b/Basic_viewer/include/CGAL/Basic_shaders.h @@ -81,10 +81,10 @@ uniform mediump float u_RenderingTransparency; uniform mediump float u_ColorMapMode; uniform mediump float u_ValueMin; uniform mediump float u_ValueMax; -// Per cell: use the cell centre instead of the fragment position, so a whole cell -// takes one flat colour and neighbouring cells do not melt together. +// Per cell: the viewer gives one value for the whole cell (its centre distance or +// its size), so a whole cell takes one flat colour and neighbouring cells do not melt. uniform int u_ColorPerCell; -uniform highp vec3 u_CellCentroid; +uniform highp float u_CellValue; vec3 colour_palette(float t, float mode) { @@ -109,8 +109,8 @@ void main(void) vec3 base = fColor.rgb; if (u_ColorMapMode > 0.5) { - vec3 p = (u_ColorPerCell != 0) ? u_CellCentroid : ls_fP.xyz; - float value = dot(p-u_PointPlane.xyz, normalize(u_ClipPlane.xyz)); + float value = (u_ColorPerCell != 0) ? u_CellValue + : dot(ls_fP.xyz-u_PointPlane.xyz, normalize(u_ClipPlane.xyz)); float t = clamp((value-u_ValueMin)/max(u_ValueMax-u_ValueMin, 1e-6), 0.0, 1.0); base = colour_palette(t, u_ColorMapMode); } diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index d38830cb9598..4ecc55c2ab05 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -125,7 +125,7 @@ class Basic_viewer : public CGAL::QGLViewer setKeyDescription(::Qt::Key_V, "Toggles vertices display"); setKeyDescription(::Qt::Key_W, "Toggles faces display"); setKeyDescription(::Qt::Key_D, "Cycle colouring faces by value (distance to the plane)"); - setKeyDescription(::Qt::ShiftModifier, ::Qt::Key_D, "Colour by value: per cell (flat) or smooth"); + setKeyDescription(::Qt::ShiftModifier, ::Qt::Key_D, "Colour by value: distance smooth, distance per cell, size per cell"); setKeyDescription(::Qt::Key_Plus, "Increase size of edges"); setKeyDescription(::Qt::Key_Minus, "Decrease size of edges"); setKeyDescription(::Qt::ControlModifier, ::Qt::Key_Plus, "Increase size of vertices"); @@ -743,18 +743,35 @@ class Basic_viewer : public CGAL::QGLViewer vao[VAO_FACES].bind(); const std::vector> &vols=m_scene.get_volume_faces(); - if (m_color_map!=0 && m_color_per_cell && !vols.empty()) + if (m_color_map!=0 && m_color_value!=0 && !vols.empty()) { - // Colour by value, per cell: one flat colour per volume, taken from its - // centre, so neighbouring cells do not melt into one. + // Per cell: draw each volume with one flat value, so a whole cell takes + // one colour and neighbouring cells do not melt into one. The value is the + // centre's distance to the plane, or the cell size. rendering_program_face.setUniformValue("u_ColorPerCell", static_cast(1)); const std::vector &bb=m_scene.get_volume_bboxes(); + const bool size_mode=(m_color_value==2); + if (size_mode) + { + if (!m_cell_sizes_valid) { compute_cell_sizes(); } + rendering_program_face.setUniformValue("u_ValueMin", static_cast(m_cell_size_min)); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(m_cell_size_max)); + } + const QVector3D n=QVector3D(clipPlane).normalized(); + const QVector3D pt=plane_point.toVector3D(); for (std::size_t v=0; v(value)); for (unsigned int fi : vols[v]) { const std::pair &r=m_scene.face_range(fi); @@ -1746,9 +1763,30 @@ class Basic_viewer : public CGAL::QGLViewer } } + // Colour by value (size): one size per cell, the bounding-box volume, with the + // range over all cells so the palette spans from the smallest to the largest. + void compute_cell_sizes() + { + const std::vector &bb=m_scene.get_volume_bboxes(); + m_cell_sizes.resize(bb.size()); + m_cell_size_min=(std::numeric_limits::max)(); + m_cell_size_max=0.f; + for (std::size_t v=0; vm_cell_size_max) { m_cell_size_max=s; } + } + if (bb.empty()) { m_cell_size_min=0.f; m_cell_size_max=1.f; } + m_cell_sizes_valid=true; + } + void initialize_buffers() { set_camera_mode(); + m_cell_sizes_valid=false; // colour by value: the scene may have changed rendering_program_p_l.bind(); unsigned int bufn = 0; @@ -2340,10 +2378,17 @@ class Basic_viewer : public CGAL::QGLViewer } else if ((e->key()==::Qt::Key_D) && (modifiers==::Qt::ShiftModifier)) { - // Colour by value: one flat colour per cell instead of a smooth gradient, - // so neighbouring cells with a close value do not melt together. - m_color_per_cell=!m_color_per_cell; - displayMessage(QString("Colour by value per cell=%1.").arg(m_color_per_cell?"true":"false")); + // Colour by value: pick the value and how it is shown. Distance to the plane + // as a smooth gradient, the same distance as one flat colour per cell, then + // the cell size as one flat colour per cell. + m_color_value=(m_color_value+1)%3; + switch(m_color_value) + { + case 0: displayMessage(QString("Colour by value = distance (smooth)")); break; + case 1: displayMessage(QString("Colour by value = distance (per cell)")); break; + case 2: displayMessage(QString("Colour by value = size (per cell)")); break; + default: break; + } update(); } else if ((e->key()==::Qt::Key_Plus) && (!modifiers.testFlag(::Qt::ControlModifier))) // No ctrl @@ -2602,7 +2647,12 @@ class Basic_viewer : public CGAL::QGLViewer std::vector> m_point_owners; // whole-volume clip: per vertex, owning volumes bool m_clip_owners_valid = false; // whole-volume clip: are the owner lists up to date int m_color_map=0; // colour by value: 0 off, 1 heat, 2 jet, 3 grey ramp - bool m_color_per_cell=false; // colour by value: one flat colour per cell (Shift+D) + int m_color_value=0; // colour by value source (Shift+D): 0 distance smooth, + // 1 distance per cell, 2 size per cell + std::vector m_cell_sizes; // colour by value: per-cell size (bbox volume) + float m_cell_size_min=0.f; // colour by value: size range for the palette + float m_cell_size_max=1.f; + bool m_cell_sizes_valid=false; // colour by value: recompute the sizes on scene change CGAL::qglviewer::ManipulatedFrame* m_frame_plane=nullptr; // Buffer for clipping plane is not stored in the scene because it is not From 4b57ba9db55ec5a3bf25f7191d49c96e381a9b9d Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Thu, 6 Aug 2026 20:45:32 +0530 Subject: [PATCH 04/15] Basic_viewer: colour the kept volumes in whole-volume clipping --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index 4ecc55c2ab05..72950aa002b3 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -988,6 +988,22 @@ class Basic_viewer : public CGAL::QGLViewer clipping_plane_rendering_transparency); rendering_program_face.setUniformValue("u_ClipPlane", clipPlane); rendering_program_face.setUniformValue("u_PointPlane", plane_point); + // Colour by value: the kept volumes follow the same colour map as the other + // face modes, per fragment or one flat value per cell. + rendering_program_face.setUniformValue("u_ColorMapMode", static_cast(m_color_map)); + rendering_program_face.setUniformValue("u_ValueMin", static_cast(-sceneRadius())); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(sceneRadius())); + const bool per_cell=(m_color_map!=0 && m_color_value!=0 && num_volumes!=0); + const bool size_mode=(m_color_value==2); + if (per_cell && size_mode) + { + if (!m_cell_sizes_valid) { compute_cell_sizes(); } + rendering_program_face.setUniformValue("u_ValueMin", static_cast(m_cell_size_min)); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(m_cell_size_max)); + } + rendering_program_face.setUniformValue("u_ColorPerCell", static_cast(per_cell?1:0)); + const QVector3D n=QVector3D(clipPlane).normalized(); + const QVector3D pt=plane_point.toVector3D(); vao[VAO_FACES].bind(); if (num_volumes == 0) @@ -997,9 +1013,24 @@ class Basic_viewer : public CGAL::QGLViewer } else { + const std::vector &bb = m_scene.get_volume_bboxes(); for (std::size_t v = 0; v < num_volumes; ++v) { if (!m_volumes_kept[v]) { continue; } + if (per_cell) + { + float value; + if (size_mode) { value=m_cell_sizes[v]; } + else + { + const CGAL::Bbox_3 &b=bb[v]; + const QVector3D c(float((b.xmin()+b.xmax())*0.5), + float((b.ymin()+b.ymax())*0.5), + float((b.zmin()+b.zmax())*0.5)); + value=QVector3D::dotProduct(c-pt, n); + } + rendering_program_face.setUniformValue("u_CellValue", static_cast(value)); + } for (unsigned int fi : volumes[v]) { const std::pair &r = m_scene.face_range(fi); From dc20ab223bdff9627523a722f30c50c59e32b66a Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Wed, 12 Aug 2026 04:54:59 +0530 Subject: [PATCH 05/15] Basic_viewer: add a colour legend for colour by value --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index 72950aa002b3..a5d7de0ed7c3 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -1159,6 +1160,10 @@ class Basic_viewer : public CGAL::QGLViewer glEnable(GL_LIGHTING); } + // Colour by value: show the palette and the value range as a small legend. + if (m_color_map!=0) + { draw_color_legend(); } + // Multiply matrix to get in the frame coordinate system. // glMultMatrixd(manipulatedFrame()->matrix()); // Linker error // Scale down the drawings @@ -1814,6 +1819,47 @@ class Basic_viewer : public CGAL::QGLViewer m_cell_sizes_valid=true; } + // Colour by value: the palette as a QColor, matching colour_palette() in the + // shader, so the legend bar shows the same colours as the faces. + QColor legend_palette_color(float t) const + { + auto cl=[](float x){ return x<0.f ? 0.f : (x>1.f ? 1.f : x); }; + float r, g, b; + if (m_color_map<2) { r=cl(t*3.f); g=cl(t*3.f-1.f); b=cl(t*3.f-2.f); } // heat + else if (m_color_map<3) { r=cl(1.5f-std::abs(4.f*t-3.f)); // jet + g=cl(1.5f-std::abs(4.f*t-2.f)); + b=cl(1.5f-std::abs(4.f*t-1.f)); } + else { r=g=b=cl(t); } // grey ramp + return QColor(int(r*255.f), int(g*255.f), int(b*255.f)); + } + + // Colour by value: draw a small legend, a gradient bar with the value range, so + // the colours read as numbers. The range and label match the current value. + void draw_color_legend() + { + const bool size_mode=(m_color_value==2 && !m_scene.get_volume_faces().empty()); + double vmin, vmax; + if (size_mode) + { if (!m_cell_sizes_valid) { compute_cell_sizes(); } + vmin=m_cell_size_min; vmax=m_cell_size_max; } + else { vmin=-sceneRadius(); vmax=sceneRadius(); } + + const int barW=16, barH=150; + const int x=width()-barW-70, y=height()-barH-30; + QPainter painter(this); + for (int i=0; i Date: Fri, 14 Aug 2026 13:49:21 +0530 Subject: [PATCH 06/15] Basic_viewer: add a viridis palette for colour by value --- Basic_viewer/include/CGAL/Basic_shaders.h | 13 +++++++++- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 28 +++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/Basic_viewer/include/CGAL/Basic_shaders.h b/Basic_viewer/include/CGAL/Basic_shaders.h index d889b9ee3ce4..8199971c881c 100644 --- a/Basic_viewer/include/CGAL/Basic_shaders.h +++ b/Basic_viewer/include/CGAL/Basic_shaders.h @@ -93,7 +93,18 @@ vec3 colour_palette(float t, float mode) if (mode < 2.5) { return clamp(vec3(1.5-abs(4.0*t-3.0), 1.5-abs(4.0*t-2.0), 1.5-abs(4.0*t-1.0)), 0.0, 1.0); } // jet - return vec3(t); // grey ramp + if (mode < 3.5) + { return vec3(t); } // grey ramp + // viridis, a perceptually uniform map (polynomial fit by Matt Zucker). The same + // coefficients are mirrored in the viewer's legend so the bar matches the faces. + const vec3 c0=vec3(0.2777273272234177, 0.005407344544966578, 0.3340998053353061); + const vec3 c1=vec3(0.1050930431085774, 1.404613529898575, 1.384590162594685); + const vec3 c2=vec3(-0.3308618287255563, 0.214847559468213, 0.09509516302823659); + const vec3 c3=vec3(-4.634230498983486, -5.799100973351585, -19.33244095627987); + const vec3 c4=vec3(6.228269936347081, 14.17993336680509, 56.69055260068105); + const vec3 c5=vec3(4.776384997670288, -13.74514537774601, -65.35303263337234); + const vec3 c6=vec3(-5.435455855934631, 4.645852612178535, 26.3124352495832); + return clamp(c0+t*(c1+t*(c2+t*(c3+t*(c4+t*(c5+t*c6))))), 0.0, 1.0); } void main(void) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index a5d7de0ed7c3..5c70dbb7f432 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -1829,7 +1829,24 @@ class Basic_viewer : public CGAL::QGLViewer else if (m_color_map<3) { r=cl(1.5f-std::abs(4.f*t-3.f)); // jet g=cl(1.5f-std::abs(4.f*t-2.f)); b=cl(1.5f-std::abs(4.f*t-1.f)); } - else { r=g=b=cl(t); } // grey ramp + else if (m_color_map<4) { r=g=b=cl(t); } // grey ramp + else + { // viridis, the same coefficients as colour_palette() in Basic_shaders.h + static const float C[7][3]={ + { 0.277727f, 0.005407f, 0.334100f}, + { 0.105093f, 1.404614f, 1.384590f}, + {-0.330862f, 0.214848f, 0.095095f}, + {-4.634230f, -5.799101f, -19.332441f}, + { 6.228270f, 14.179933f, 56.690553f}, + { 4.776385f,-13.745145f, -65.353033f}, + {-5.435456f, 4.645853f, 26.312435f}}; + float rgb[3]; + for (int k=0; k<3; ++k) + { float v=C[6][k]; + for (int j=5; j>=0; --j) { v=C[j][k]+t*v; } + rgb[k]=cl(v); } + r=rgb[0]; g=rgb[1]; b=rgb[2]; + } return QColor(int(r*255.f), int(g*255.f), int(b*255.f)); } @@ -2442,13 +2459,14 @@ class Basic_viewer : public CGAL::QGLViewer { // Colour the faces by a value (here the distance to the clipping plane): // off, then the heat, jet and grey palettes. - m_color_map=(m_color_map+1)%4; + m_color_map=(m_color_map+1)%5; switch(m_color_map) { case 0: displayMessage(QString("Colour by value = off")); break; - case 1: displayMessage(QString("Colour by value = heat (distance to plane)")); break; - case 2: displayMessage(QString("Colour by value = jet (distance to plane)")); break; - case 3: displayMessage(QString("Colour by value = grey (distance to plane)")); break; + case 1: displayMessage(QString("Colour by value = heat")); break; + case 2: displayMessage(QString("Colour by value = jet")); break; + case 3: displayMessage(QString("Colour by value = grey")); break; + case 4: displayMessage(QString("Colour by value = viridis")); break; default: break; } update(); From 433bb9fffd24b387ce8fa8289790d0fe181263fd Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Fri, 14 Aug 2026 21:02:59 +0530 Subject: [PATCH 07/15] Basic_viewer: colour by distance over the actual range, not the scene radius --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 40 +++++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index 5c70dbb7f432..beae77d04530 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -739,8 +739,9 @@ class Basic_viewer : public CGAL::QGLViewer // Colour by value: the value is the distance to the clipping plane, scaled // by the scene radius each side of it. rendering_program_face.setUniformValue("u_ColorMapMode", static_cast(m_color_map)); - rendering_program_face.setUniformValue("u_ValueMin", static_cast(-sceneRadius())); - rendering_program_face.setUniformValue("u_ValueMax", static_cast(sceneRadius())); + { double dvmin, dvmax; distance_value_range(clipPlane, plane_point, dvmin, dvmax); + rendering_program_face.setUniformValue("u_ValueMin", static_cast(dvmin)); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(dvmax)); } vao[VAO_FACES].bind(); const std::vector> &vols=m_scene.get_volume_faces(); @@ -992,8 +993,9 @@ class Basic_viewer : public CGAL::QGLViewer // Colour by value: the kept volumes follow the same colour map as the other // face modes, per fragment or one flat value per cell. rendering_program_face.setUniformValue("u_ColorMapMode", static_cast(m_color_map)); - rendering_program_face.setUniformValue("u_ValueMin", static_cast(-sceneRadius())); - rendering_program_face.setUniformValue("u_ValueMax", static_cast(sceneRadius())); + { double dvmin, dvmax; distance_value_range(clipPlane, plane_point, dvmin, dvmax); + rendering_program_face.setUniformValue("u_ValueMin", static_cast(dvmin)); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(dvmax)); } const bool per_cell=(m_color_map!=0 && m_color_value!=0 && num_volumes!=0); const bool size_mode=(m_color_value==2); if (per_cell && size_mode) @@ -1162,7 +1164,7 @@ class Basic_viewer : public CGAL::QGLViewer // Colour by value: show the palette and the value range as a small legend. if (m_color_map!=0) - { draw_color_legend(); } + { draw_color_legend(clipPlane, plane_point); } // Multiply matrix to get in the frame coordinate system. // glMultMatrixd(manipulatedFrame()->matrix()); // Linker error @@ -1850,16 +1852,40 @@ class Basic_viewer : public CGAL::QGLViewer return QColor(int(r*255.f), int(g*255.f), int(b*255.f)); } + // Colour by value (distance): the actual signed-distance range the geometry spans + // along the plane normal, from the scene bounding-box corners, so the palette and + // the legend cover the values really present rather than the whole scene radius + // (which left the colours bunched in the middle of the ramp). + void distance_value_range(const QVector4D &clipPlane, const QVector4D &plane_point, + double &vmin, double &vmax) + { + const CGAL::Bbox_3 b=m_scene.bounding_box(); + const QVector3D n=QVector3D(clipPlane).normalized(); + const QVector3D pt=plane_point.toVector3D(); + vmin=(std::numeric_limits::max)(); + vmax=-(std::numeric_limits::max)(); + for (int c=0; c<8; ++c) + { + const QVector3D corner(float((c&1) ? b.xmax() : b.xmin()), + float((c&2) ? b.ymax() : b.ymin()), + float((c&4) ? b.zmax() : b.zmin())); + const double d=QVector3D::dotProduct(corner-pt, n); + if (dvmax) { vmax=d; } + } + if (vmax-vmin<1e-6) { vmax=vmin+1.0; } // guard a degenerate (flat) range + } + // Colour by value: draw a small legend, a gradient bar with the value range, so // the colours read as numbers. The range and label match the current value. - void draw_color_legend() + void draw_color_legend(const QVector4D &clipPlane, const QVector4D &plane_point) { const bool size_mode=(m_color_value==2 && !m_scene.get_volume_faces().empty()); double vmin, vmax; if (size_mode) { if (!m_cell_sizes_valid) { compute_cell_sizes(); } vmin=m_cell_size_min; vmax=m_cell_size_max; } - else { vmin=-sceneRadius(); vmax=sceneRadius(); } + else { distance_value_range(clipPlane, plane_point, vmin, vmax); } const int barW=16, barH=150; const int x=width()-barW-70, y=height()-barH-30; From 5d3beea4473957569ed4907b28354f00b25f2381 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Sat, 15 Aug 2026 19:16:34 +0530 Subject: [PATCH 08/15] Basic_viewer: colour faces by a value provided by the drawer (surface mesh aspect ratio) --- BGL/include/CGAL/draw_face_graph.h | 6 ++ Basic_viewer/include/CGAL/Graphics_scene.h | 40 +++++++++++++ .../include/CGAL/Graphics_scene_options.h | 8 +++ Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 56 +++++++++++++++---- Surface_mesh/include/CGAL/draw_surface_mesh.h | 20 +++++++ 5 files changed, 119 insertions(+), 11 deletions(-) diff --git a/BGL/include/CGAL/draw_face_graph.h b/BGL/include/CGAL/draw_face_graph.h index 11c1da1d6188..ed22b853397c 100644 --- a/BGL/include/CGAL/draw_face_graph.h +++ b/BGL/include/CGAL/draw_face_graph.h @@ -60,6 +60,9 @@ void compute_elements(const FG &fg, if (gs_options.are_faces_enabled()) { + // Colour by value: name the value once, for the legend. + if (!gs_options.face_value_name.empty()) + { graphics_scene.set_value_name(gs_options.face_value_name); } for (auto fh : faces(fg)) { if (fh != boost::graph_traits::null_face() && // face exists @@ -78,6 +81,9 @@ void compute_elements(const FG &fg, hd = next(hd, fg); } while (hd != first_hd); + // Colour by value: attach the face's scalar value before committing it. + if (gs_options.valued_face(fg, fh)) + { graphics_scene.set_face_value(gs_options.face_value(fg, fh)); } graphics_scene.face_end(); } } diff --git a/Basic_viewer/include/CGAL/Graphics_scene.h b/Basic_viewer/include/CGAL/Graphics_scene.h index cd2331d17b11..79b5bc6dfaec 100644 --- a/Basic_viewer/include/CGAL/Graphics_scene.h +++ b/Basic_viewer/include/CGAL/Graphics_scene.h @@ -252,6 +252,11 @@ class Graphics_scene return m_buffer_for_faces.is_a_face_started(); } + // Colour by value: set the scalar value of the face about to be built, and the + // legend name for the value. The viewer colours the faces by these when asked. + void set_face_value(float v) { m_current_face_value=v; m_has_face_values=true; } + void set_value_name(const std::string &n) { m_value_name=n; } + void face_begin() { if (a_face_started()) @@ -314,6 +319,7 @@ class Graphics_scene const unsigned int idx=static_cast(m_faces.size()); m_faces.emplace_back(m_current_face_start, number_of_elements(POS_FACES)-m_current_face_start); + record_current_face_value(); m_face_dedup.emplace(std::move(key), idx); m_volume_faces.back().push_back(idx); return; @@ -324,6 +330,22 @@ class Graphics_scene // Record this face's vertex range in POS_FACES, for the clip-plane cap. m_faces.emplace_back(m_current_face_start, number_of_elements(POS_FACES) - m_current_face_start); + record_current_face_value(); + } + + // Colour by value: store the value of the face just committed, parallel to + // m_faces, and keep the min and max for the palette range. + void record_current_face_value() + { + if (m_has_face_values) + { + if (m_face_values.empty()) + { m_face_value_min=m_face_value_max=m_current_face_value; } + else + { if (m_current_face_valuem_face_value_max) { m_face_value_max=m_current_face_value; } } + } + m_face_values.push_back(m_current_face_value); } // Clip-plane cap: a volume groups the faces added until volume_end, de-duplicated @@ -366,6 +388,14 @@ class Graphics_scene const std::vector &get_volume_bboxes() const { return m_volume_bboxes; } + // Colour by value: the per-face values set by the drawer, whether any were set, + // the legend name, and the value range. + const std::vector &get_face_values() const { return m_face_values; } + bool has_face_values() const { return m_has_face_values; } + const std::string &value_name() const { return m_value_name; } + float face_value_min() const { return m_face_value_min; } + float face_value_max() const { return m_face_value_max; } + template void add_text(const KPoint &kp, const std::string &txt) { @@ -509,6 +539,16 @@ class Graphics_scene std::vector m_volume_bboxes; unsigned int m_current_face_start = 0; + // Colour by value: an optional scalar per face, set by the drawer, that the viewer + // maps to a palette (like the colour, but any float). Each value is parallel to + // m_faces; m_value_name labels the legend. + std::vector m_face_values; + float m_current_face_value = 0.f; + bool m_has_face_values = false; + std::string m_value_name; + float m_face_value_min = 0.f; + float m_face_value_max = 1.f; + // Clip-plane cap: geometric face de-duplication during volume building. The key // is the sorted face vertex positions, so both sides of a shared wall match. // Hashed so the build stays linear on meshes with many faces. diff --git a/Basic_viewer/include/CGAL/Graphics_scene_options.h b/Basic_viewer/include/CGAL/Graphics_scene_options.h index b1cefd56df24..cbcab9153724 100644 --- a/Basic_viewer/include/CGAL/Graphics_scene_options.h +++ b/Basic_viewer/include/CGAL/Graphics_scene_options.h @@ -52,6 +52,8 @@ struct Graphics_scene_optionsbool { return false; }; face_wireframe=[](const DS &, face_descriptor)->bool { return false; }; + + valued_face=[](const DS &, face_descriptor)->bool { return false; }; } // The seven following functions should not be null @@ -65,6 +67,12 @@ struct Graphics_scene_options face_wireframe; + // Colour by value: valued_face marks a face that carries a scalar value, + // face_value returns it, and face_value_name labels the legend. + std::function valued_face; + std::function face_value; + std::string face_value_name; + // These functions must be non null if the corresponding colored_XXX function // returns true. std::function vertex_color; diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index beae77d04530..fc7f714d9b69 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -745,7 +745,24 @@ class Basic_viewer : public CGAL::QGLViewer vao[VAO_FACES].bind(); const std::vector> &vols=m_scene.get_volume_faces(); - if (m_color_map!=0 && m_color_value!=0 && !vols.empty()) + if (m_color_map!=0 && m_color_value==3 && m_scene.has_face_values()) + { + // User value: one flat value per face, provided by the drawer (for example + // the aspect ratio of the face), mapped to the palette. + rendering_program_face.setUniformValue("u_ColorPerCell", static_cast(1)); + rendering_program_face.setUniformValue("u_ValueMin", static_cast(m_scene.face_value_min())); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(m_scene.face_value_max())); + const std::vector &fvals=m_scene.get_face_values(); + const unsigned int nf=m_scene.number_of_faces(); + for (unsigned int f=0; f(fvals[f])); + const std::pair &r=m_scene.face_range(f); + glDrawArrays(GL_TRIANGLES, static_cast(r.first), + static_cast(r.second)); + } + } + else if (m_color_map!=0 && (m_color_value==1 || m_color_value==2) && !vols.empty()) { // Per cell: draw each volume with one flat value, so a whole cell takes // one colour and neighbouring cells do not melt into one. The value is the @@ -1873,19 +1890,27 @@ class Basic_viewer : public CGAL::QGLViewer if (dvmax) { vmax=d; } } - if (vmax-vmin<1e-6) { vmax=vmin+1.0; } // guard a degenerate (flat) range } // Colour by value: draw a small legend, a gradient bar with the value range, so // the colours read as numbers. The range and label match the current value. void draw_color_legend(const QVector4D &clipPlane, const QVector4D &plane_point) { - const bool size_mode=(m_color_value==2 && !m_scene.get_volume_faces().empty()); double vmin, vmax; - if (size_mode) + QString label; + if (m_color_value==3 && m_scene.has_face_values()) + { vmin=m_scene.face_value_min(); vmax=m_scene.face_value_max(); + label=QString(m_scene.value_name().c_str()); } + else if (m_color_value==2 && !m_scene.get_volume_faces().empty()) { if (!m_cell_sizes_valid) { compute_cell_sizes(); } - vmin=m_cell_size_min; vmax=m_cell_size_max; } - else { distance_value_range(clipPlane, plane_point, vmin, vmax); } + vmin=m_cell_size_min; vmax=m_cell_size_max; label=QString("size"); } + else + { distance_value_range(clipPlane, plane_point, vmin, vmax); label=QString("distance"); } + + // No range to map (a uniform value, e.g. a flat mesh with a parallel plane): + // skip the legend rather than show a misleading full gradient. The threshold is + // relative to the value magnitude, so it holds at any scale. + if (vmax-vmin<=1e-6*(std::fabs(vmin)+std::fabs(vmax))) { return; } const int barW=16, barH=150; const int x=width()-barW-70, y=height()-barH-30; @@ -1897,7 +1922,7 @@ class Basic_viewer : public CGAL::QGLViewer } painter.setPen(::Qt::black); painter.drawRect(x, y, barW, barH); - painter.drawText(x-2, y-8, QString(size_mode ? "size" : "distance")); + painter.drawText(x-2, y-8, label); painter.drawText(x+barW+5, y+11, QString::number(vmax, 'g', 3)); painter.drawText(x+barW+5, y+barH, QString::number(vmin, 'g', 3)); painter.end(); @@ -2499,15 +2524,24 @@ class Basic_viewer : public CGAL::QGLViewer } else if ((e->key()==::Qt::Key_D) && (modifiers==::Qt::ShiftModifier)) { - // Colour by value: pick the value and how it is shown. Distance to the plane - // as a smooth gradient, the same distance as one flat colour per cell, then - // the cell size as one flat colour per cell. - m_color_value=(m_color_value+1)%3; + // Colour by value: pick the value and how it is shown. Skip the modes that + // do not apply to the current scene: the per-cell and size modes need + // volumes, and the user value needs values set by the drawer. This keeps the + // message, the faces and the legend in agreement. + const bool has_vols=!m_scene.get_volume_faces().empty(); + const bool has_vals=m_scene.has_face_values(); + for (int step=1; step<=4; ++step) + { + const int m=(m_color_value+step)%4; + if (m==0 || ((m==1 || m==2) && has_vols) || (m==3 && has_vals)) + { m_color_value=m; break; } + } switch(m_color_value) { case 0: displayMessage(QString("Colour by value = distance (smooth)")); break; case 1: displayMessage(QString("Colour by value = distance (per cell)")); break; case 2: displayMessage(QString("Colour by value = size (per cell)")); break; + case 3: displayMessage(QString("Colour by value = %1 (per face)").arg(m_scene.value_name().c_str())); break; default: break; } update(); diff --git a/Surface_mesh/include/CGAL/draw_surface_mesh.h b/Surface_mesh/include/CGAL/draw_surface_mesh.h index 1f1736dc37c6..76121f7bfa74 100644 --- a/Surface_mesh/include/CGAL/draw_surface_mesh.h +++ b/Surface_mesh/include/CGAL/draw_surface_mesh.h @@ -144,6 +144,26 @@ struct Graphics_scene_options_surface_mesh } else { this->colored_face=[](const SM &, face_descriptor)->bool { return false; }; } + + // Colour by value: expose the aspect ratio (longest edge / shortest edge) of + // each face, so the viewer can colour the mesh by it (Shift+D). + this->face_value_name="aspect ratio"; + this->valued_face=[](const SM &, face_descriptor)->bool { return true; }; + this->face_value=[](const SM &sm, face_descriptor f)->float + { + double l2min=(std::numeric_limits::max)(), l2max=0.0; + auto h=halfedge(f, sm); const auto first=h; + do + { + const auto vec=sm.point(target(h, sm))-sm.point(source(h, sm)); + const double l2=CGAL::to_double(vec.squared_length()); + if (l2l2max) { l2max=l2; } + h=next(h, sm); + } + while (h!=first); + return (l2min>0.0) ? static_cast(std::sqrt(l2max/l2min)) : 1.f; + }; } private: From 163d647aa3c28987c3631407a37627ffefd54942 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Mon, 17 Aug 2026 05:44:27 +0530 Subject: [PATCH 09/15] Basic_viewer: colour the clip-plane cap by the palette, and clarify the distance legend --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 37 ++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index fc7f714d9b69..4a2b2fbdf835 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -910,6 +910,12 @@ class Basic_viewer : public CGAL::QGLViewer QVector4D capcol; if (num_volumes == 0) { capcol = QVector4D(0.6f, 0.6f, 0.6f, 1.0f); } + else if (m_color_map!=0) + { // Colour by value: cap follows the palette, like the volume's faces. + const QColor cc=volume_value_color(v, clipPlane, plane_point); + capcol = QVector4D(float(cc.redF()), float(cc.greenF()), + float(cc.blueF()), 1.0f); + } else { const CGAL::IO::Color &c = vcolors[v]; @@ -1892,6 +1898,35 @@ class Basic_viewer : public CGAL::QGLViewer } } + // Colour by value: the palette colour for a volume's clip-plane cap. The cap faces + // carry no value of their own, so the cap takes the value the volume shows: its + // size, its centre distance, or the mean of its per-face values. + QColor volume_value_color(std::size_t v, const QVector4D &clipPlane, + const QVector4D &plane_point) + { + const std::vector> &vols=m_scene.get_volume_faces(); + double vmin, vmax, value; + if (m_color_value==3 && m_scene.has_face_values()) + { vmin=m_scene.face_value_min(); vmax=m_scene.face_value_max(); + const std::vector &fv=m_scene.get_face_values(); + double sum=0.0; std::size_t n=0; + for (unsigned int fi : vols[v]) { if (fi0) ? sum/double(n) : vmin; } + else if (m_color_value==2) + { if (!m_cell_sizes_valid) { compute_cell_sizes(); } + vmin=m_cell_size_min; vmax=m_cell_size_max; value=m_cell_sizes[v]; } + else + { distance_value_range(clipPlane, plane_point, vmin, vmax); + const CGAL::Bbox_3 &b=m_scene.get_volume_bboxes()[v]; + const QVector3D c(float((b.xmin()+b.xmax())*0.5), float((b.ymin()+b.ymax())*0.5), + float((b.zmin()+b.zmax())*0.5)); + value=QVector3D::dotProduct(c-plane_point.toVector3D(), + QVector3D(clipPlane).normalized()); } + double t=(vmax-vmin>1e-12) ? (value-vmin)/(vmax-vmin) : 0.0; + t=(t<0.0) ? 0.0 : (t>1.0 ? 1.0 : t); + return legend_palette_color(float(t)); + } + // Colour by value: draw a small legend, a gradient bar with the value range, so // the colours read as numbers. The range and label match the current value. void draw_color_legend(const QVector4D &clipPlane, const QVector4D &plane_point) @@ -1905,7 +1940,7 @@ class Basic_viewer : public CGAL::QGLViewer { if (!m_cell_sizes_valid) { compute_cell_sizes(); } vmin=m_cell_size_min; vmax=m_cell_size_max; label=QString("size"); } else - { distance_value_range(clipPlane, plane_point, vmin, vmax); label=QString("distance"); } + { distance_value_range(clipPlane, plane_point, vmin, vmax); label=QString("distance to plane"); } // No range to map (a uniform value, e.g. a flat mesh with a parallel plane): // skip the legend rather than show a misleading full gradient. The threshold is From 5db58d4df35c2a308243e79ad1cc0a966aacf0dd Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Mon, 17 Aug 2026 05:48:32 +0530 Subject: [PATCH 10/15] Basic_viewer: document the colour-by-value drawer API (valued_face, face_value, set_face_value) --- .../doc/Basic_viewer/Concepts/GraphicsSceneOptions.h | 12 ++++++++++++ Basic_viewer/include/CGAL/Graphics_scene.h | 6 ++++-- Basic_viewer/include/CGAL/Graphics_scene_options.h | 11 +++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h b/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h index f845dc792813..1780f6971760 100644 --- a/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h +++ b/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h @@ -75,6 +75,18 @@ class GraphicsSceneOptions /// `nullptr` by default. std::function face_color; + /// `std::function` that returns `true` if the given face carries a scalar value to + /// colour it by, `false` otherwise. `false` by default. + std::function valued_face; + + /// `std::function` that returns the scalar value of the given face. Used only when + /// `valued_face()` returns `true`. The viewer normalises the values over their range + /// and maps them to a color palette. + std::function face_value; + + /// name of the value, shown in the viewer's color legend (for example "aspect ratio"). + std::string face_value_name; + /// ignores all vertices when `b` is `true`; otherwise ignores only vertices for which `ignore_vertex()` returns `true`. void ignore_all_vertices(bool b); diff --git a/Basic_viewer/include/CGAL/Graphics_scene.h b/Basic_viewer/include/CGAL/Graphics_scene.h index 79b5bc6dfaec..a7a58dfd2661 100644 --- a/Basic_viewer/include/CGAL/Graphics_scene.h +++ b/Basic_viewer/include/CGAL/Graphics_scene.h @@ -252,9 +252,11 @@ class Graphics_scene return m_buffer_for_faces.is_a_face_started(); } - // Colour by value: set the scalar value of the face about to be built, and the - // legend name for the value. The viewer colours the faces by these when asked. + /// sets the scalar value of the face currently being built. The viewer can colour + /// the faces by these values, normalised over their range and mapped to a palette. void set_face_value(float v) { m_current_face_value=v; m_has_face_values=true; } + + /// sets the name of the value, shown in the viewer's colour legend. void set_value_name(const std::string &n) { m_value_name=n; } void face_begin() diff --git a/Basic_viewer/include/CGAL/Graphics_scene_options.h b/Basic_viewer/include/CGAL/Graphics_scene_options.h index cbcab9153724..709f109b0c10 100644 --- a/Basic_viewer/include/CGAL/Graphics_scene_options.h +++ b/Basic_viewer/include/CGAL/Graphics_scene_options.h @@ -67,10 +67,17 @@ struct Graphics_scene_options face_wireframe; - // Colour by value: valued_face marks a face that carries a scalar value, - // face_value returns it, and face_value_name labels the legend. + /// `std::function` that returns `true` if the given face carries a scalar value to + /// colour it by, `false` otherwise. `false` by default. std::function valued_face; + + /// `std::function` that returns the scalar value of the given face. Called only + /// when `valued_face()` returns `true`. The viewer normalises the values over their + /// range and maps them to a colour palette. std::function face_value; + + /// The name of the value, shown in the viewer's colour legend (for example + /// "aspect ratio"). Empty by default. std::string face_value_name; // These functions must be non null if the corresponding colored_XXX function From 7820923a97842795c31ab1abcda6994bf0527103 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Mon, 17 Aug 2026 06:11:14 +0530 Subject: [PATCH 11/15] Basic_viewer: add a changelog entry for colour by value --- Installation/CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 22255b8075ad..379f75135bb4 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -34,6 +34,13 @@ Release date: December 2026 handle cases when some identical faces are shared between the input meshes. This leads to a significant speed up in those cases. +### [Basic Viewer](https://doc.cgal.org/6.3/Manual/packages.html#PkgBasicViewer) + +- Added the possibility to colour the faces by a value mapped to a colour palette: the + distance to the clipping plane, the cell size, or a scalar value provided by the drawer. + A drawer can attach a value to each face through the new `Graphics_scene_options` functions + `valued_face` and `face_value` (with `face_value_name` for the legend). As an example, the + surface mesh drawer exposes the aspect ratio of each face. ## [Release 6.2](https://github.com/CGAL/cgal/releases/tag/v6.2) From 3164f46f0773d52bd7040905c3df2dbbe4aded9b Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Mon, 17 Aug 2026 06:36:00 +0530 Subject: [PATCH 12/15] Basic_viewer: use the full 'distance to clipping plane' legend label, right-aligned so it fits --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index 4a2b2fbdf835..5c90f50ed525 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -1940,7 +1940,7 @@ class Basic_viewer : public CGAL::QGLViewer { if (!m_cell_sizes_valid) { compute_cell_sizes(); } vmin=m_cell_size_min; vmax=m_cell_size_max; label=QString("size"); } else - { distance_value_range(clipPlane, plane_point, vmin, vmax); label=QString("distance to plane"); } + { distance_value_range(clipPlane, plane_point, vmin, vmax); label=QString("distance to clipping plane"); } // No range to map (a uniform value, e.g. a flat mesh with a parallel plane): // skip the legend rather than show a misleading full gradient. The threshold is @@ -1957,7 +1957,10 @@ class Basic_viewer : public CGAL::QGLViewer } painter.setPen(::Qt::black); painter.drawRect(x, y, barW, barH); - painter.drawText(x-2, y-8, label); + // Right-align the label so a long name grows to the left, into empty space, + // instead of running off the right edge. + painter.drawText(QRect(0, y-22, x+barW+40, 16), + ::Qt::AlignRight | ::Qt::AlignVCenter, label); painter.drawText(x+barW+5, y+11, QString::number(vmax, 'g', 3)); painter.drawText(x+barW+5, y+barH, QString::number(vmin, 'g', 3)); painter.end(); From 7862c7babab2f8a74d7597a9cc27c8e8ed3ef3f0 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Mon, 17 Aug 2026 22:06:47 +0530 Subject: [PATCH 13/15] Basic_viewer: apply the drawer value in whole-volume mode for a surface mesh In whole-volume clipping the surface-mesh path (no volumes to keep or hide) drew every face with a plain glDrawArrays, so it ignored the drawer's per-face value: the colour stayed on distance and did not change when switching to the aspect ratio, and did not update when the plane moved. Draw that path the same way the main renderer does, per face with u_CellValue, so the drawer value applies here too. --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index 5c90f50ed525..5e450d78a812 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -1034,8 +1034,28 @@ class Basic_viewer : public CGAL::QGLViewer vao[VAO_FACES].bind(); if (num_volumes == 0) { - glDrawArrays(GL_TRIANGLES, 0, static_cast( - m_scene.number_of_elements(GS::POS_FACES))); + // No volumes (a surface mesh, for example): there is nothing to clip whole, + // so colour all faces by value, including the drawer's per-face value. + if (m_color_map!=0 && m_color_value==3 && m_scene.has_face_values()) + { + rendering_program_face.setUniformValue("u_ColorPerCell", static_cast(1)); + rendering_program_face.setUniformValue("u_ValueMin", static_cast(m_scene.face_value_min())); + rendering_program_face.setUniformValue("u_ValueMax", static_cast(m_scene.face_value_max())); + const std::vector &fvals=m_scene.get_face_values(); + const unsigned int nf=m_scene.number_of_faces(); + for (unsigned int f=0; f(fvals[f])); + const std::pair &r=m_scene.face_range(f); + glDrawArrays(GL_TRIANGLES, static_cast(r.first), + static_cast(r.second)); + } + } + else + { + glDrawArrays(GL_TRIANGLES, 0, static_cast( + m_scene.number_of_elements(GS::POS_FACES))); + } } else { From 9349827c9018693e668422894e94454a00c32ccb Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Tue, 18 Aug 2026 08:03:59 +0530 Subject: [PATCH 14/15] Basic_viewer: colour by distance over a plane-anchored range so moving the plane updates the colours distance_value_range took the min and max of the bounding-box corner distances to the current plane. Both ends then shifted with the plane exactly as the per-fragment distance did, so translating the plane left the normalised colours unchanged (they only changed on rotation). It also put the clipped-away half in the range, so the legend advertised colours no visible face showed. Anchor the range at the plane instead: 0 at the plane, growing into the kept half up to its farthest point. Moving the plane now sweeps the colours (the farthest distance changes), and the legend matches the visible faces over the full palette. --- Basic_viewer/include/CGAL/Qt/Basic_viewer.h | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h index 5e450d78a812..0eed4f1a8dab 100644 --- a/Basic_viewer/include/CGAL/Qt/Basic_viewer.h +++ b/Basic_viewer/include/CGAL/Qt/Basic_viewer.h @@ -736,8 +736,9 @@ class Basic_viewer : public CGAL::QGLViewer rendering_program_face.setUniformValue("u_RenderingTransparency", clipping_plane_rendering_transparency); rendering_program_face.setUniformValue("u_ClipPlane", clipPlane); rendering_program_face.setUniformValue("u_PointPlane", plane_point); - // Colour by value: the value is the distance to the clipping plane, scaled - // by the scene radius each side of it. + // Colour by value: the value is the distance to the clipping plane, over a scale + // anchored at the plane (0) and growing into the kept half, so moving the plane + // sweeps the colours instead of leaving them unchanged. rendering_program_face.setUniformValue("u_ColorMapMode", static_cast(m_color_map)); { double dvmin, dvmax; distance_value_range(clipPlane, plane_point, dvmin, dvmax); rendering_program_face.setUniformValue("u_ValueMin", static_cast(dvmin)); @@ -1902,20 +1903,29 @@ class Basic_viewer : public CGAL::QGLViewer void distance_value_range(const QVector4D &clipPlane, const QVector4D &plane_point, double &vmin, double &vmax) { + // Colour by distance to the clipping plane, anchored at the plane: 0 at the plane + // (one end of the palette), growing into the kept (solid) half up to its farthest + // point. The kept half is dot(pos-pt, n) > 0 (see onPlane in the shader). We do not + // use the symmetric bounding-box span, which would (a) shift with the plane so both + // range ends moved with the distances and the colours never changed when the plane + // was only translated (they did on rotation), and (b) advertise in the legend the + // colours of the clipped-away half, which no visible face shows. Anchored at the + // plane the colours sweep as the plane is moved (the farthest distance changes), and + // the legend matches the visible faces. const CGAL::Bbox_3 b=m_scene.bounding_box(); const QVector3D n=QVector3D(clipPlane).normalized(); const QVector3D pt=plane_point.toVector3D(); - vmin=(std::numeric_limits::max)(); - vmax=-(std::numeric_limits::max)(); + double dmax=0.0; for (int c=0; c<8; ++c) { const QVector3D corner(float((c&1) ? b.xmax() : b.xmin()), float((c&2) ? b.ymax() : b.ymin()), float((c&4) ? b.zmax() : b.zmin())); const double d=QVector3D::dotProduct(corner-pt, n); - if (dvmax) { vmax=d; } + if (d>dmax) { dmax=d; } } + vmin=0.0; + vmax=dmax; } // Colour by value: the palette colour for a volume's clip-plane cap. The cap faces From f950dbdad1fb1d5bb8580d847478d5298d05e872 Mon Sep 17 00:00:00 2001 From: Rajdeep Singh Date: Fri, 21 Aug 2026 19:08:52 +0530 Subject: [PATCH 15/15] Basic_viewer: rename valued_face to is_face_valued (review) Rename the Graphics_scene_options predicate valued_face to is_face_valued, as sloriot asked on the pull request, so the boolean predicate reads as a question while face_value and face_value_name return the data. Updated the concept documentation, the surface mesh drawer, the BGL face-graph drawer, and the changelog to match. --- BGL/include/CGAL/draw_face_graph.h | 2 +- .../doc/Basic_viewer/Concepts/GraphicsSceneOptions.h | 4 ++-- Basic_viewer/include/CGAL/Graphics_scene_options.h | 6 +++--- Installation/CHANGES.md | 2 +- Surface_mesh/include/CGAL/draw_surface_mesh.h | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/BGL/include/CGAL/draw_face_graph.h b/BGL/include/CGAL/draw_face_graph.h index ed22b853397c..72d462a4e031 100644 --- a/BGL/include/CGAL/draw_face_graph.h +++ b/BGL/include/CGAL/draw_face_graph.h @@ -82,7 +82,7 @@ void compute_elements(const FG &fg, } while (hd != first_hd); // Colour by value: attach the face's scalar value before committing it. - if (gs_options.valued_face(fg, fh)) + if (gs_options.is_face_valued(fg, fh)) { graphics_scene.set_face_value(gs_options.face_value(fg, fh)); } graphics_scene.face_end(); } diff --git a/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h b/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h index 1780f6971760..5beef8b82238 100644 --- a/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h +++ b/Basic_viewer/doc/Basic_viewer/Concepts/GraphicsSceneOptions.h @@ -77,10 +77,10 @@ class GraphicsSceneOptions /// `std::function` that returns `true` if the given face carries a scalar value to /// colour it by, `false` otherwise. `false` by default. - std::function valued_face; + std::function is_face_valued; /// `std::function` that returns the scalar value of the given face. Used only when - /// `valued_face()` returns `true`. The viewer normalises the values over their range + /// `is_face_valued()` returns `true`. The viewer normalises the values over their range /// and maps them to a color palette. std::function face_value; diff --git a/Basic_viewer/include/CGAL/Graphics_scene_options.h b/Basic_viewer/include/CGAL/Graphics_scene_options.h index 709f109b0c10..033c02271267 100644 --- a/Basic_viewer/include/CGAL/Graphics_scene_options.h +++ b/Basic_viewer/include/CGAL/Graphics_scene_options.h @@ -53,7 +53,7 @@ struct Graphics_scene_optionsbool { return false; }; - valued_face=[](const DS &, face_descriptor)->bool { return false; }; + is_face_valued=[](const DS &, face_descriptor)->bool { return false; }; } // The seven following functions should not be null @@ -69,10 +69,10 @@ struct Graphics_scene_options valued_face; + std::function is_face_valued; /// `std::function` that returns the scalar value of the given face. Called only - /// when `valued_face()` returns `true`. The viewer normalises the values over their + /// when `is_face_valued()` returns `true`. The viewer normalises the values over their /// range and maps them to a colour palette. std::function face_value; diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 379f75135bb4..c030976a1bff 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -39,7 +39,7 @@ Release date: December 2026 - Added the possibility to colour the faces by a value mapped to a colour palette: the distance to the clipping plane, the cell size, or a scalar value provided by the drawer. A drawer can attach a value to each face through the new `Graphics_scene_options` functions - `valued_face` and `face_value` (with `face_value_name` for the legend). As an example, the + `is_face_valued` and `face_value` (with `face_value_name` for the legend). As an example, the surface mesh drawer exposes the aspect ratio of each face. ## [Release 6.2](https://github.com/CGAL/cgal/releases/tag/v6.2) diff --git a/Surface_mesh/include/CGAL/draw_surface_mesh.h b/Surface_mesh/include/CGAL/draw_surface_mesh.h index 76121f7bfa74..03a4c0df83c6 100644 --- a/Surface_mesh/include/CGAL/draw_surface_mesh.h +++ b/Surface_mesh/include/CGAL/draw_surface_mesh.h @@ -148,7 +148,7 @@ struct Graphics_scene_options_surface_mesh // Colour by value: expose the aspect ratio (longest edge / shortest edge) of // each face, so the viewer can colour the mesh by it (Shift+D). this->face_value_name="aspect ratio"; - this->valued_face=[](const SM &, face_descriptor)->bool { return true; }; + this->is_face_valued=[](const SM &, face_descriptor)->bool { return true; }; this->face_value=[](const SM &sm, face_descriptor f)->float { double l2min=(std::numeric_limits::max)(), l2max=0.0;