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
20const vec2 transfer_function_editor::color_point_size = { 12.0f, 18.0f };
21const vec2 transfer_function_editor::opacity_point_size = { 12.0f };
22
23transfer_function_editor::transfer_function_editor() {
24 set_name("Color Scale Editor");
25 blocks_events(true);
26
27 layout.total_height = supports_opacity ? 200 : 60;
28
29 set_size(ivec2(600u, layout.total_height));
30
31 color_draggables.set_constraint(layout.color_draggables_rect);
32 color_draggables.callback = std::bind(&transfer_function_editor::handle_drag, this, std::placeholders::_1, DraggableType::kColor);
33
34 opacity_draggables.set_constraint(layout.opacity_editor_rect);
35 opacity_draggables.callback = std::bind(&transfer_function_editor::handle_drag, this, std::placeholders::_1, DraggableType::kOpacity);
36
37 color_handle_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::arrow);
38 opacity_handle_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::rectangle);
39 line_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::line);
40 polygon_renderer = cgv::g2d::generic_2d_renderer(cgv::g2d::shaders::polygon);
41}
42
44 cgv::g2d::ref_msdf_font_regular(ctx, -1);
45 cgv::g2d::ref_msdf_gl_font_renderer_2d(ctx, -1);
46
48
49 color_handle_renderer.destruct(ctx);
50 opacity_handle_renderer.destruct(ctx);
51 line_renderer.destruct(ctx);
52 polygon_renderer.destruct(ctx);
53
54 background_tex.destruct(ctx);
55 preview_tex.destruct(ctx);
56 histogram_tex.destruct(ctx);
57}
58
60 bool capture_event = false;
61
62 //bool request_clear_selection = false;
63
64 if(e.get_action() == cgv::gui::MA_PRESS) {
65 if(e.get_button() & cgv::gui::MB_LEFT_BUTTON) {
66 //request_clear_selection = is_hit_local(local_mouse_pos);
67
68 if(!get_hit_point(local_mouse_pos)) {
69 add_point(local_mouse_pos);
70 //request_clear_selection = false;
71 }
72 capture_event = true;
73 } else if(e.get_button() & cgv::gui::MB_RIGHT_BUTTON) {
74 cgv::g2d::draggable* hit_point = get_hit_point(local_mouse_pos);
75 if(hit_point)
76 erase_point(hit_point);
77 }
78 }
79
80 if(color_draggables.handle(e, get_viewport_size(), get_rectangle()))
81 return true;
82 if(opacity_draggables.handle(e, get_viewport_size(), get_rectangle()))
83 return true;
84 /*
85 if(request_clear_selection) {
86 selected_color_draggable = nullptr;
87 selected_opacity_draggable = nullptr;
88 handle_selection_change();
89 }*/
90 if (capture_event)
91 return true;
92 return false;
93}
94
96 if(ptr.points_to(layout.total_height)) {
97 ivec2 size = get_rectangle().size;
98 size.y() = layout.total_height;
99 set_size(size);
100 }
101
102 if(ptr.points_to(opacity_scale_exponent)) {
103 opacity_scale_exponent = cgv::math::clamp(opacity_scale_exponent, 1.0f, 5.0f);
104
105 update_point_positions();
106 sort_points();
107 create_geometry();
108 }
109
110 /*
111 if(ptr.points_to(discretization_resolution)) {
112 if(cmc.cm)
113 cmc.cm->set_resolution(resolution);
114 update_color_map(false);
115 }
116 */
117
118 if(ptr.points_to_one_of(color_interpolation, opacity_interpolation)) {
119 if(transfer_function) {
120 transfer_function->set_color_interpolation(color_interpolation);
121 if(supports_opacity)
122 transfer_function->set_opacity_interpolation(opacity_interpolation);
123
124 create_preview_texture();
125 post_damage();
126
127 if(on_change_callback)
128 on_change_callback();
129 }
130 }
131
132 if(ptr.points_to_data_of(color_draggables.ref_draggables())) {
133 update_transfer_function_from_data();
134 create_preview_texture();
135 create_geometry();
136 post_damage();
137 }
138
139 for(auto& draggable : opacity_draggables) {
140 if(ptr.points_to(draggable.uv.y())) {
141 draggable.set_uv_and_update_position(draggable.uv);
142 update_transfer_function_from_data();
143 create_geometry();
144 post_damage();
145 break;
146 }
147 }
148
149 if(ptr.points_to(supports_opacity)) {
150 // Todo: Fixme: This will overwrite the user-defined total height.
151 layout.total_height = supports_opacity ? 200 : 60;
152 on_set(&layout.total_height);
153
154 if(!supports_opacity) {
155 if(transfer_function) {
156 //transfer_function->clear_opacity_points();
157 //opacity_draggables.clear();
158
159 update_point_positions();
160 //update_color_map(false);
161 }
162 }
163
164 post_recreate_layout();
166 }
167}
168
170 cgv::g2d::ref_msdf_font_regular(ctx, 1);
171 cgv::g2d::ref_msdf_gl_font_renderer_2d(ctx, 1);
172
173 register_shader("rectangle", cgv::g2d::shaders::rectangle);
174 register_shader("circle", cgv::g2d::shaders::circle);
175 register_shader("histogram", "heightfield1d.glpr");
176 //Todo: Rename shader program?
177 register_shader("background", "color_map_editor_bg.glpr");
178
179 bool success = canvas_overlay::init(ctx);
180
181 success &= color_handle_renderer.init(ctx);
182 success &= opacity_handle_renderer.init(ctx);
183 success &= line_renderer.init(ctx);
184 success &= polygon_renderer.init(ctx);
185 success &= create_background_texture();
186
187 //update_color_map(false);
188
189 return success;
190}
191
193 if(ensure_layout(ctx)) {
194 ivec2 container_size = get_rectangle().size;
195
196 update_layout(container_size);
197 force_update_data_from_transfer_function();
198
199 auto& bg_prog = content_canvas.enable_shader(ctx, "background");
200 float width_factor = static_cast<float>(layout.opacity_editor_rect.w()) / static_cast<float>(layout.opacity_editor_rect.h());
201 bg_style.texcoord_scaling = vec2(5.0f * width_factor, 5.0f);
202 bg_style.apply(ctx, bg_prog);
203 content_canvas.disable_current_shader(ctx);
204
205 //update_point_positions();
207 //update_geometry();
208
209 color_draggables.set_constraint(layout.color_draggables_rect);
210 opacity_draggables.set_constraint(layout.opacity_editor_rect);
211 }
212}
213
214void transfer_function_editor::draw_content(cgv::render::context& ctx) {
215 begin_content(ctx);
216
217 // draw inner border
218 ivec2 container_size = get_rectangle().size;
219 content_canvas.enable_shader(ctx, "rectangle");
220 content_canvas.set_style(ctx, border_style);
221 content_canvas.draw_shape(ctx, ivec2(padding() - 1) + ivec2(0, 10), container_size - 2 * padding() + 2 - ivec2(0, 10));
222
223 if(transfer_function && preview_tex.is_created()) {
224 // draw color scale texture
225 content_canvas.set_style(ctx, color_map_style);
226 preview_tex.enable(ctx, 0);
227 content_canvas.draw_shape(ctx, layout.color_editor_rect);
228 preview_tex.disable(ctx);
229
230 if(supports_opacity) {
231 // draw opacity editor checkerboard background
232 auto& bg_prog = content_canvas.enable_shader(ctx, "background");
233 bg_style.apply(ctx, bg_prog);
234 bg_prog.set_uniform(ctx, "scale_exponent", opacity_scale_exponent);
235 background_tex.enable(ctx, 0);
236 content_canvas.draw_shape(ctx, layout.opacity_editor_rect);
237 background_tex.disable(ctx);
238 content_canvas.disable_current_shader(ctx);
239
240 // draw histogram
241 if(histogram_type != HistogramType::kNone && histogram_tex.is_created()) {
242 auto& hist_prog = content_canvas.enable_shader(ctx, "histogram");
243 hist_prog.set_uniform(ctx, "max_value", hist_norm_ignore_zero ? hist_max_non_zero : hist_max);
244 hist_prog.set_uniform(ctx, "norm_gamma", hist_norm_gamma);
245 hist_prog.set_uniform(ctx, "sampling_type", cgv::math::clamp(static_cast<unsigned>(histogram_type) - 1, 0u, 2u));
246 hist_style.apply(ctx, hist_prog);
247
248 histogram_tex.enable(ctx, 1);
249 content_canvas.draw_shape(ctx, layout.opacity_editor_rect);
250 histogram_tex.disable(ctx);
251 content_canvas.disable_current_shader(ctx);
252 }
253
254 preview_tex.enable(ctx, 0);
255 // draw transfer function area polygon
256 polygon_renderer.render(ctx, content_canvas, cgv::render::PT_TRIANGLE_STRIP, triangle_geometry, polygon_style);
257
258 // draw transfer function lines
259 line_renderer.render(ctx, content_canvas, cgv::render::PT_LINE_STRIP, line_geometry, line_style);
260 preview_tex.disable(ctx);
261
262 // draw separator line
263 content_canvas.enable_shader(ctx, "rectangle");
264 content_canvas.set_style(ctx, border_style);
265 content_canvas.draw_shape(ctx,
266 ivec2(layout.color_editor_rect.x(), layout.color_editor_rect.y1()),
267 ivec2(container_size.x() - 2 * padding(), 1)
268 );
269 content_canvas.disable_current_shader(ctx);
270 }
271
272 // draw control points
273 // color handles
274 color_handle_renderer.render(ctx, content_canvas, cgv::render::PT_LINES, color_draggables_geometry, color_handle_style);
275
276 // opacity handles
277 if(supports_opacity) {
278 auto& opacity_handle_prog = opacity_handle_renderer.enable_prog(ctx);
279 opacity_handle_prog.set_attribute(ctx, "size", opacity_point_size); // size is constant for all points
280 opacity_handle_renderer.render(ctx, content_canvas, cgv::render::PT_POINTS, opacity_draggables_geometry, opacity_handle_style);
281 }
282 } else {
283 content_canvas.disable_current_shader(ctx);
284 }
285
286 auto& font = cgv::g2d::ref_msdf_font_regular(ctx);
287 auto& font_renderer = cgv::g2d::ref_msdf_gl_font_renderer_2d(ctx);
288 cgv::g2d::msdf_gl_font_renderer::text_render_info text_render_info;
289
290 if(!value_label.empty()) {
291 cgv::g2d::irect rectangle = value_label_rectangle;
292 rectangle.translate(0, 5);
293 rectangle.size += ivec2(10, 6);
294
295 content_canvas.enable_shader(ctx, "rectangle");
296 content_canvas.set_style(ctx, label_box_style);
297 content_canvas.draw_shape(ctx, rectangle);
298 content_canvas.disable_current_shader(ctx);
299
300 text_render_info.alignment = cgv::render::TextAlignment::TA_BOTTOM;
301 font_renderer.render(ctx, content_canvas, font, value_label, value_label_rectangle.position, text_render_info, value_label_style);
302 }
303
304 end_content(ctx);
305}
306
312
314 if(begin_tree_node("Settings", layout, false)) {
315 align("\a");
316 std::string height_options = "min=";
317 height_options += supports_opacity ? "80" : "40";
318 height_options += ";max=500;step=10;ticks=true";
319 add_member_control(this, "Height", layout.total_height, "value_slider", height_options);
320 //add_member_control(this, "Opacity Scale Exponent", opacity_scale_exponent, "value_slider", "min=1.0;max=5.0;step=0.001;ticks=true");
321 //add_member_control(this, "Resolution", discretization_resolution, "dropdown", "enums='2=2,4=4,8=8,16=16,32=32,64=64,128=128,256=256,512=512,1024=1024,2048=2048';w=106", " ");
322 align("\b");
323 end_tree_node(layout);
324 }
325
326 add_decorator("Color", "heading", "level=4;font_style=regular;w=94", " ");
327 add_decorator("Opacity", "heading", "level=4;font_style=regular;w=94", "%y-=6\n");
328 const std::string interpolation_enums = "enums='Step,Linear,Smooth'";
329 add_member_control(this, "Interpolation", color_interpolation, "dropdown", interpolation_enums + ";w=94", " ");
330 add_member_control(this, "", opacity_interpolation, "dropdown", interpolation_enums + ";w=94");
331
332 add_member_control(this, "Domain", input_domain[0], "value", "step=0.001;w=94", " ");
333 add_member_control(this, "", input_domain[1], "value", "step=0.001;w=94");
334 cgv::signal::connect_copy(add_button("Rescale")->click, cgv::signal::rebind(this, &transfer_function_editor::rescale_domain));
335
336 // Todo: Add input and button to set point position through GUI.
337
338 if(begin_tree_node("Color Points", color_draggables, false)) {
339 align("\a");
340 auto& points = color_draggables;
341 for(unsigned i = 0; i < points.size(); ++i) {
342 std::string label_prefix = "";
343 std::string options = "w=48";
344 if(&points[i] == selected_color_draggable) {
345 label_prefix = "> ";
346 options += ";label_color=" + cgv::media::to_hex(highlight_color);
347 }
348
349 add_view(label_prefix + std::to_string(i), points[i].uv.x(), "", options, " ");
350 add_member_control(this, "", points[i].data, "", "w=140");
351 }
352 align("\b");
353 end_tree_node(color_draggables);
354 }
355
356 if(supports_opacity) {
357 if(begin_tree_node("Opacity Points", opacity_draggables, false)) {
358 align("\a");
359 auto& points = opacity_draggables;
360 for(unsigned i = 0; i < points.size(); ++i) {
361 std::string label_prefix = "";
362 std::string options = "w=48";
363 if(&points[i] == selected_opacity_draggable) {
364 label_prefix = "> ";
365 options += ";label_color=" + cgv::media::to_hex(highlight_color);
366 }
367
368 add_view(label_prefix + std::to_string(i), points[i].uv.x(), "", options, " ");
369 add_member_control(this, "", points[i].uv.y(), "value", "w=140");
370 }
371 align("\b");
372 end_tree_node(opacity_draggables);
373 }
374 }
375
376 add_member_control(this, "New position", input_position, "value", "step=0.001");
377 cgv::signal::connect_copy(add_button("Set selected position")->click, cgv::signal::rebind(this, &transfer_function_editor::set_selected_point_domain_value));
378
379 if(begin_tree_node("Histogram", histogram, false)) {
380 align("\a");
381 add_member_control(this, "Type", histogram_type, "dropdown", "enums='None,Nearest,Linear,Smooth'");
382 add_member_control(this, "Ignore Zero for Normalization", hist_norm_ignore_zero, "check");
383 add_member_control(this, "Gamma", hist_norm_gamma, "value_slider", "min=0.001;max=2;step=0.001;ticks=true");
384 add_member_control(this, "Fill Color", hist_style.fill_color);
385 add_member_control(this, "Border Color", hist_style.border_color);
386 add_member_control(this, "Border Width", hist_style.border_width, "value_slider", "min=0;max=10;step=0.5;ticks=true");
387 align("\b");
388 end_tree_node(histogram);
389 }
390}
391
392void transfer_function_editor::set_opacity_support(bool flag) {
393 supports_opacity = flag;
394 //set_transfer_function(transfer_function);
395 on_set(&supports_opacity);
396}
397
398void transfer_function_editor::set_transfer_function(std::shared_ptr<cgv::media::transfer_function> transfer_function) {
399 if(this->transfer_function != transfer_function) {
400 this->transfer_function = transfer_function;
401 build_time.reset();
402 }
403
404 this->transfer_function = transfer_function;
405 update_data_from_transfer_function();
406 post_damage();
407}
408
409void transfer_function_editor::set_histogram_data(const std::vector<unsigned> data) {
410 histogram = data;
411
412 std::vector<float> float_data(histogram.size(), 0.0f);
413 hist_max = 1;
414 hist_max_non_zero = 1;
415 for(size_t i = 0; i < histogram.size(); ++i) {
416 unsigned count = histogram[i];
417 hist_max = std::max(hist_max, count);
418 if(i > 0)
419 hist_max_non_zero = std::max(hist_max_non_zero, count);
420 float_data[i] = static_cast<float>(count);
421 }
422
423 if(get_context()) {
424 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());
425 histogram_tex.create(*get_context(), data_view, 0);
426 post_damage();
427 }
428}
429
430void transfer_function_editor::set_selected_color(rgb color) {
431 if(selected_color_draggable) {
432 selected_color_draggable->data = color;
433 on_set(&selected_color_draggable->data);
434 post_damage();
435 }
436}
437
438void transfer_function_editor::init_styles() {
439 auto& theme = cgv::gui::theme_info::instance();
440 // Todo: Check if alpha is set to 1.
441 handle_color = theme.text();
442 highlight_color = theme.highlight();
443
444 // configure style for the border rectangles
445 border_style.fill_color = theme.border();
446 border_style.border_width = 0.0f;
447 border_style.feather_width = 0.0f;
448
449 // configure style for the color scale rectangle
450 color_map_style = border_style;
451 color_map_style.use_texture = true;
452
453 // configure style for background
454 bg_style.use_texture = true;
455 bg_style.feather_width = 0.0f;
456
457 // configure style for histogram
458 hist_style.use_blending = true;
459 hist_style.feather_width = 1.0f;
460 hist_style.feather_origin = 0.0f;
461 hist_style.fill_color = rgba(rgb(0.5f), 0.666f);
462 hist_style.border_color = rgba(rgb(0.0f), 0.666f);
463 hist_style.border_width = 1.0f;
464
465 // configure style for color handles
466 color_handle_style.use_blending = true;
467 color_handle_style.use_fill_color = false;
468 color_handle_style.position_is_center = true;
469 color_handle_style.border_color = theme.border();
470 color_handle_style.border_width = 1.5f;
471 color_handle_style.border_radius = 2.0f;
472 color_handle_style.stem_width = color_point_size.x();
473 color_handle_style.head_width = color_point_size.x();
474
475 label_box_style.position_is_center = true;
476 label_box_style.use_blending = true;
477 label_box_style.fill_color = handle_color;
478 label_box_style.border_color = theme.border();
479 label_box_style.border_width = 1.5f;
480 label_box_style.border_radius = 4.0f;
481
482 // configure style for opacity handles
483 opacity_handle_style.use_blending = true;
484 opacity_handle_style.use_fill_color = false;
485 opacity_handle_style.position_is_center = true;
486 opacity_handle_style.border_color = theme.border();
487 opacity_handle_style.border_width = 1.5f;
488
489 // configure style for the lines and polygon
490 line_style.use_blending = true;
491 line_style.use_fill_color = false;
492 line_style.use_texture = true;
493 line_style.use_texture_alpha = false;
494 line_style.width = 3.0f;
495
496 polygon_style = static_cast<cgv::g2d::shape2d_style>(line_style);
497 polygon_style.use_texture_alpha = true;
498
499 // label style
500 cursor_label_style.fill_color = rgb(0.0f);
501 cursor_label_style.font_size = 16.0f;
502
503 value_label_style.fill_color = theme.group();
504 value_label_style.font_size = 12.0f;
505}
506
507void transfer_function_editor::update_layout(const ivec2& parent_size) {
508 int content_height = layout.total_height - 10 - 2 * padding();
509 if(supports_opacity) {
510 layout.color_editor_height = static_cast<int>(floor(0.15f * static_cast<float>(content_height)));
511 layout.color_editor_height = cgv::math::clamp(layout.color_editor_height, 4, 80);
512 layout.opacity_editor_height = content_height - layout.color_editor_height - 1;
513 } else {
514 layout.color_editor_height = content_height;
515 layout.opacity_editor_height = 0;
516 }
517
518 int y_off = padding();
519
520 layout.color_draggables_rect = {
521 ivec2(padding(), 16),
522 ivec2(parent_size.x() - 2 * padding(), 0)
523 };
524
525 // move 10px up to clear some space for the color handles rect
526 y_off += 10;
527
528 layout.color_editor_rect.position = ivec2(padding(), y_off);
529 layout.color_editor_rect.size = ivec2(parent_size.x() - 2 * padding(), layout.color_editor_height);
530
531 y_off += layout.color_editor_height + 1; // plus 1px border
532
533 layout.opacity_editor_rect.position = ivec2(padding(), y_off);
534 layout.opacity_editor_rect.size = ivec2(parent_size.x() - 2 * padding(), layout.opacity_editor_height);
535}
536
537void transfer_function_editor::clear_data() {
538 selected_color_draggable = nullptr;
539 selected_opacity_draggable = nullptr;
540 color_draggables.clear();
541 opacity_draggables.clear();
542 color_draggables_geometry.clear();
543 opacity_draggables_geometry.clear();
544 line_geometry.clear();
545 triangle_geometry.clear();
546}
547
548void transfer_function_editor::force_update_data_from_transfer_function() {
549 if(!transfer_function)
550 return;
551
552 clear_data();
553
554 const vec2 domain = transfer_function->get_domain();
555 input_domain = domain;
556 update_member(&input_domain[0]);
557 update_member(&input_domain[1]);
558
559 for(const auto& point : transfer_function->get_color_points()) {
560 color_point draggable = make_color_point();
561 draggable.data = point.second;
562 const vec2 uv = { cgv::math::normalize(point.first, domain[0], domain[1]), 0.0f };
563 draggable.set_uv_and_update_position(uv);
564 color_draggables.add(draggable);
565 }
566
567 if(supports_opacity) {
568 for(const auto& point : transfer_function->get_opacity_points()) {
569 opacity_point draggable = make_opacity_point();
570 const vec2 uv = { cgv::math::normalize(point.first, domain[0], domain[1]), point.second };
571 draggable.set_uv_and_update_position(uv);
572 opacity_draggables.add(draggable);
573 }
574 }
575
576 create_geometry();
577 create_preview_texture();
578
580 post_damage();
581}
582
583void transfer_function_editor::update_data_from_transfer_function() {
584 if(!transfer_function)
585 return;
586
587 // Todo: Only update if the tf changed or if forced.
588 if(!build_time.is_valid() || transfer_function->get_modified_time() > build_time.get_modified_time()) {
589 std::cout << "set editor data from tf" << std::endl;
590 //std::cout << transfer_function->get_modified_time().time_since_epoch().count() << " > " << build_time.get_modified_time().time_since_epoch().count() << std::endl;
591
592 // Todo: Need force_update?
593 force_update_data_from_transfer_function();
594
595 build_time.modified();
596 }
597}
598
599void transfer_function_editor::update_transfer_function_from_data() {
600 if(!transfer_function)
601 return;
602
603 std::vector<std::pair<float, rgb>> colors;
604 std::vector<std::pair<float, float>> opacities;
605
606 const vec2 domain = transfer_function->get_domain();
607
608 auto map_to_domain = [domain](float u) {
609 return cgv::math::map(u, 0.0f, 1.0f, domain[0], domain[1]);
610 };
611
612 std::transform(color_draggables.begin(), color_draggables.end(), std::back_inserter(colors), [&map_to_domain](const color_point& point) {
613 return std::make_pair(map_to_domain(point.uv.x()), point.data);
614 });
615 std::transform(opacity_draggables.begin(), opacity_draggables.end(), std::back_inserter(opacities), [&map_to_domain](const opacity_point& point) {
616 return std::make_pair(map_to_domain(point.uv.x()), point.uv.y());
617 });
618
619 transfer_function->set_color_points(colors);
620 transfer_function->set_opacity_points(opacities);
621
622 // Set the build time to the transfer function's modified time since the geometry is already synchronized.
623 // This avoids a rebuild at the next draw.
624 //build_time = transfer_function->get_modified_time();
625 //build_time.modified();
626
627 if(on_change_callback)
628 on_change_callback();
629}
630
631void transfer_function_editor::rescale_domain() {
632 if(transfer_function) {
633 transfer_function->rescale(input_domain);
634 update_data_from_transfer_function();
635
636 if(on_change_callback)
637 on_change_callback();
638 }
639}
640
641cgv::g2d::draggable* transfer_function_editor::get_hit_point(const vec2& pos) {
642 const auto& contains = [&pos](const auto& draggable) {
643 return draggable.contains(pos);
644 };
645
646 auto color_it = std::find_if(color_draggables.begin(), color_draggables.end(), contains);
647 if(color_it != color_draggables.end())
648 return &*color_it;
649
650 auto opacity_it = std::find_if(opacity_draggables.begin(), opacity_draggables.end(), contains);
651 if(opacity_it != opacity_draggables.end())
652 return &*opacity_it;
653
654 return nullptr;
655}
656
657template<typename T>
658bool is_boundary_point(const T* point, cgv::g2d::draggable_collection<T>& draggables) {
659 if(draggables.empty())
660 return false;
661 return point == &draggables.ref_draggables().front() || point == &draggables.ref_draggables().back();
662}
663
664void transfer_function_editor::add_point(const vec2& pos) {
665 if(!transfer_function)
666 return;
667
668 ivec2 test_pos = static_cast<ivec2>(pos);
669
670 selected_color_draggable = nullptr;
671 selected_opacity_draggable = nullptr;
672
673 if(layout.color_editor_rect.contains(test_pos)) {
674 color_point draggable = make_color_point();
675 draggable.set_position_and_update_uv({ pos.x(), 0.0f });
676 draggable.data = transfer_function->get_mapped_color(draggable.uv.x());
677 size_t index = color_draggables.add(draggable);
678 // Todo: Allow drag after add.
679 //color_draggables.set_dragged(index);
680 selected_color_draggable = &color_draggables[index];
681 } else if(supports_opacity && layout.opacity_editor_rect.contains(test_pos)) {
682 opacity_point draggable = make_opacity_point();
683 draggable.set_position_and_update_uv(pos);
684 size_t index = opacity_draggables.add(draggable);
685 selected_opacity_draggable = &opacity_draggables[index];
686 }
687
688 sort_points();
689 update_transfer_function_from_data();
690 handle_selection_change();
691 create_geometry();
692 post_damage();
693}
694
695void transfer_function_editor::erase_point(const cgv::g2d::draggable* point) {
696 const auto& try_erase_point_from = [point](auto& draggables) {
697 if(draggables.size() > 1) {
698 if(point == &draggables.ref_draggables().front() || point == &draggables.ref_draggables().back())
699 return false;
700
701 auto it = std::remove_if(draggables.begin(), draggables.end(), [point](const cgv::g2d::draggable& draggable) {
702 return &draggable == point;
703 });
704 if(it != draggables.end()) {
705 draggables.ref_draggables().erase(it);
706 return true;
707 }
708 }
709 return false;
710 };
711
712 if(try_erase_point_from(color_draggables) || try_erase_point_from(opacity_draggables)) {
713 selected_color_draggable = nullptr;
714 selected_opacity_draggable = nullptr;
715 update_transfer_function_from_data();
716 handle_selection_change();
717 create_preview_texture();
718 create_geometry();
719 post_damage();
720 }
721}
722
723void transfer_function_editor::set_selected_point_domain_value() {
724 if(!transfer_function)
725 return;
726
727 const vec2 domain = transfer_function->get_domain();
728 const float x = cgv::math::clamp(input_position, domain[0], domain[1]);
729 const float u = cgv::math::normalize(x, domain[0], domain[1]);
730
731 if(selected_color_draggable && !is_boundary_point(selected_color_draggable, color_draggables))
732 selected_color_draggable->set_uv_and_update_position({ u, 0.0f });
733 else if(selected_opacity_draggable && !is_boundary_point(selected_opacity_draggable, opacity_draggables))
734 selected_opacity_draggable->set_uv_and_update_position({ u, selected_opacity_draggable->uv.y() });
735
736 sort_points();
737 update_transfer_function_from_data();
738 create_preview_texture();
739 create_geometry();
740 post_damage();
741}
742
743void transfer_function_editor::set_value_label(vec2 position, const std::string& text) {
744 value_label = text;
745 value_label_rectangle.position = position;
746
747 if(get_context()) {
748 auto& font = cgv::g2d::ref_msdf_font_regular(*get_context());
749 value_label_rectangle.size = font.compute_render_size(value_label, value_label_style.font_size);
750 }
751
752 cgv::g2d::rect constraint = get_content_rectangle();
753 constraint.scale(-0.5f * (value_label_rectangle.size + 4.0f));
754 value_label_rectangle.position = cgv::math::clamp(value_label_rectangle.position, constraint.a(), constraint.b());
755}
756
757void transfer_function_editor::handle_drag(cgv::g2d::DragAction action, DraggableType type) {
758 bool modified = false;
759
760 switch(action) {
761 case cgv::g2d::DragAction::kDragStart:
762 if(type == DraggableType::kColor) {
763 color_point* dragged = color_draggables.get_dragged();
764 if(dragged)
765 color_draggable_start_position = dragged->position;
766 } else if(type == DraggableType::kOpacity) {
767 opacity_point* dragged = opacity_draggables.get_dragged();
768 if(dragged)
769 opacity_draggable_start_position = dragged->position;
770 }
771 case cgv::g2d::DragAction::kDrag:
772 if(type == DraggableType::kColor) {
773 color_point* dragged = color_draggables.get_dragged();
774
775 if(is_boundary_point(dragged, color_draggables))
776 dragged->position = color_draggable_start_position;
777
778 if(dragged) {
779 dragged->set_position_and_update_uv(dragged->position);
780 set_value_label(
781 dragged->position + cgv::vec2(0.0f, 25.0f),
782 value_to_string(dragged->uv.x())
783 );
784 selected_color_draggable = dragged;
785 selected_opacity_draggable = nullptr;
786 modified = true;
787 }
788 } else if(type == DraggableType::kOpacity) {
789 opacity_point* dragged = opacity_draggables.get_dragged();
790
791 if(is_boundary_point(dragged, opacity_draggables))
792 dragged->position.x() = opacity_draggable_start_position.x();
793
794 if(dragged) {
795 dragged->set_position_and_update_uv(dragged->position);
796
797 set_value_label(
798 dragged->position + cgv::vec2(0.0f, 10.0f),
799 value_to_string(dragged->uv.x()) + ", " + cgv::utils::to_string(dragged->uv.y(), -1, 3u)
800 );
801 selected_opacity_draggable = dragged;
802 selected_color_draggable = nullptr;
803 modified = true;
804 }
805 }
806 if(modified)
807 handle_selection_change();
808 break;
809 case cgv::g2d::DragAction::kDragEnd:
810 handle_selection_change();
811 value_label.clear();
812 modified = true;
813 break;
814 default:
815 break;
816 }
817
818 //press_inside = has_constraint && !use_individual_constraints ? constraint_area.contains(mouse_position) : true;
819
820 if(modified) {
821 sort_points();
822 update_transfer_function_from_data();
823 create_preview_texture();
824 create_geometry();
825 post_damage();
826 }
827}
828
829void transfer_function_editor::handle_selection_change() {
830 if(selected_color_draggable) {
831 if(on_color_point_select_callback)
832 on_color_point_select_callback(selected_color_draggable->data);
833 } else {
834 if(on_color_point_deselect_callback)
835 on_color_point_deselect_callback();
836 }
838}
839
840std::string transfer_function_editor::value_to_string(float value) {
841 if(!transfer_function)
842 return "";
843
844 const vec2 domain = transfer_function->get_domain();
845 float display_value = cgv::math::lerp(domain.x(), domain.y(), value);
846
847 float total = domain.y() - domain.x();
848 if(total < 100.0f) {
849 // show as float
850 return cgv::utils::to_string(display_value, -1, 3u);
851 } else {
852 // show as int
853 int display_value_int = static_cast<int>(round(display_value));
854 return std::to_string(display_value_int);
855 }
856}
857
858template<typename T>
859void sort_draggables(cgv::g2d::draggable_collection<T>& draggables, T*& selected) {
860 if(draggables.empty())
861 return;
862
863 int dragged_idx = -1;
864 int selected_idx = -1;
865
866 for(size_t i = 0; i < draggables.size(); ++i) {
867 if(&draggables[i] == draggables.get_dragged())
868 dragged_idx = static_cast<int>(i);
869 if(&draggables[i] == selected)
870 selected_idx = static_cast<int>(i);
871 }
872
873 std::vector<T> draggables_copy = draggables.ref_draggables();
874 std::vector<size_t> permutation = cgv::utils::stable_sort_indices(draggables_copy.begin(), draggables_copy.end());
875
876 for(size_t i = 0; i < permutation.size(); ++i) {
877 int permuted_index = static_cast<int>(permutation[i]);
878 draggables[i] = draggables_copy[permuted_index];
879 if(permuted_index == dragged_idx)
880 draggables.set_dragged(i);
881 if(permuted_index == selected_idx)
882 selected = &draggables[i];
883 }
884}
885
886void transfer_function_editor::sort_points() {
887 sort_draggables(color_draggables, selected_color_draggable);
888 if(supports_opacity)
889 sort_draggables(opacity_draggables, selected_opacity_draggable);
890}
891
892void transfer_function_editor::update_point_positions() {
893 for(auto& draggable : color_draggables)
894 draggable.set_position_and_update_uv(draggable.position);
895
896 for(auto& draggable : opacity_draggables)
897 draggable.set_position_and_update_uv(draggable.position);
898}
899
900bool transfer_function_editor::create_preview_texture() {
901 const size_t size = static_cast<size_t>(256);// discretization_resolution);
902 std::vector<rgba> cs_data = transfer_function->quantize(size);
903
904 std::vector<cgv::rgba8> texture_data;
905 texture_data.reserve(size);
906 std::transform(cs_data.begin(), cs_data.end(), std::back_inserter(texture_data), [](const cgv::rgba& color) {
907 return cgv::rgba8(color);
908 });
909
911 cgv::data::data_view data_view(&data_format, texture_data.data());
912
913 const cgv::render::TextureFilter filter = cgv::render::TF_LINEAR;
914
915 if(get_context()) {
916 preview_tex.set_min_filter(filter);
917 preview_tex.set_mag_filter(filter);
918 return preview_tex.create(*get_context(), data_view, 0);
919 }
920 return false;
921}
922
923bool transfer_function_editor::create_background_texture() {
924 const rgb dark(0.75f);
925 const rgb light(0.9f);
926 std::vector<rgb> data = { dark, light, light, dark };
928 cgv::data::data_view data_view = cgv::data::data_view(&format, data.data());
929 if(get_context())
930 return background_tex.create(*get_context(), data_view, 0);
931 return false;
932}
933
934void transfer_function_editor::create_geometry() {
935 color_draggables_geometry.clear();
936 opacity_draggables_geometry.clear();
937 line_geometry.clear();
938 triangle_geometry.clear();
939
940 vec2 pos_offset = vec2(0.0f, 0.5f * color_point_size.y());
941
942 for(const auto& draggable : color_draggables) {
943 vec2 pos = draggable.center();
944 rgba col = selected_color_draggable == &draggable ? highlight_color : handle_color;
945 color_draggables_geometry.add(pos - pos_offset, col);
946 color_draggables_geometry.add(pos + pos_offset, col);
947 }
948
949 if(!opacity_draggables.empty()) {
950 const auto& first = *opacity_draggables.begin();
951
952 vec2 tex_coord(0.0f, 0.5f);
953
954 line_geometry.add(vec2(static_cast<float>(layout.opacity_editor_rect.x()), first.center().y()), tex_coord);
955
956 triangle_geometry.add(vec2(static_cast<float>(layout.opacity_editor_rect.x()), first.center().y()), tex_coord);
957 triangle_geometry.add(layout.opacity_editor_rect.position, tex_coord);
958
959 for(const auto& draggable : opacity_draggables) {
960 vec2 pos = draggable.center();
961 rgba col = selected_opacity_draggable == &draggable ? highlight_color : handle_color;
962 opacity_draggables_geometry.add(pos, col);
963
964 tex_coord.x() = draggable.uv.x();
965
966 line_geometry.add(pos, tex_coord);
967
968 triangle_geometry.add(pos, tex_coord);
969 triangle_geometry.add(vec2(pos.x(), static_cast<float>(layout.opacity_editor_rect.y())), tex_coord);
970 }
971
972 const auto& last = *(--opacity_draggables.end());
973 vec2 max_pos = layout.opacity_editor_rect.position + vec2(1.0f, 0.0f) * layout.opacity_editor_rect.size;
974
975 tex_coord.x() = 1.0f;
976
977 line_geometry.add(vec2(max_pos.x(), last.center().y()), tex_coord);
978
979 triangle_geometry.add(vec2(max_pos.x(), last.center().y()), tex_coord);
980 triangle_geometry.add(max_pos, tex_coord);
981 }
982}
983
984} // namespace overlay
985} // 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
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:668
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:2207
void set_mag_filter(TextureFilter _mag_filter)
set the magnification filter
Definition texture.cxx:143
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:213
bool disable(const context &ctx)
disable texture and restore state from before last enable call
Definition texture.cxx:774
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:763
bool destruct(const context &ctx)
destruct the texture and free texture memory and handle
Definition texture.cxx:748
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:125
@ 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:285
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