Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions docs/ARIADNE.md
Original file line number Diff line number Diff line change
Expand Up @@ -617,10 +617,22 @@ down), centres it in x and y, sits it on z = 0 and scales its largest extent to
| `decimate.target_faces` | triangle target for `cvc::simplify` | `target_faces` |
| `repair.weld`, `repair.remove_degenerate`, `repair.remove_unreferenced`, `repair.orient` | `cvc::repair_params` | `weld`, `remove_degenerate`, `remove_unreferenced`, `orient` |
| `color.mode` | `none`, `mean`, `gaussian`, `k1`, `k2`, `geodesic`, `modes` — followed live | `color_mode` |
| `color.colormap` | `viridis`, `magma`, `plasma`, `inferno`, `turbo`, `jet`, `parula`, `gray` | `colormap` |
| `color.colormap` | `viridis`, `magma`, `plasma`, `inferno`, `turbo`, `jet`, `parula`, `gray`: resets the field colours (below) to that ramp | `colormap` |
| `geodesic.source` | source vertex for `geodesic` (clamped to #V-1; -1 is the highest vertex) | `geodesic_source` |
| `modes.index` | the Laplace–Beltrami eigenfunction `modes` shows (`fem::laplacian_eigenmodes` on the surface; 1 is the first non-constant, up to 50) | `mode` |
| `color.min`, `color.max` | the colour range used (2-98% percentiles for curvature) | — |
| `color.min`, `color.max` | the colour range used: the field colours' explicit domain, else the values' (2-98% percentiles for curvature) | — |

**Field colours (both node types).** The values a node shows are coloured through a transfer
function at `<node>.field_colors` (a `cvc::tf::model` string, outside `mesh_ops.*` / `fe.*`), so
a `tf_editor` edits the ramp: mesh_tools and fe_controls each hold one (`show_opacity: false`,
`show_isocontours: false`). Only the colour ramp is used. The colormap key (`color.colormap`,
`view.colormap`) seeds it and resets it to that colormap's ramp whenever it changes, so a
document stepping through colormaps still works; an edit stands until the next change. An
explicit domain in the model is the range the values are coloured over (in fe_lab it wins over
`view.fixed_range`). Beside it the node publishes `field_colors.range` (the colours' span under
an `auto` domain) and `field_colors.histogram` (128 bins of the values over it; fe_lab's is the
whole solution in the field view, the shown elements' size or quality in the elements view): the
editor's axis and histogram.

`curvature` switches a non-curvature `color.mode` to `mean` (or recomputes the one shown).
A geodesic solver is cached per mesh version, so moving the source is two
Expand Down Expand Up @@ -676,13 +688,13 @@ Keys under `<node>.fe.*`:
| `modes.index` | the eigenmode `modes` solves for (1 is the first non-constant, up to 50) | `mode` |
| `view.mode` | `field` or `elements` | `view` |
| `view.boundary` | `input` (the smooth surface, when there is one) or `mesh` | `boundary` |
| `view.colormap`, `view.show_surface`, `view.show_slice` | display | `colormap`, `show_surface`, `show_slice` |
| `view.colormap`, `view.show_surface`, `view.show_slice` | display; `view.colormap` resets the field colours (above) to that ramp | `colormap`, `show_surface`, `show_slice` |
| `view.slice_axis`, `view.slice_offset` | 0/1/2 and -1..1 across the mesh bounds: the cut, in both views | `slice_axis`, `slice_offset` |
| `view.surface_opacity` | the boundary's opacity while a slice shows | `surface_opacity` |
| `view.color_by` | the elements' colour: `size` or `quality` | `color_by` |
| `view.cut_side` | the side of the cut the elements view keeps: 1 (+) or -1 (−) | `cut_side` |
| `view.ghost` | the outer surface's opacity in the elements view (0 = off) | `ghost` |
| `view.fixed_range`, `view.range_min`, `view.range_max` | colour over a fixed range instead of the data's (both views) | `fixed_range`, `range_min`, `range_max` |
| `view.fixed_range`, `view.range_min`, `view.range_max` | colour over a fixed range instead of the data's (both views; an explicit field-colours domain wins) | `fixed_range`, `range_min`, `range_max` |

The heat problem holds the boundary vertices in the lowest 5% of the height at 1 and
insulates the rest; the body starts at 0. `auto.solve` solves after every mesh,
Expand Down
11 changes: 11 additions & 0 deletions docs/TRANSFER_FUNCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,17 @@ and the overlays, computed by a `schedule_overlays` task from a copy-on-write co
(the histogram at once, the spectrum and tree on first request) and re-binned onto `T.range` when
that differs from the range they were computed over.

**Field colours: a colour ramp for a field, not a volume.** `mesh_lab` and `fe_lab` also colour
the values they show (a curvature, a geodesic distance, a FEM solution, element sizes) through a
model at `<node>.field_colors`, which the nodes follow themselves (no `tf_binding`): its colour
ramp is baked into the table every GeometryNode showing the values maps through
(`GeometryNode::setColorTable`), over the model's explicit domain or else the values' own range.
The opacity curve and isocontours are not used, so the components' editors hide them
(`show_opacity: false`, `show_isocontours: false`). The node publishes `field_colors.range` and a
128-bin `field_colors.histogram` of the values beside it. The colormap key (`color.colormap`,
`fe.view.colormap`) seeds the model and resets it to that colormap's ramp when it changes. See
[ARIADNE.md](ARIADNE.md), "Field colours".

**volslice: the TF is fixed in data units, the window is the LUT's span.** `VolSliceNode` samples
the raw-unit tables over its window (else the data range), so the model's `auto` domain is the
volume's range, not the window: moving the window re-lowers nothing, and the slices show the
Expand Down
7 changes: 7 additions & 0 deletions inc/cvc/gl/GeometryNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ class GeometryNode : public GraphicsNode {
double hi = std::numeric_limits<double>::quiet_NaN());
void setScalarRange(double lo, double hi);
void setColorMap(cvc::colormap_kind cm);
// A colour table the field maps through instead of the colormap: `rgb` holds n >= 2 entries
// (3n floats, each channel 0..1) uniform over [lo, hi], sampled linearly between entries (a
// transfer function's baked ramp, cvc::tf::model::bake). It stays across setScalarField,
// setColorMap and clearScalarField; an empty (or shorter) table goes back to the colormap.
// Recolours in place. NaN values draw mid-gray, as with a colormap.
void setColorTable(const std::vector<float> &rgb);
void clearScalarField();
// True while the mesh has a scalar field to colour by (an explicit one, or a
// color_by field this geometry carries), shown or hidden by use_single_color.
Expand Down Expand Up @@ -315,6 +321,7 @@ class GeometryNode : public GraphicsNode {
std::vector<double> m_scalarField; // ... and its values
std::string m_colorBy = "none"; // the color_by state key
cvc::colormap_kind m_colorMap = cvc::colormap_kind::VIRIDIS;
std::vector<float> m_colorTable; // setColorTable: replaces m_colorMap while it has >= 2 entries
double m_scalarMin, m_scalarMax; // scalar_min / scalar_max; NaN = auto
bool m_scalarActive = false; // the last recolour mapped a field
std::pair<double, double> m_scalarRangeUsed;
Expand Down
11 changes: 11 additions & 0 deletions inc/cvc/gl/ariadne/mesh_nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
// geodesic.source (clamped to #V-1; -1 = the highest vertex), modes.index (the
// Laplace-Beltrami eigenfunction, >= 1) (followed live)
// color.min, color.max (published)
// and <node>.field_colors (below).
//
// type: fe_lab -- a volume read like a `volren` node (source: { file } or { sdf }), meshed
// with the LBIE mesher (cvc::tetrahedralize, tetrahedralize_interval below, hexahedralize)
Expand Down Expand Up @@ -83,6 +84,16 @@
// <node>.transfer_function is bound (tf_binding.h): its isocontours drive the bounds, over
// the range of the volume the last load brought.
//
// Field colours (both types): "<node>.field_colors" holds a cvc::tf::model (a tf_editor widget
// edits it) whose colour ramp the shown values map through -- mesh_lab's colour field; fe_lab's
// field on the boundary and the cut, and its elements' size or quality -- over the range they
// are coloured with: the model's explicit domain, else (fe_lab) view.fixed_range's, else their
// own. Its opacity curve and isocontours are not used. The colormap key (color.colormap,
// view.colormap) seeds it and, whenever it changes, resets it to that colormap's ramp (auto
// domain); otherwise an edit stands. Beside it the node publishes .range (the colours' span
// under an auto domain) and .histogram (128 bins of the values over it; fe_lab: the whole
// solution, or the shown elements' values). mesh_lab's color.min / color.max are the range used.
//
// Counts and indices read from state are range-checked (NaN = the default): smooth.iterations
// 1..1000, decimate.target_faces >= 1, mesh.improve 0..5, mesh.improve_iterations 0..100,
// heat.steps 1..1e6, modes.index 1..50, source.dim 0..1024, view.slice_axis 0..2.
Expand Down
15 changes: 13 additions & 2 deletions src/cvc/ariadne/components/fe_controls.ari
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
# settles, and the editor shades the meshed interval; these sliders move the bars too, and wait for
# Mesh. "Solve" runs a linear (P1) FEM Poisson, heat or eigenmode problem on that mesh (cvc::fem,
# libigl assembly). The field is shown on the tet boundary surface and on a movable axis-aligned
# cut, coloured by a colormap; the elements view shows the mesh itself, cut away, each element
# coloured by its size or quality. Binds the fe.* keys
# cut; the elements view shows the mesh itself, cut away, each element coloured by its size or
# quality. Both are coloured through the node's field colours (graphics.root.children.domain
# .field_colors, a transfer function: the Colours editor edits its ramp over the values' histogram,
# and the Colormap combo resets it to a named ramp). Binds the fe.* keys
# the cvcGL fe_lab realizer owns (state at "<scene>.graphics.root.children.domain.fe.*"). Commands
# go through fe.request: the realizer consumes it and runs the job off the render thread; Stop
# interrupts a heat solve at its next step, and is enabled only while one runs (fe.stoppable):
Expand Down Expand Up @@ -192,6 +194,15 @@ units:
- combo: Colormap
bind: graphics.root.children.domain.fe.view.colormap
options: [viridis, magma, plasma, inferno, turbo, jet, parula, gray]
tooltip: "resets the colours below to this colormap's ramp"
- tf_editor: Colours
id: fe_field_colors
bind: graphics.root.children.domain.field_colors
height: 70
layers: [histogram]
show_opacity: false
show_isocontours: false
interactive_updates: true
- checkbox: Boundary surface
bind: graphics.root.children.domain.fe.view.show_surface
visible_when: (!= (state-get "graphics.root.children.domain.fe.view.mode") "elements")
Expand Down
14 changes: 13 additions & 1 deletion src/cvc/ariadne/components/mesh_tools.ari
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
# mesh_ops.request, the realizer consumes it (writes "" back), runs the operation off the render
# thread and publishes status / stats. While a job runs mesh_ops.busy is 1 and the command buttons
# grey out. Colour by / Colormap / Geodesic source / Mode are FOLLOWED: changing them recolours the
# mesh. Load reads another mesh file (source.*), so one node can switch datasets.
# mesh. The colours come from the node's field colours (graphics.root.children.mesh.field_colors, a
# transfer function): the Colours editor edits its ramp over the values' histogram, and Colormap
# resets it to a named ramp. Load reads another mesh file (source.*), so one node can switch
# datasets.
# Needs a libcvc built with CVC_ENABLE_LIBIGL (the mesh_lab node type is registered only there).
#
# Convention: it binds a mesh_lab node named `mesh`. To drive a differently-named node, copy this
Expand Down Expand Up @@ -117,6 +120,15 @@ units:
- combo: Colormap
bind: graphics.root.children.mesh.mesh_ops.color.colormap
options: [viridis, magma, plasma, inferno, turbo, jet, parula, gray]
tooltip: "resets the colours below to this colormap's ramp"
- tf_editor: Colours
id: mesh_field_colors
bind: graphics.root.children.mesh.field_colors
height: 70
layers: [histogram]
show_opacity: false
show_isocontours: false
interactive_updates: true
- slider_int: Geodesic source vertex
bind: graphics.root.children.mesh.mesh_ops.geodesic.source
lo: -1
Expand Down
40 changes: 39 additions & 1 deletion src/cvcGL/GeometryNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,32 @@ bool isColorBy(const std::string &s) {
return s == "none" || s == "colors" || s == "function" || s == "k1" || s == "k2" || s == "mean" ||
s == "gaussian";
}

// colormap_rgb through a colour table (setColorTable) instead of a colormap: entry i of the
// table's n sits at lo + i (hi - lo) / (n - 1), linear between entries, clamped at the ends.
std::vector<unsigned char> tableRgb(const std::vector<double> &values,
const std::vector<float> &table, double lo, double hi) {
const std::size_t n = table.size() / 3;
std::vector<unsigned char> rgb(3 * values.size());
const double span = hi - lo;
const auto byte = [](double c) {
return static_cast<unsigned char>(std::lround(255.0 * std::clamp(c, 0.0, 1.0)));
};
for (std::size_t i = 0; i < values.size(); ++i) {
unsigned char *out = &rgb[3 * i];
if (!std::isfinite(values[i])) {
out[0] = out[1] = out[2] = 128;
continue;
}
const double t = span > 0.0 ? std::clamp((values[i] - lo) / span, 0.0, 1.0) : 0.5;
const double x = t * static_cast<double>(n - 1);
const std::size_t a = std::min(static_cast<std::size_t>(x), n - 2);
const double f = x - static_cast<double>(a);
for (int c = 0; c < 3; ++c)
out[c] = byte((1.0 - f) * table[3 * a + c] + f * table[3 * (a + 1) + c]);
}
return rgb;
}
} // namespace

void GeometryNode::setScalarField(const std::vector<double> &perVertex, cvc::colormap_kind cm,
Expand Down Expand Up @@ -824,6 +850,17 @@ void GeometryNode::setColorMap(cvc::colormap_kind cm) {
});
}

void GeometryNode::setColorTable(const std::vector<float> &rgb) {
runOnMainThread([this, table = rgb]() mutable {
if (table.size() < 6 || table.size() % 3 != 0)
table.clear();
if (table == m_colorTable)
return;
m_colorTable = std::move(table);
recolor();
});
}

void GeometryNode::clearScalarField() {
runOnMainThread([this]() {
if (!m_hasScalarField)
Expand Down Expand Up @@ -1034,7 +1071,8 @@ void GeometryNode::applyVertexColors(const cvc::geometry &geom, bool inPlace) {
}
m_scalarRangeUsed = {lo, hi};
if (!m_useSingleColor) {
rgb = cvc::colormap_rgb(field, m_colorMap, lo, hi);
rgb = m_colorTable.empty() ? cvc::colormap_rgb(field, m_colorMap, lo, hi)
: tableRgb(field, m_colorTable, lo, hi);
if (rgb.size() != 3 * n)
rgb.clear();
}
Expand Down
Loading
Loading