cgv
Loading...
Searching...
No Matches
transfer_function_editor.cxx
1#include "transfer_function_editor.h"
2
3#include <algorithm>
4#include <numeric>
5
6#include <cgv/gui/key_event.h>
7#include <cgv/gui/mouse_event.h>
8#include <cgv/gui/theme_info.h>
9#include <cgv/math/ftransform.h>
10#include <cgv/utils/algorithm.h>
12#include <cgv/utils/file.h>
13#include <cgv_gl/gl/gl.h>
14#include <cgv_g2d/msdf_font.h>
15#include <cgv_g2d/msdf_gl_font_renderer.h>
16
17namespace cgv {
18namespace overlay {
19
20template<> vec2 transfer_function_editor::color_point::domain = { 0.0f, 1.0f };
21template<> vec2 transfer_function_editor::opacity_point::domain = { 0.0f, 1.0f };
22const vec2 transfer_function_editor::color_point_size = { 12.0f, 18.0f };
23const vec2 transfer_function_editor::opacity_point_size = { 12.0f };
24const size_t transfer_function_editor::preview_texture_resolution = 256;
25
26transfer_function_editor::transfer_function_editor() {
27 set_name("Color Scale Editor");
28 blocks_events(true);
29
30 layout.total_height = supports_opacity ? 200 : 60;
31
32 set_size(ivec2(600u, layout.total_height));
33
34 color_draggables.set_constraint(layout.color_draggables_rect);
35 color_draggables.callback = std::bind(&transfer_function_editor::handle_drag, this, std::placeholders::_1, DraggableType::kColor);
36
37 opacity_draggables.set_constraint(layout.opacity_editor_rect);
38 opacity_draggables.callback = std::bind(&transfer_function_editor::handle_drag, this, std::placeholders::_1, DraggableType::kOpacity);
39
40 color_handle_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::arrow);
41 opacity_handle_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::rectangle);
42 line_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::line);
43 polygon_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::polygon);
44}
45
47 cgv::g2d::ref_msdf_font_regular(ctx, -1);
48 cgv::g2d::ref_msdf_gl_font_renderer_2d(ctx, -1);
49
51
52 color_handle_renderer.destruct(ctx);
53 opacity_handle_renderer.destruct(ctx);
54 line_renderer.destruct(ctx);
55 polygon_renderer.destruct(ctx);
56
57 background_tex.destruct(ctx);
58 preview_tex.destruct(ctx);
59 histogram_tex.destruct(ctx);
60}
61
63 bool capture_event = false;
64
65 //bool request_clear_selection = false;
66
67 if(e.get_action() == cgv::gui::MA_PRESS) {
68 if(e.get_button() & cgv::gui::MB_LEFT_BUTTON) {
69 //request_clear_selection = is_hit_local(local_mouse_pos);
70
71 if(!get_hit_point(local_mouse_pos)) {
72 add_point(local_mouse_pos);
73 //request_clear_selection = false;
74 }
75 capture_event = true;
76 } else if(e.get_button() & cgv::gui::MB_RIGHT_BUTTON) {
77 cgv::g2d::draggable* hit_point = get_hit_point(local_mouse_pos);
78 if(hit_point)
79 erase_point(hit_point);
80 }
81 }
82
83 if(color_draggables.handle(e, get_viewport_size(), get_rectangle()))
84 return true;
85 if(opacity_draggables.handle(e, get_viewport_size(), get_rectangle()))
86 return true;
87 /*
88 if(request_clear_selection) {
89 selected_color_draggable = nullptr;
90 selected_opacity_draggable = nullptr;
91 handle_selection_change();
92 }*/
93 if (capture_event)
94 return true;
95 return false;
96}
97
99 if(ptr.points_to(layout.total_height)) {
100 ivec2 size = get_rectangle().size;
101 size.y() = layout.total_height;
102 set_size(size);
103 }
104
105 if(ptr.points_to_one_of(color_interpolation, opacity_interpolation)) {
106 if(transfer_function) {
107 transfer_function->set_color_interpolation(color_interpolation);
108 if(supports_opacity)
109 transfer_function->set_opacity_interpolation(opacity_interpolation);
110
111 create_preview_texture();
112 post_damage();
113
114 if(on_change_callback)
115 on_change_callback();
116 }
117 }
118
119 if(ptr.points_to_data_of(color_draggables.ref_draggables())) {
120 update_transfer_function_from_data();
121 create_preview_texture();
122 create_geometry();
123 post_damage();
124 }
125
126 for(auto& draggable : opacity_draggables) {
127 if(ptr.points_to(draggable.uv.y())) {
128 draggable.set_uv_and_update_position(draggable.uv);
129 update_transfer_function_from_data();
130 create_geometry();
131 post_damage();
132 break;
133 }
134 }
135
136 if(ptr.points_to(supports_opacity)) {
137 // Todo: Fixme: This will overwrite the user-defined total height.
138 layout.total_height = supports_opacity ? 200 : 60;
139 on_set(&layout.total_height);
140
141 if(!supports_opacity) {
142 if(transfer_function) {
143 //transfer_function->clear_opacity_points();
144 //opacity_draggables.clear();
145
146 update_point_positions();
147 //update_color_map(false);
148 }
149 }
150
151 post_recreate_layout();
153 }
154}
155
157 cgv::g2d::ref_msdf_font_regular(ctx, 1);
158 cgv::g2d::ref_msdf_gl_font_renderer_2d(ctx, 1);
159
160 register_shader("rectangle", cgv::g2d::shaders::rectangle);
161 register_shader("circle", cgv::g2d::shaders::circle);
162 register_shader("histogram", "heightfield1d.glpr");
163
164 bool success = canvas_overlay::init(ctx);
165
166 success &= color_handle_renderer.init(ctx);
167 success &= opacity_handle_renderer.init(ctx);
168 success &= line_renderer.init(ctx);
169 success &= polygon_renderer.init(ctx);
170 success &= create_background_texture();
171
172 return success;
173}
174
176 if(ensure_layout(ctx)) {
177 ivec2 container_size = get_rectangle().size;
178
179 update_layout(container_size);
180 force_update_data_from_transfer_function();
181
182 float width_factor = static_cast<float>(layout.opacity_editor_rect.w()) / static_cast<float>(layout.opacity_editor_rect.h());
183 background_style.texcoord_scaling = vec2(5.0f * width_factor, 5.0f);
184
185 color_draggables.set_constraint(layout.color_draggables_rect);
186 opacity_draggables.set_constraint(layout.opacity_editor_rect);
187 }
188}
189
191 begin_content(ctx);
192
193 // draw inner border
194 ivec2 container_size = get_rectangle().size;
195 content_canvas.enable_shader(ctx, "rectangle");
196 content_canvas.set_style(ctx, border_style);
197 content_canvas.draw_shape(ctx, ivec2(padding() - 1) + ivec2(0, 10), container_size - 2 * padding() + 2 - ivec2(0, 10));
198
199 if(transfer_function && preview_tex.is_created()) {
200 // draw color scale texture
201 content_canvas.set_style(ctx, color_map_style);
202 preview_tex.enable(ctx, 0);
203 content_canvas.draw_shape(ctx, layout.color_editor_rect);
204 preview_tex.disable(ctx);
205
206 if(supports_opacity) {
207 // draw opacity editor checkerboard background
208 content_canvas.set_style(ctx, background_style);
209 background_tex.enable(ctx, 0);
210 content_canvas.draw_shape(ctx, layout.opacity_editor_rect);
211 background_tex.disable(ctx);
212
213 // draw histogram
214 if(histogram_type != HistogramType::kNone && histogram_tex.is_created()) {
215 auto& hist_prog = content_canvas.enable_shader(ctx, "histogram");
216 hist_prog.set_uniform(ctx, "max_value", hist_norm_ignore_zero ? hist_max_non_zero : hist_max);
217 hist_prog.set_uniform(ctx, "norm_gamma", hist_norm_gamma);
218 hist_prog.set_uniform(ctx, "sampling_type", cgv::math::clamp(static_cast<unsigned>(histogram_type) - 1, 0u, 2u));
219 histogram_style.apply(ctx, hist_prog);
220
221 histogram_tex.enable(ctx, 1);
222 content_canvas.draw_shape(ctx, layout.opacity_editor_rect);
223 histogram_tex.disable(ctx);
224 content_canvas.disable_current_shader(ctx);
225 }
226
227 preview_tex.enable(ctx, 0);
228 // draw transfer function area polygon
229 polygon_renderer.render(ctx, content_canvas, cgv::render::PT_TRIANGLE_STRIP, triangle_geometry, polygon_style);
230
231 // draw transfer function lines
232 line_renderer.render(ctx, content_canvas, cgv::render::PT_LINE_STRIP, line_geometry, line_style);
233 preview_tex.disable(ctx);
234
235 // draw separator line
236 content_canvas.enable_shader(ctx, "rectangle");
237 content_canvas.set_style(ctx, border_style);
238 content_canvas.draw_shape(ctx,
239 ivec2(layout.color_editor_rect.x(), layout.color_editor_rect.y1()),
240 ivec2(container_size.x() - 2 * padding(), 1)
241 );
242 content_canvas.disable_current_shader(ctx);
243 }
244
245 // draw control points
246 // color handles
247 color_handle_renderer.render(ctx, content_canvas, cgv::render::PT_LINES, color_draggables_geometry, color_handle_style);
248
249 // opacity handles
250 if(supports_opacity) {
251 auto& opacity_handle_prog = opacity_handle_renderer.enable_prog(ctx);
252 opacity_handle_prog.set_attribute(ctx, "size", opacity_point_size); // size is constant for all points
253 opacity_handle_renderer.render(ctx, content_canvas, cgv::render::PT_POINTS, opacity_draggables_geometry, opacity_handle_style);
254 }
255 } else {
256 content_canvas.disable_current_shader(ctx);
257 }
258
259 auto& font = cgv::g2d::ref_msdf_font_regular(ctx);
260 auto& font_renderer = cgv::g2d::ref_msdf_gl_font_renderer_2d(ctx);
261 cgv::g2d::msdf_gl_font_renderer::text_render_info text_render_info;
262
263 if(!value_label.empty()) {
264 cgv::g2d::irect rectangle = value_label_rectangle;
265 rectangle.translate(0, 5);
266 rectangle.size += ivec2(10, 6);
267
268 content_canvas.enable_shader(ctx, "rectangle");
269 content_canvas.set_style(ctx, label_box_style);
270 content_canvas.draw_shape(ctx, rectangle);
271 content_canvas.disable_current_shader(ctx);
272
273 text_render_info.alignment = cgv::render::TextAlignment::TA_BOTTOM;
274 font_renderer.render(ctx, content_canvas, font, value_label, value_label_rectangle.position, text_render_info, value_label_style);
275 }
276
277 end_content(ctx);
278}
279
285
287 if(begin_tree_node("Settings", layout, false)) {
288 align("\a");
289 std::string height_options = "min=";
290 height_options += supports_opacity ? "80" : "40";
291 height_options += ";max=500;step=10;ticks=true";
292 add_member_control(this, "Height", layout.total_height, "value_slider", height_options);
293 align("\b");
294 end_tree_node(layout);
295 }
296
297 add_decorator("Color", "heading", "level=4;font_style=regular;w=94", " ");
298 add_decorator("Opacity", "heading", "level=4;font_style=regular;w=94", "%y-=6\n");
299 const std::string interpolation_enums = "enums='Step,Linear,Smooth'";
300 add_member_control(this, "Interpolation", color_interpolation, "dropdown", interpolation_enums + ";w=94", " ");
301 add_member_control(this, "", opacity_interpolation, "dropdown", interpolation_enums + ";w=94");
302
303 add_member_control(this, "Domain", input_domain[0], "value", "step=0.001;w=94", " ");
304 add_member_control(this, "", input_domain[1], "value", "step=0.001;w=94");
305 cgv::signal::connect_copy(add_button("Rescale")->click, cgv::signal::rebind(this, &transfer_function_editor::rescale_domain));
306
307 if(begin_tree_node("Color Points", color_draggables, false)) {
308 align("\a");
309 auto& points = color_draggables;
310 for(size_t i = 0; i < points.size(); ++i) {
311 std::string label_prefix = "";
312 std::string options = "w=48";
313 if(&points[i] == selected_color_draggable) {
314 label_prefix = "> ";
315 options += ";label_color=" + cgv::media::to_hex(highlight_color);
316 }
317
318 add_view(label_prefix + std::to_string(i), points[i].domain_value, "", options, " ");
319 add_member_control(this, "", points[i].data, "", "w=140");
320 }
321 align("\b");
322 end_tree_node(color_draggables);
323 }
324
325 if(supports_opacity) {
326 if(begin_tree_node("Opacity Points", opacity_draggables, false)) {
327 align("\a");
328 auto& points = opacity_draggables;
329 for(size_t i = 0; i < points.size(); ++i) {
330 std::string label_prefix = "";
331 std::string options = "w=48";
332 if(&points[i] == selected_opacity_draggable) {
333 label_prefix = "> ";
334 options += ";label_color=" + cgv::media::to_hex(highlight_color);
335 }
336
337 add_view(label_prefix + std::to_string(i), points[i].domain_value, "", options, " ");
338 add_member_control(this, "", points[i].uv.y(), "value", "w=140");
339 }
340 align("\b");
341 end_tree_node(opacity_draggables);
342 }
343 }
344
345 add_member_control(this, "New position", input_position, "value", "step=0.001");
346 cgv::signal::connect_copy(add_button("Set selected position")->click, cgv::signal::rebind(this, &transfer_function_editor::set_selected_point_domain_value));
347
348 if(begin_tree_node("Histogram", histogram, false)) {
349 align("\a");
350 add_member_control(this, "Type", histogram_type, "dropdown", "enums='None,Nearest,Linear,Smooth'");
351 add_member_control(this, "Ignore Zero for Normalization", hist_norm_ignore_zero, "check");
352 add_member_control(this, "Gamma", hist_norm_gamma, "value_slider", "min=0.001;max=2;step=0.001;ticks=true");
353 add_member_control(this, "Fill Color", histogram_style.fill_color);
354 add_member_control(this, "Border Color", histogram_style.border_color);
355 add_member_control(this, "Border Width", histogram_style.border_width, "value_slider", "min=0;max=10;step=0.5;ticks=true");
356 align("\b");
357 end_tree_node(histogram);
358 }
359}
360
361void transfer_function_editor::set_opacity_support(bool flag) {
362 supports_opacity = flag;
363 //set_transfer_function(transfer_function);
364 on_set(&supports_opacity);
365}
366
367void transfer_function_editor::set_transfer_function(std::shared_ptr<cgv::media::transfer_function> transfer_function) {
368 if(this->transfer_function != transfer_function) {
369 this->transfer_function = transfer_function;
370 build_time.reset();
371 }
372
373 this->transfer_function = transfer_function;
374 update_data_from_transfer_function();
375 post_damage();
376}
377
378void transfer_function_editor::set_histogram_data(const std::vector<unsigned> data) {
379 histogram = data;
380
381 std::vector<float> float_data(histogram.size(), 0.0f);
382 hist_max = 1;
383 hist_max_non_zero = 1;
384 for(size_t i = 0; i < histogram.size(); ++i) {
385 unsigned count = histogram[i];
386 hist_max = std::max(hist_max, count);
387 if(i > 0)
388 hist_max_non_zero = std::max(hist_max_non_zero, count);
389 float_data[i] = static_cast<float>(count);
390 }
391
392 if(get_context()) {
393 cgv::data::data_view data_view = cgv::data::data_view(new cgv::data::data_format(unsigned(histogram.size()), cgv::type::info::TI_FLT32, cgv::data::CF_R), float_data.data());
394 histogram_tex.create(*get_context(), data_view, 0);
395 post_damage();
396 }
397}
398
399void transfer_function_editor::set_selected_color(rgb color) {
400 if(selected_color_draggable) {
401 selected_color_draggable->data = color;
402 on_set(&selected_color_draggable->data);
403 post_damage();
404 }
405}
406
407void transfer_function_editor::init_styles() {
408 auto& theme = cgv::gui::theme_info::instance();
409 handle_color = theme.text();
410 highlight_color = theme.highlight();
411
412 // configure style for the border rectangles
413 border_style.fill_color = theme.border();
414 border_style.border_width = 0.0f;
415 border_style.feather_width = 0.0f;
416
417 // configure style for the color scale rectangle
418 color_map_style = border_style;
419 color_map_style.use_texture = true;
420
421 // configure style for background
422 background_style.use_texture = true;
423 background_style.feather_width = 0.0f;
424
425 // configure style for histogram
426 histogram_style.use_blending = true;
427 histogram_style.feather_width = 1.0f;
428 histogram_style.feather_origin = 0.0f;
429 histogram_style.fill_color = rgba(rgb(0.5f), 0.666f);
430 histogram_style.border_color = rgba(rgb(0.0f), 0.666f);
431 histogram_style.border_width = 1.0f;
432
433 // configure style for color handles
434 color_handle_style.use_blending = true;
435 color_handle_style.use_fill_color = false;
436 color_handle_style.position_is_center = true;
437 color_handle_style.border_color = theme.border();
438 color_handle_style.border_width = 1.5f;
439 color_handle_style.border_radius = 2.0f;
440 color_handle_style.stem_width = color_point_size.x();
441 color_handle_style.head_width = color_point_size.x();
442
443 label_box_style.position_is_center = true;
444 label_box_style.use_blending = true;
445 label_box_style.fill_color = handle_color;
446 label_box_style.border_color = theme.border();
447 label_box_style.border_width = 1.5f;
448 label_box_style.border_radius = 4.0f;
449
450 // configure style for opacity handles
451 opacity_handle_style.use_blending = true;
452 opacity_handle_style.use_fill_color = false;
453 opacity_handle_style.position_is_center = true;
454 opacity_handle_style.border_color = theme.border();
455 opacity_handle_style.border_width = 1.5f;
456
457 // configure style for the lines and polygon
458 line_style.use_blending = true;
459 line_style.use_fill_color = false;
460 line_style.use_texture = true;
461 line_style.use_texture_alpha = false;
462 line_style.width = 3.0f;
463
464 polygon_style = static_cast<cgv::g2d::shape2d_style>(line_style);
465 polygon_style.use_texture_alpha = true;
466
467 // label style
468 cursor_label_style.fill_color = rgb(0.0f);
469 cursor_label_style.font_size = 16.0f;
470
471 value_label_style.fill_color = theme.group();
472 value_label_style.font_size = 12.0f;
473}
474
475void transfer_function_editor::update_layout(const ivec2& parent_size) {
476 int content_height = layout.total_height - 10 - 2 * padding();
477 if(supports_opacity) {
478 layout.color_editor_height = static_cast<int>(floor(0.15f * static_cast<float>(content_height)));
479 layout.color_editor_height = cgv::math::clamp(layout.color_editor_height, 4, 80);
480 layout.opacity_editor_height = content_height - layout.color_editor_height - 1;
481 } else {
482 layout.color_editor_height = content_height;
483 layout.opacity_editor_height = 0;
484 }
485
486 int y_off = padding();
487
488 layout.color_draggables_rect = {
489 ivec2(padding(), 16),
490 ivec2(parent_size.x() - 2 * padding(), 0)
491 };
492
493 // move 10px up to clear some space for the color handles rect
494 y_off += 10;
495
496 layout.color_editor_rect.position = ivec2(padding(), y_off);
497 layout.color_editor_rect.size = ivec2(parent_size.x() - 2 * padding(), layout.color_editor_height);
498
499 y_off += layout.color_editor_height + 1; // plus 1px border
500
501 layout.opacity_editor_rect.position = ivec2(padding(), y_off);
502 layout.opacity_editor_rect.size = ivec2(parent_size.x() - 2 * padding(), layout.opacity_editor_height);
503}
504
505void transfer_function_editor::clear_data() {
506 selected_color_draggable = nullptr;
507 selected_opacity_draggable = nullptr;
508 color_draggables.clear();
509 opacity_draggables.clear();
510 color_draggables_geometry.clear();
511 opacity_draggables_geometry.clear();
512 line_geometry.clear();
513 triangle_geometry.clear();
514}
515
516void transfer_function_editor::force_update_data_from_transfer_function() {
517 if(!transfer_function)
518 return;
519
520 size_t previous_color_point_count = color_draggables.size();
521 size_t previous_opacity_point_count = opacity_draggables.size();
522
523 clear_data();
524
525 const vec2 domain = transfer_function->get_domain();
526 input_domain = domain;
527 update_member(&input_domain[0]);
528 update_member(&input_domain[1]);
529 color_point::domain = domain;
530 opacity_point::domain = domain;
531
532 for(const auto& point : transfer_function->get_color_points()) {
533 color_point draggable = make_color_point();
534 draggable.data = point.second;
535 const vec2 uv = { cgv::math::normalize(point.first, domain[0], domain[1]), 0.0f };
536 draggable.set_uv_and_update_position(uv);
537 color_draggables.add(draggable);
538 }
539
540 if(supports_opacity) {
541 for(const auto& point : transfer_function->get_opacity_points()) {
542 opacity_point draggable = make_opacity_point();
543 const vec2 uv = { cgv::math::normalize(point.first, domain[0], domain[1]), point.second };
544 draggable.set_uv_and_update_position(uv);
545 opacity_draggables.add(draggable);
546 }
547 }
548
549 create_geometry();
550 create_preview_texture();
551
552 color_interpolation = transfer_function->get_color_interpolation();
553 opacity_interpolation = transfer_function->get_opacity_interpolation();
554
555 // Don't do a full gui recreation if the number of control points did not change to prevent canceling potential mouse interactions.
556 if(previous_color_point_count == color_draggables.size() && previous_opacity_point_count == opacity_draggables.size()) {
557 update_member(&color_interpolation);
558 update_member(&opacity_interpolation);
559 for(auto& point : color_draggables) {
560 update_member(&point.domain_value);
561 update_member(&point.data);
562 }
563 for(auto& point : opacity_draggables) {
564 update_member(&point.domain_value);
565 update_member(&point.uv.y());
566 }
567 } else {
569 }
570
571 post_damage();
572}
573
574void transfer_function_editor::update_data_from_transfer_function() {
575 if(!transfer_function)
576 return;
577
578 if(!build_time.is_valid() || transfer_function->get_modified_time() > build_time.get_modified_time()) {
579 force_update_data_from_transfer_function();
580 build_time.modified();
581 }
582}
583
584void transfer_function_editor::update_transfer_function_from_data() {
585 if(!transfer_function)
586 return;
587
588 std::vector<std::pair<float, rgb>> colors;
589 std::vector<std::pair<float, float>> opacities;
590
591 const vec2 domain = transfer_function->get_domain();
592
593 auto map_to_domain = [domain](float u) {
594 return cgv::math::map(u, 0.0f, 1.0f, domain[0], domain[1]);
595 };
596
597 std::transform(color_draggables.begin(), color_draggables.end(), std::back_inserter(colors), [&map_to_domain](const color_point& point) {
598 return std::make_pair(map_to_domain(point.uv.x()), point.data);
599 });
600 std::transform(opacity_draggables.begin(), opacity_draggables.end(), std::back_inserter(opacities), [&map_to_domain](const opacity_point& point) {
601 return std::make_pair(map_to_domain(point.uv.x()), point.uv.y());
602 });
603
604 transfer_function->set_color_points(colors);
605 transfer_function->set_opacity_points(opacities);
606
607 if(on_change_callback)
608 on_change_callback();
609}
610
611void transfer_function_editor::rescale_domain() {
612 if(transfer_function) {
613 transfer_function->rescale(input_domain);
614 update_data_from_transfer_function();
615
616 if(on_change_callback)
617 on_change_callback();
618 }
619}
620
621cgv::g2d::draggable* transfer_function_editor::get_hit_point(const vec2& pos) {
622 const auto& contains = [&pos](const auto& draggable) {
623 return draggable.contains(pos);
624 };
625
626 auto color_it = std::find_if(color_draggables.begin(), color_draggables.end(), contains);
627 if(color_it != color_draggables.end())
628 return &*color_it;
629
630 auto opacity_it = std::find_if(opacity_draggables.begin(), opacity_draggables.end(), contains);
631 if(opacity_it != opacity_draggables.end())
632 return &*opacity_it;
633
634 return nullptr;
635}
636
637template<typename T>
638bool is_boundary_point(const T* point, cgv::g2d::draggable_collection<T>& draggables) {
639 if(draggables.empty())
640 return false;
641 return point == &draggables.ref_draggables().front() || point == &draggables.ref_draggables().back();
642}
643
644void transfer_function_editor::add_point(const vec2& pos) {
645 if(!transfer_function)
646 return;
647
648 ivec2 test_pos = static_cast<ivec2>(pos);
649
650 selected_color_draggable = nullptr;
651 selected_opacity_draggable = nullptr;
652
653 if(layout.color_editor_rect.contains(test_pos)) {
654 color_point draggable = make_color_point();
655 draggable.set_position_and_update_uv({ pos.x(), 0.0f });
656 draggable.data = transfer_function->get_mapped_color(draggable.uv.x());
657 size_t index = color_draggables.add(draggable);
658 selected_color_draggable = &color_draggables[index];
659 } else if(supports_opacity && layout.opacity_editor_rect.contains(test_pos)) {
660 opacity_point draggable = make_opacity_point();
661 draggable.set_position_and_update_uv(pos);
662 size_t index = opacity_draggables.add(draggable);
663 selected_opacity_draggable = &opacity_draggables[index];
664 }
665
666 sort_points();
667 update_transfer_function_from_data();
668 handle_selection_change();
669 create_geometry();
670 post_damage();
671}
672
673void transfer_function_editor::erase_point(const cgv::g2d::draggable* point) {
674 const auto& try_erase_point_from = [point](auto& draggables) {
675 if(draggables.size() > 1) {
676 if(point == &draggables.ref_draggables().front() || point == &draggables.ref_draggables().back())
677 return false;
678
679 auto it = std::remove_if(draggables.begin(), draggables.end(), [point](const cgv::g2d::draggable& draggable) {
680 return &draggable == point;
681 });
682 if(it != draggables.end()) {
683 draggables.ref_draggables().erase(it);
684 return true;
685 }
686 }
687 return false;
688 };
689
690 if(try_erase_point_from(color_draggables) || try_erase_point_from(opacity_draggables)) {
691 selected_color_draggable = nullptr;
692 selected_opacity_draggable = nullptr;
693 update_transfer_function_from_data();
694 handle_selection_change();
695 create_preview_texture();
696 create_geometry();
697 post_damage();
698 }
699}
700
701void transfer_function_editor::set_selected_point_domain_value() {
702 if(!transfer_function)
703 return;
704
705 const vec2 domain = transfer_function->get_domain();
706 const float x = cgv::math::clamp(input_position, domain[0], domain[1]);
707 const float u = cgv::math::normalize(x, domain[0], domain[1]);
708
709 if(selected_color_draggable && !is_boundary_point(selected_color_draggable, color_draggables)) {
710 selected_color_draggable->set_uv_and_update_position({ u, 0.0f });
711 update_member(&selected_color_draggable->domain_value);
712 }
713 else if(selected_opacity_draggable && !is_boundary_point(selected_opacity_draggable, opacity_draggables)) {
714 selected_opacity_draggable->set_uv_and_update_position({ u, selected_opacity_draggable->uv.y() });
715 update_member(&selected_opacity_draggable->domain_value);
716 }
717
718 sort_points();
719 update_transfer_function_from_data();
720 create_preview_texture();
721 create_geometry();
722 post_damage();
723}
724
725void transfer_function_editor::set_value_label(vec2 position, const std::string& text) {
726 value_label = text;
727 value_label_rectangle.position = position;
728
729 if(get_context()) {
730 auto& font = cgv::g2d::ref_msdf_font_regular(*get_context());
731 value_label_rectangle.size = font.compute_render_size(value_label, value_label_style.font_size);
732 }
733
734 cgv::g2d::rect constraint = get_content_rectangle();
735 constraint.scale(-0.5f * (value_label_rectangle.size + 4.0f));
736 value_label_rectangle.position = cgv::math::clamp(value_label_rectangle.position, constraint.a(), constraint.b());
737}
738
739void transfer_function_editor::handle_drag(cgv::g2d::DragAction action, DraggableType type) {
740 bool modified = false;
741
742 switch(action) {
743 case cgv::g2d::DragAction::kDragStart:
744 if(type == DraggableType::kColor) {
745 color_point* dragged = color_draggables.get_dragged();
746 if(dragged)
747 color_draggable_start_position = dragged->position;
748 } else if(type == DraggableType::kOpacity) {
749 opacity_point* dragged = opacity_draggables.get_dragged();
750 if(dragged)
751 opacity_draggable_start_position = dragged->position;
752 }
753 case cgv::g2d::DragAction::kDrag:
754 if(type == DraggableType::kColor) {
755 color_point* dragged = color_draggables.get_dragged();
756
757 if(is_boundary_point(dragged, color_draggables))
758 dragged->position = color_draggable_start_position;
759
760 if(dragged) {
761 dragged->set_position_and_update_uv(dragged->position);
762 set_value_label(
763 dragged->position + cgv::vec2(0.0f, 25.0f),
764 value_to_string(dragged->uv.x())
765 );
766 selected_color_draggable = dragged;
767 selected_opacity_draggable = nullptr;
768 modified = true;
769 }
770 } else if(type == DraggableType::kOpacity) {
771 opacity_point* dragged = opacity_draggables.get_dragged();
772
773 if(is_boundary_point(dragged, opacity_draggables))
774 dragged->position.x() = opacity_draggable_start_position.x();
775
776 if(dragged) {
777 dragged->set_position_and_update_uv(dragged->position);
778
779 set_value_label(
780 dragged->position + cgv::vec2(0.0f, 10.0f),
781 value_to_string(dragged->uv.x()) + ", " + cgv::utils::to_string(dragged->uv.y(), -1, 3u)
782 );
783 selected_opacity_draggable = dragged;
784 selected_color_draggable = nullptr;
785 modified = true;
786 }
787 }
788 if(modified)
789 handle_selection_change();
790 break;
791 case cgv::g2d::DragAction::kDragEnd:
792 handle_selection_change();
793 value_label.clear();
794 modified = true;
795 break;
796 default:
797 break;
798 }
799
800 //press_inside = has_constraint && !use_individual_constraints ? constraint_area.contains(mouse_position) : true;
801
802 if(modified) {
803 sort_points();
804 update_transfer_function_from_data();
805 create_preview_texture();
806 create_geometry();
807 post_damage();
808 }
809}
810
811void transfer_function_editor::handle_selection_change() {
812 if(selected_color_draggable) {
813 if(on_color_point_select_callback)
814 on_color_point_select_callback(selected_color_draggable->data);
815 } else {
816 if(on_color_point_deselect_callback)
817 on_color_point_deselect_callback();
818 }
820}
821
822std::string transfer_function_editor::value_to_string(float value) {
823 if(!transfer_function)
824 return "";
825
826 const vec2 domain = transfer_function->get_domain();
827 float display_value = cgv::math::lerp(domain.x(), domain.y(), value);
828
829 float total = domain.y() - domain.x();
830 if(total < 100.0f) {
831 // show as float
832 return cgv::utils::to_string(display_value, -1, 3u);
833 } else {
834 // show as int
835 int display_value_int = static_cast<int>(round(display_value));
836 return std::to_string(display_value_int);
837 }
838}
839
840template<typename T>
841void sort_draggables(cgv::g2d::draggable_collection<T>& draggables, T*& selected) {
842 if(draggables.empty())
843 return;
844
845 int dragged_idx = -1;
846 int selected_idx = -1;
847
848 for(size_t i = 0; i < draggables.size(); ++i) {
849 if(&draggables[i] == draggables.get_dragged())
850 dragged_idx = static_cast<int>(i);
851 if(&draggables[i] == selected)
852 selected_idx = static_cast<int>(i);
853 }
854
855 std::vector<T> draggables_copy = draggables.ref_draggables();
856 std::vector<size_t> permutation = cgv::utils::stable_sort_indices(draggables_copy.begin(), draggables_copy.end());
857
858 for(size_t i = 0; i < permutation.size(); ++i) {
859 int permuted_index = static_cast<int>(permutation[i]);
860 draggables[i] = draggables_copy[permuted_index];
861 if(permuted_index == dragged_idx)
862 draggables.set_dragged(int(i));
863 if(permuted_index == selected_idx)
864 selected = &draggables[i];
865 }
866}
867
868void transfer_function_editor::sort_points() {
869 sort_draggables(color_draggables, selected_color_draggable);
870 if(supports_opacity)
871 sort_draggables(opacity_draggables, selected_opacity_draggable);
872}
873
874void transfer_function_editor::update_point_positions() {
875 for(auto& draggable : color_draggables)
876 draggable.set_position_and_update_uv(draggable.position);
877
878 for(auto& draggable : opacity_draggables)
879 draggable.set_position_and_update_uv(draggable.position);
880}
881
882bool transfer_function_editor::create_preview_texture() {
883 std::vector<rgba> cs_data = transfer_function->quantize(preview_texture_resolution);
884
885 std::vector<cgv::rgba8> texture_data;
886 texture_data.reserve(preview_texture_resolution);
887 std::transform(cs_data.begin(), cs_data.end(), std::back_inserter(texture_data), [](const cgv::rgba& color) {
888 return cgv::rgba8(color);
889 });
890
891 cgv::data::data_format data_format(preview_texture_resolution, 1, cgv::type::info::TI_UINT8, cgv::data::CF_RGBA);
892 cgv::data::data_view data_view(&data_format, texture_data.data());
893
894 const cgv::render::TextureFilter filter = cgv::render::TF_LINEAR;
895
896 if(get_context()) {
897 preview_tex.set_min_filter(filter);
898 preview_tex.set_mag_filter(filter);
899 return preview_tex.create(*get_context(), data_view, 0);
900 }
901 return false;
902}
903
904bool transfer_function_editor::create_background_texture() {
905 const rgb dark(0.75f);
906 const rgb light(0.9f);
907 std::vector<rgb> data = { dark, light, light, dark };
909 cgv::data::data_view data_view = cgv::data::data_view(&format, data.data());
910 if(get_context())
911 return background_tex.create(*get_context(), data_view, 0);
912 return false;
913}
914
915void transfer_function_editor::create_geometry() {
916 color_draggables_geometry.clear();
917 opacity_draggables_geometry.clear();
918 line_geometry.clear();
919 triangle_geometry.clear();
920
921 vec2 pos_offset = vec2(0.0f, 0.5f * color_point_size.y());
922
923 for(const auto& draggable : color_draggables) {
924 vec2 pos = draggable.center();
925 rgba col = selected_color_draggable == &draggable ? highlight_color : handle_color;
926 color_draggables_geometry.add(pos - pos_offset, col);
927 color_draggables_geometry.add(pos + pos_offset, col);
928 }
929
930 if(!opacity_draggables.empty()) {
931 const auto& first = *opacity_draggables.begin();
932
933 vec2 tex_coord(0.0f, 0.5f);
934
935 line_geometry.add(vec2(static_cast<float>(layout.opacity_editor_rect.x()), first.center().y()), tex_coord);
936
937 triangle_geometry.add(vec2(static_cast<float>(layout.opacity_editor_rect.x()), first.center().y()), tex_coord);
938 triangle_geometry.add(layout.opacity_editor_rect.position, tex_coord);
939
940 for(const auto& draggable : opacity_draggables) {
941 vec2 pos = draggable.center();
942 rgba col = selected_opacity_draggable == &draggable ? highlight_color : handle_color;
943 opacity_draggables_geometry.add(pos, col);
944
945 tex_coord.x() = draggable.uv.x();
946
947 line_geometry.add(pos, tex_coord);
948
949 triangle_geometry.add(pos, tex_coord);
950 triangle_geometry.add(vec2(pos.x(), static_cast<float>(layout.opacity_editor_rect.y())), tex_coord);
951 }
952
953 const auto& last = *(--opacity_draggables.end());
954 vec2 max_pos = layout.opacity_editor_rect.position + vec2(1.0f, 0.0f) * layout.opacity_editor_rect.size;
955
956 tex_coord.x() = 1.0f;
957
958 line_geometry.add(vec2(max_pos.x(), last.center().y()), tex_coord);
959
960 triangle_geometry.add(vec2(max_pos.x(), last.center().y()), tex_coord);
961 triangle_geometry.add(max_pos, tex_coord);
962 }
963}
964
965} // namespace overlay
966} // namespace cgv
More advanced text processing for splitting text into lines or tokens.
void set_name(const std::string &_name)
set a new parent node
Definition named.cxx:13
A data_format describes a multidimensional data block of data entries.
Definition data_format.h:17
the data view gives access to a data array of one, two, three or four dimensions.
Definition data_view.h:153
This class provides methods to test if a stored pointer points to addresses of given variables or ins...
bool points_to_data_of(const std::array< T, N > &array) const
Return true if the stored pointer points to an element of the data of the given std::array.
bool points_to_one_of(const T &ref, const Ts &... refs) const
Return true if the stored pointer points to one of the given objects.
bool points_to(const T &ref) const
Return true if the stored pointer points to the given object.
class to represent all possible mouse events with the EID_MOUSE
Definition mouse_event.h:33
cgv::base::base_ptr add_decorator(const std::string &label, const std::string &decorator_type, const std::string &options="", const std::string &align="\n")
add a newly created decorator to the group
Definition provider.cxx:168
void align(const std::string &_align)
send pure alignment information
Definition provider.cxx:36
bool begin_tree_node(const std::string &label, const T &value, bool initial_visibility=false, const std::string &options="", gui_group_ptr ggp=gui_group_ptr())
Begin a sub tree of a tree structured gui.
Definition provider.h:212
virtual void update_member(void *member_ptr)
call this to update all views and controls of a member
Definition provider.cxx:72
data::ref_ptr< control< T > > add_member_control(cgv::base::base *base_ptr, const std::string &label, T &value, const std::string &gui_type="", const std::string &options="", const std::string &align="\n")
add control with callback to cgv::base::on_set method on cgv::gui::control::value_change
Definition provider.h:137
void end_tree_node(const T &value)
template specialization that allows to specify value reference plus node_instance by using the result...
Definition provider.h:222
virtual void post_recreate_gui()
delayed recreation of gui
Definition provider.cxx:509
data::ref_ptr< view< T > > add_view(const std::string &label, const T &value, const std::string &gui_type="", const std::string &options="", const std::string &align="\n")
use this to add a new view to the gui with a given value type, gui type and init options
Definition provider.h:112
button_ptr add_button(const std::string &label, const std::string &options="", const std::string &align="\n")
use the current gui driver to append a new button with the given label
Definition provider.cxx:176
T & y()
return second component
Definition fvec.h:140
T & x()
return first component
Definition fvec.h:136
static cgv::type::uint32_type size()
return number of components
Definition fvec.h:134
bool init(cgv::render::context &ctx) override
this method is called after creation or recreation of the context, return whether all necessary funct...
virtual void on_set(void *member_ptr) override
default implementation of that calls handle_member_change and afterwards upates the member in the gui...
void clear(cgv::render::context &ctx) override
clear all objects living in the context like textures or display lists
void set_size(const ivec2 &size)
set the default size of the overlay before stretch gets applied
Definition overlay.cxx:111
bool blocks_events() const
return whether this overlay blocks events, i.e. does not pass them to the next event handler
Definition overlay.h:83
cgv::g2d::irect get_rectangle() const
return the current rectangle area (in screen coordinates) of the overlay taking layout into account
Definition overlay.h:95
void update_layout()
update the layout of the overlay container
Definition overlay.cxx:215
ivec2 get_viewport_size() const
return the current viewport size
Definition overlay.h:89
virtual void handle_theme_change(const cgv::gui::theme_info &theme) override
override in your class to handle theme changes
cgv::g2d::irect get_content_rectangle() const
return the current rectangle area of the themed_overlay content
void clear(cgv::render::context &ctx) override
clear all objects living in the context like textures or display lists
void handle_theme_change(const cgv::gui::theme_info &theme) override
override in your class to handle theme changes
void create_gui_impl() override
virtual method to implement the derived class gui creation
void handle_member_change(cgv::data::informed_ptr ptr) override
implement to handle member changes
void init_frame(cgv::render::context &ctx) override
this method is called in one pass over all drawables before the draw method
void draw_content(cgv::render::context &ctx) override
draw_content is implemented by decendent classes
bool handle_mouse_event(cgv::gui::mouse_event &e, cgv::ivec2 local_mouse_pos) override
overload this method to handle mouse events; local_mouse_pos is the mouse position in the local coord...
bool init(cgv::render::context &ctx) override
this method is called after creation or recreation of the context, return whether all necessary funct...
base class for all drawables, which is independent of the used rendering API.
Definition context.h:670
context * get_context() const
access the current context. The context will be available latestly in the init method but not in the ...
Definition drawable.cxx:37
virtual bool is_created() const
return whether component has been created
Definition context.cxx:2226
void set_mag_filter(TextureFilter _mag_filter)
set the magnification filter
Definition texture.cxx:144
bool create(const context &ctx, TextureType _tt=TT_UNDEF, unsigned width=-1, unsigned height=-1, unsigned depth=-1)
create the texture of dimension and resolution specified in the data format base class.
Definition texture.cxx:214
bool disable(const context &ctx)
disable texture and restore state from before last enable call
Definition texture.cxx:752
bool enable(const context &ctx, int tex_unit=-1)
enable this texture in the given texture unit, -1 corresponds to the current unit.
Definition texture.cxx:741
bool destruct(const context &ctx)
destruct the texture and free texture memory and handle
Definition texture.cxx:726
void set_min_filter(TextureFilter _min_filter, float _anisotropy=2.0f)
set the minification filters, if minification is set to TF_ANISOTROP, the second floating point param...
Definition texture.cxx:126
@ CF_RGBA
color format with components R, G and B
@ CF_R
undefinded format with no component
@ CF_RGB
color format with two components R and G
@ MB_RIGHT_BUTTON
right button
Definition mouse_event.h:28
@ MB_LEFT_BUTTON
left button
Definition mouse_event.h:26
@ MA_PRESS
mouse button pressed
Definition mouse_event.h:13
@ TA_BOTTOM
center of bottom edge of text bounds
Definition context.h:334
TextureFilter
different texture filter
Definition context.h:245
@ TI_FLT32
floating point type stored in 16 bits
Definition type_id.h:28
@ TI_UINT8
signed integer stored in 64 bits
Definition type_id.h:23
std::string to_string(const std::string &v, unsigned int w, unsigned int p, bool)
specialization of conversion from string to strings
std::vector< size_t > stable_sort_indices(const RandomIt first, const RandomIt last)
Return a sequence of indices corresponding to the sorted order of values in [first,...
Definition algorithm.h:298
this header is dependency free
Definition print.h:11
cgv::media::color< float, cgv::media::RGB, cgv::media::OPACITY > rgba
declare rgba color type with 32 bit components
Definition color.h:898
cgv::media::color< float, cgv::media::RGB > rgb
declare rgb color type with 32 bit components
Definition color.h:896
cgv::math::fvec< int32_t, 2 > ivec2
declare type of 2d 32 bit integer vectors
Definition fvec.h:708
cgv::math::fvec< float, 2 > vec2
declare type of 2d single precision floating point vectors
Definition fvec.h:681