ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | LICENSE

Commit: 11c736cb42998512ce7773cd5a1d58cab914d149
Parent: cc0f350ea880bb7415d64be70c6f4064b14c5a8f
Author: Randy Palamar
Date:   Wed,  8 Jan 2025 08:07:24 -0700

improve display ruler interaction

Diffstat:
Mbeamformer.c | 4+++-
Mbeamformer.h | 11++++++++++-
Mui.c | 180++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mutil.c | 9+++++++++
4 files changed, 131 insertions(+), 73 deletions(-)

diff --git a/beamformer.c b/beamformer.c @@ -729,6 +729,7 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) do_beamform_work(ctx, arena); /* NOTE: draw output image texture using render fragment shader */ + b32 output_image_drawn; BeginTextureMode(ctx->fsctx.output); ClearBackground(PINK); BeginShaderMode(ctx->fsctx.shader); @@ -742,6 +743,7 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) /* NOTE: verify we have actually beamformed something yet */ if (f->dim.w) out_texture = f->textures[f->dim.w - 1]; } + output_image_drawn = out_texture != 0; glBindTextureUnit(0, out_texture); glUniform1f(fs->db_cutoff_id, fs->db); glUniform1f(fs->threshold_id, fs->threshold); @@ -758,7 +760,7 @@ DEBUG_EXPORT BEAMFORMER_FRAME_STEP_FN(beamformer_frame_step) ctx->flags &= ~GEN_MIPMAPS; } - draw_ui(ctx, input); + draw_ui(ctx, input, output_image_drawn); if (IsKeyPressed(KEY_R)) { ctx->flags |= RELOAD_SHADERS; diff --git a/beamformer.h b/beamformer.h @@ -68,13 +68,18 @@ enum interaction_states { IS_SCALE_BAR, }; +enum ruler_state { + RS_NONE, + RS_START, + RS_HOLD, +}; + typedef struct { Variable hot; Variable next_hot; Variable active; u32 hot_state; u32 state; - v2 last_mouse_click_p; } InteractionState; typedef struct { @@ -88,6 +93,10 @@ typedef struct { InteractionState interaction; InputState text_input_state; + + v2 ruler_start_p; + v2 ruler_stop_p; + u32 ruler_state; } BeamformerUI; #define MAX_FRAMES_IN_FLIGHT 3 diff --git a/ui.c b/ui.c @@ -115,10 +115,10 @@ hover_text(v2 mouse, Rect text_rect, f32 *hover_t, b32 can_advance) * an orientation rather than force CCW/right-handed */ static void draw_ruler(BeamformerUI *ui, Stream *buf, v2 start_point, v2 end_point, - f32 start_coord, f32 end_coord, u32 segments, s8 suffix, + f32 start_value, f32 end_value, u32 segments, s8 suffix, Color ruler_colour, Color txt_colour) { - b32 draw_plus = SIGN(start_coord) != SIGN(end_coord); + b32 draw_plus = SIGN(start_value) != SIGN(end_value); end_point = sub_v2(end_point, start_point); f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI; @@ -128,8 +128,8 @@ draw_ruler(BeamformerUI *ui, Stream *buf, v2 start_point, v2 end_point, rlRotatef(rotation, 0, 0, 1); f32 inc = magnitude_v2(end_point) / segments; - f32 coord_inc = (end_coord - start_coord) / segments; - f32 coord = start_coord; + f32 value_inc = (end_value - start_value) / segments; + f32 value = start_value; v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH}; v2 tp = {.x = ui->small_font_height / 2, .y = ep.y + RULER_TEXT_PAD}; @@ -137,12 +137,12 @@ draw_ruler(BeamformerUI *ui, Stream *buf, v2 start_point, v2 end_point, DrawLineEx(sp.rl, ep.rl, 3, ruler_colour); buf->widx = 0; - if (draw_plus && coord > 0) stream_append_byte(buf, '+'); - stream_append_f64(buf, coord, 10); + if (draw_plus && value > 0) stream_append_byte(buf, '+'); + stream_append_f64(buf, value, 10); stream_append_s8(buf, suffix); draw_text(ui->small_font, stream_to_s8(buf), tp, 90, txt_colour); - coord += coord_inc; + value += value_inc; sp.x += inc; ep.x += inc; tp.x += inc; @@ -150,6 +150,53 @@ draw_ruler(BeamformerUI *ui, Stream *buf, v2 start_point, v2 end_point, rlPopMatrix(); } +static void +do_display_overlay(BeamformerCtx *ctx, Stream *buf, v2 mouse, v2 output_dim, Rect display_rect) +{ + BeamformerUI *ui = ctx->ui; + + if (CheckCollisionPointRec(mouse.rl, display_rect.rl)) { + InteractionState *is = &ui->interaction; + is->hot_state = IS_DISPLAY; + is->hot.store = &ctx->fsctx.threshold; + is->hot.type = VT_F32; + is->hot.f32_limits = (v2){.y = 240}; + is->hot.flags = V_GEN_MIPMAPS; + is->hot.display_scale = 1; + is->hot.scroll_scale = 1; + } + + if (ui->ruler_state != RS_NONE) { + v2 end_p; + if (ui->ruler_state == RS_START) end_p = mouse; + else end_p = ui->ruler_stop_p; + + Color colour = colour_from_normalized(RULER_COLOUR); + + v2 pixels_to_mm = output_dim; + pixels_to_mm.x /= display_rect.size.x * 1e-3; + pixels_to_mm.y /= display_rect.size.y * 1e-3; + + end_p = clamp_v2_rect(end_p, display_rect); + v2 pixel_delta = sub_v2(ui->ruler_start_p, end_p); + v2 mm_delta = mul_v2(pixels_to_mm, pixel_delta); + + DrawCircleV(ui->ruler_start_p.rl, 3, colour); + DrawLineEx(end_p.rl, ui->ruler_start_p.rl, 2, colour); + DrawCircleV(end_p.rl, 3, colour); + + buf->widx = 0; + stream_append_f64(buf, magnitude_v2(mm_delta), 100); + stream_append_s8(buf, s8(" mm")); + + v2 txt_p = ui->ruler_start_p; + v2 txt_s = measure_text(ui->small_font, stream_to_s8(buf)); + if (pixel_delta.y < 0) txt_p.y -= txt_s.y; + if (pixel_delta.x < 0) txt_p.x -= txt_s.x; + draw_text(ui->small_font, stream_to_s8(buf), txt_p, 0, colour); + } +} + /* TODO(rnp): this is known after the first frame, we could unbind * the texture for the first draw pass or just accept a slight glitch * at start up (make a good default guess) */ @@ -689,7 +736,39 @@ ui_gen_mipmaps(BeamformerCtx *ctx) } static void -ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) +display_interaction_end(BeamformerUI *ui) +{ + b32 is_hot = ui->interaction.hot_state == IS_DISPLAY; + b32 is_active = ui->interaction.state == IS_DISPLAY; + if ((is_active && is_hot) || ui->ruler_state == RS_HOLD) + return; + ui->ruler_state = RS_NONE; +} + +static void +display_interaction(BeamformerUI *ui, v2 mouse) +{ + b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); + b32 is_hot = ui->interaction.hot_state == IS_DISPLAY; + b32 is_active = ui->interaction.state == IS_DISPLAY; + + if (mouse_left_pressed && is_active) { + ui->ruler_state++; + switch (ui->ruler_state) { + case RS_START: ui->ruler_start_p = mouse; break; + case RS_HOLD: ui->ruler_stop_p = mouse; break; + default: + ui->ruler_state = RS_NONE; + break; + } + } else if ((mouse_left_pressed && !is_hot) || (mouse_right_pressed && is_hot)) { + ui->ruler_state = RS_NONE; + } +} + +static void +ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mouse_left_pressed) { InteractionState *is = &ui->interaction; if (is->hot_state != IS_NONE) { @@ -702,7 +781,7 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) case VT_F32: { if (scroll) { is->state = IS_SCROLL; - } else { + } else if (mouse_left_pressed) { is->state = IS_TEXT; begin_text_input(&ui->text_input_state, &is->hot); } @@ -715,8 +794,9 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll) } static void -ui_end_interact(BeamformerCtx *ctx, BeamformerUI *ui) +ui_end_interact(BeamformerCtx *ctx, v2 mouse) { + BeamformerUI *ui = ctx->ui; InteractionState *is = &ui->interaction; switch (is->state) { case IS_NONE: break; @@ -729,9 +809,7 @@ ui_end_interact(BeamformerCtx *ctx, BeamformerUI *ui) } break; } } break; - case IS_DISPLAY: - is->last_mouse_click_p = (v2){0}; - /* FALLTHROUGH */ + case IS_DISPLAY: display_interaction_end(ui); /* FALLTHROUGH */ case IS_SCROLL: { f32 delta = GetMouseWheelMove() * is->active.scroll_scale; switch (is->active.type) { @@ -768,35 +846,28 @@ ui_end_interact(BeamformerCtx *ctx, BeamformerUI *ui) static void ui_interact(BeamformerCtx *ctx, BeamformerInput *input) { - BeamformerUI *ui = ctx->ui; - InteractionState *is = &ui->interaction; - b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); - b32 wheel_moved = GetMouseWheelMove(); - if (mouse_left_pressed || wheel_moved) { + BeamformerUI *ui = ctx->ui; + InteractionState *is = &ui->interaction; + b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); + b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); + b32 wheel_moved = GetMouseWheelMove(); + if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { if (is->state != IS_NONE) - ui_end_interact(ctx, ui); - ui_begin_interact(ui, input, wheel_moved); + ui_end_interact(ctx, input->mouse); + ui_begin_interact(ui, input, wheel_moved, mouse_left_pressed); } if (IsKeyPressed(KEY_ENTER) && is->state == IS_TEXT) - ui_end_interact(ctx, ui); + ui_end_interact(ctx, input->mouse); switch (is->state) { - case IS_DISPLAY: { - b32 should_end = wheel_moved || IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) || - (is->active.store != is->hot.store); - if (should_end) { - ui_end_interact(ctx, ui); - } else if (mouse_left_pressed) { - is->last_mouse_click_p = input->mouse; - } - } break; - case IS_SCROLL: ui_end_interact(ctx, ui); break; - case IS_SET: ui_end_interact(ctx, ui); break; + case IS_DISPLAY: display_interaction(ui, input->mouse); break; + case IS_SCROLL: ui_end_interact(ctx, input->mouse); break; + case IS_SET: ui_end_interact(ctx, input->mouse); break; case IS_TEXT: update_text_input(&ui->text_input_state); break; case IS_DRAG: { if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { - ui_end_interact(ctx, ui); + ui_end_interact(ctx, input->mouse); } else { switch (is->active.type) { } @@ -838,7 +909,7 @@ ui_init(BeamformerCtx *ctx, Arena store) } static void -draw_ui(BeamformerCtx *ctx, BeamformerInput *input) +draw_ui(BeamformerCtx *ctx, BeamformerInput *input, b32 draw_scale_bars) { BeamformerParameters *bp = &ctx->params->raw; BeamformerUI *ui = ctx->ui; @@ -868,8 +939,10 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input) rr.size.w = wr.size.w - lr.size.w; rr.pos.x = lr.pos.x + lr.size.w; + Stream buf = stream_alloc(&ui->arena_for_frame, 64); Rect vr = INVERTED_INFINITY_RECT; - if (output_dim.x > 1e-6 && output_dim.y > 1e-6) { + /* TODO(rnp): move this into the display overlay function */ + if (draw_scale_bars) { v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); rr.pos.x += 0.02 * rr.size.w; @@ -918,8 +991,6 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input) ui_start_compute(ctx); } - Stream buf = stream_alloc(&ui->arena_for_frame, 64); - f32 mm = bp->output_min_coordinate.x * 1e3; f32 mm_end = bp->output_max_coordinate.x * 1e3; @@ -955,39 +1026,6 @@ draw_ui(BeamformerCtx *ctx, BeamformerInput *input) draw_settings_ui(ctx, lr, mouse); draw_debug_overlay(ctx, ui->arena_for_frame, lr); - - if (CheckCollisionPointRec(mouse.rl, vr.rl)) { - InteractionState *is = &ui->interaction; - is->hot_state = IS_DISPLAY; - is->hot.store = &ctx->fsctx.threshold; - is->hot.type = VT_F32; - is->hot.f32_limits = (v2){.y = 240}; - is->hot.flags = V_GEN_MIPMAPS; - is->hot.display_scale = 1; - is->hot.scroll_scale = 1; - - /* NOTE: check and draw Ruler */ - if (CheckCollisionPointRec(is->last_mouse_click_p.rl, vr.rl)) { - Stream buf = arena_stream(&ui->arena_for_frame); - - Color colour = colour_from_normalized(RULER_COLOUR); - DrawCircleV(is->last_mouse_click_p.rl, 3, colour); - DrawLineEx(mouse.rl, is->last_mouse_click_p.rl, 2, colour); - v2 pixels_to_mm = output_dim; - pixels_to_mm.x /= vr.size.x * 1e-3; - pixels_to_mm.y /= vr.size.y * 1e-3; - - v2 pixel_delta = sub_v2(is->last_mouse_click_p, mouse); - v2 mm_delta = mul_v2(pixels_to_mm, pixel_delta); - - stream_append_f64(&buf, magnitude_v2(mm_delta), 100); - stream_append_s8(&buf, s8(" mm")); - v2 txt_p = is->last_mouse_click_p; - v2 txt_s = measure_text(ui->small_font, stream_to_s8(&buf)); - if (pixel_delta.y < 0) txt_p.y -= txt_s.y; - if (pixel_delta.x < 0) txt_p.x -= txt_s.x; - draw_text(ui->small_font, stream_to_s8(&buf), txt_p, 0, colour); - } - } + do_display_overlay(ctx, &buf, mouse, output_dim, vr); EndDrawing(); } diff --git a/util.c b/util.c @@ -319,6 +319,15 @@ normalize_v3(v3 a) } static v2 +clamp_v2_rect(v2 v, Rect r) +{ + v2 result = v; + result.x = CLAMP(v.x, r.pos.x, r.pos.x + r.size.x); + result.y = CLAMP(v.y, r.pos.y, r.pos.y + r.size.y); + return result; +} + +static v2 sub_v2(v2 a, v2 b) { v2 result = {