Commit: 4a9c90ccc3db2c8ab66292e5bbfbe881c53d1270
Parent: 744d93141298ce58994b740def9e7801d5735d10
Author: Randy Palamar
Date: Wed, 2 Jul 2025 14:32:46 -0600
ui: 3D X-Plane dragging interaction
Diffstat:
7 files changed, 293 insertions(+), 53 deletions(-)
diff --git a/beamformer.h b/beamformer.h
@@ -67,6 +67,7 @@ typedef struct {
#define FRAME_VIEW_LOG_SCALE_LOC 6
#define FRAME_VIEW_BB_COLOUR_LOC 7
#define FRAME_VIEW_BB_FRACTION_LOC 8
+#define FRAME_VIEW_SOLID_BB_LOC 10
#define FRAME_VIEW_BB_COLOUR 0.92, 0.88, 0.78, 1.0
#define FRAME_VIEW_BB_FRACTION 0.007f
diff --git a/math.c b/math.c
@@ -245,16 +245,23 @@ v3_dot(v3 a, v3 b)
}
function f32
-v3_length_squared(v3 a)
+v3_magnitude_squared(v3 a)
{
f32 result = v3_dot(a, a);
return result;
}
+function f32
+v3_magnitude(v3 a)
+{
+ f32 result = sqrt_f32(v3_dot(a, a));
+ return result;
+}
+
function v3
v3_normalize(v3 a)
{
- v3 result = v3_scale(a, 1.0f / sqrt_f32(v3_length_squared(a)));
+ v3 result = v3_scale(a, 1.0f / v3_magnitude(a));
return result;
}
@@ -361,6 +368,49 @@ m4_mul(m4 a, m4 b)
return result;
}
+/* NOTE(rnp): based on:
+ * https://web.archive.org/web/20131215123403/ftp://download.intel.com/design/PentiumIII/sml/24504301.pdf
+ * TODO(rnp): redo with SIMD as given in the link (but need to rewrite for column-major)
+ */
+function m4
+m4_inverse(m4 m)
+{
+ m4 result;
+ result.E[ 0] = m.E[5] * m.E[10] * m.E[15] - m.E[5] * m.E[11] * m.E[14] - m.E[9] * m.E[6] * m.E[15] + m.E[9] * m.E[7] * m.E[14] + m.E[13] * m.E[6] * m.E[11] - m.E[13] * m.E[7] * m.E[10];
+ result.E[ 4] = -m.E[4] * m.E[10] * m.E[15] + m.E[4] * m.E[11] * m.E[14] + m.E[8] * m.E[6] * m.E[15] - m.E[8] * m.E[7] * m.E[14] - m.E[12] * m.E[6] * m.E[11] + m.E[12] * m.E[7] * m.E[10];
+ result.E[ 8] = m.E[4] * m.E[ 9] * m.E[15] - m.E[4] * m.E[11] * m.E[13] - m.E[8] * m.E[5] * m.E[15] + m.E[8] * m.E[7] * m.E[13] + m.E[12] * m.E[5] * m.E[11] - m.E[12] * m.E[7] * m.E[ 9];
+ result.E[12] = -m.E[4] * m.E[ 9] * m.E[14] + m.E[4] * m.E[10] * m.E[13] + m.E[8] * m.E[5] * m.E[14] - m.E[8] * m.E[6] * m.E[13] - m.E[12] * m.E[5] * m.E[10] + m.E[12] * m.E[6] * m.E[ 9];
+ result.E[ 1] = -m.E[1] * m.E[10] * m.E[15] + m.E[1] * m.E[11] * m.E[14] + m.E[9] * m.E[2] * m.E[15] - m.E[9] * m.E[3] * m.E[14] - m.E[13] * m.E[2] * m.E[11] + m.E[13] * m.E[3] * m.E[10];
+ result.E[ 5] = m.E[0] * m.E[10] * m.E[15] - m.E[0] * m.E[11] * m.E[14] - m.E[8] * m.E[2] * m.E[15] + m.E[8] * m.E[3] * m.E[14] + m.E[12] * m.E[2] * m.E[11] - m.E[12] * m.E[3] * m.E[10];
+ result.E[ 9] = -m.E[0] * m.E[ 9] * m.E[15] + m.E[0] * m.E[11] * m.E[13] + m.E[8] * m.E[1] * m.E[15] - m.E[8] * m.E[3] * m.E[13] - m.E[12] * m.E[1] * m.E[11] + m.E[12] * m.E[3] * m.E[ 9];
+ result.E[13] = m.E[0] * m.E[ 9] * m.E[14] - m.E[0] * m.E[10] * m.E[13] - m.E[8] * m.E[1] * m.E[14] + m.E[8] * m.E[2] * m.E[13] + m.E[12] * m.E[1] * m.E[10] - m.E[12] * m.E[2] * m.E[ 9];
+ result.E[ 2] = m.E[1] * m.E[ 6] * m.E[15] - m.E[1] * m.E[ 7] * m.E[14] - m.E[5] * m.E[2] * m.E[15] + m.E[5] * m.E[3] * m.E[14] + m.E[13] * m.E[2] * m.E[ 7] - m.E[13] * m.E[3] * m.E[ 6];
+ result.E[ 6] = -m.E[0] * m.E[ 6] * m.E[15] + m.E[0] * m.E[ 7] * m.E[14] + m.E[4] * m.E[2] * m.E[15] - m.E[4] * m.E[3] * m.E[14] - m.E[12] * m.E[2] * m.E[ 7] + m.E[12] * m.E[3] * m.E[ 6];
+ result.E[10] = m.E[0] * m.E[ 5] * m.E[15] - m.E[0] * m.E[ 7] * m.E[13] - m.E[4] * m.E[1] * m.E[15] + m.E[4] * m.E[3] * m.E[13] + m.E[12] * m.E[1] * m.E[ 7] - m.E[12] * m.E[3] * m.E[ 5];
+ result.E[14] = -m.E[0] * m.E[ 5] * m.E[14] + m.E[0] * m.E[ 6] * m.E[13] + m.E[4] * m.E[1] * m.E[14] - m.E[4] * m.E[2] * m.E[13] - m.E[12] * m.E[1] * m.E[ 6] + m.E[12] * m.E[2] * m.E[ 5];
+ result.E[ 3] = -m.E[1] * m.E[ 6] * m.E[11] + m.E[1] * m.E[ 7] * m.E[10] + m.E[5] * m.E[2] * m.E[11] - m.E[5] * m.E[3] * m.E[10] - m.E[ 9] * m.E[2] * m.E[ 7] + m.E[ 9] * m.E[3] * m.E[ 6];
+ result.E[ 7] = m.E[0] * m.E[ 6] * m.E[11] - m.E[0] * m.E[ 7] * m.E[10] - m.E[4] * m.E[2] * m.E[11] + m.E[4] * m.E[3] * m.E[10] + m.E[ 8] * m.E[2] * m.E[ 7] - m.E[ 8] * m.E[3] * m.E[ 6];
+ result.E[11] = -m.E[0] * m.E[ 5] * m.E[11] + m.E[0] * m.E[ 7] * m.E[ 9] + m.E[4] * m.E[1] * m.E[11] - m.E[4] * m.E[3] * m.E[ 9] - m.E[ 8] * m.E[1] * m.E[ 7] + m.E[ 8] * m.E[3] * m.E[ 5];
+ result.E[15] = m.E[0] * m.E[ 5] * m.E[10] - m.E[0] * m.E[ 6] * m.E[ 9] - m.E[4] * m.E[1] * m.E[10] + m.E[4] * m.E[2] * m.E[ 9] + m.E[ 8] * m.E[1] * m.E[ 6] - m.E[ 8] * m.E[2] * m.E[ 5];
+
+ f32 determinant = m.E[0] * result.E[0] + m.E[1] * result.E[4] + m.E[2] * result.E[8] + m.E[3] * result.E[12];
+ determinant = 1.0f / determinant;
+ for(i32 i = 0; i < 16; i++)
+ result.E[i] *= determinant;
+ return result;
+}
+
+function m4
+m4_translation(v3 delta)
+{
+ m4 result;
+ result.c[0] = (v4){{1, 0, 0, delta.x}};
+ result.c[1] = (v4){{0, 1, 0, delta.y}};
+ result.c[2] = (v4){{0, 0, 1, delta.z}};
+ result.c[3] = (v4){{0, 0, 0, 1}};
+ return result;
+}
+
function m4
m4_rotation_about_y(f32 turns)
{
@@ -384,13 +434,7 @@ y_aligned_volume_transform(v3 extent, v3 translation, f32 rotation_turns)
S.c[3] = (v4){{0, 0, 0, 1}};
m4 R = m4_rotation_about_y(rotation_turns);
-
- m4 T;
- T.c[0] = (v4){{1, 0, 0, translation.x}};
- T.c[1] = (v4){{0, 1, 0, translation.y}};
- T.c[2] = (v4){{0, 0, 1, translation.z}};
- T.c[3] = (v4){{0, 0, 0, 1}};
-
+ m4 T = m4_translation(translation);
m4 result = m4_mul(m4_mul(R, S), T);
return result;
}
@@ -442,3 +486,49 @@ camera_look_at(v3 camera, v3 point)
result.c[3] = (v4){{translate.x, translate.y, translate.z, 1}};
return result;
}
+
+/* NOTE(rnp): adapted from "Essential Mathematics for Games and Interactive Applications" (Verth, Bishop) */
+function f32
+obb_raycast(m4 obb_orientation, v3 obb_size, v3 obb_center, ray ray)
+{
+ v3 p = v3_sub(obb_center, ray.origin);
+ v3 X = obb_orientation.c[0].xyz;
+ v3 Y = obb_orientation.c[1].xyz;
+ v3 Z = obb_orientation.c[2].xyz;
+
+ /* NOTE(rnp): projects direction vector onto OBB axis */
+ v3 f;
+ f.x = v3_dot(X, ray.direction);
+ f.y = v3_dot(Y, ray.direction);
+ f.z = v3_dot(Z, ray.direction);
+
+ /* NOTE(rnp): projects relative vector onto OBB axis */
+ v3 e;
+ e.x = v3_dot(X, p);
+ e.y = v3_dot(Y, p);
+ e.z = v3_dot(Z, p);
+
+ f32 result = 0;
+ f32 t[6] = {0};
+ for (i32 i = 0; i < 3; i++) {
+ if (f32_cmp(f.E[i], 0)) {
+ if (-e.E[i] - obb_size.E[i] > 0 || -e.E[i] + obb_size.E[i] < 0)
+ result = -1.0f;
+ f.E[i] = F32_EPSILON;
+ }
+ t[i * 2 + 0] = (e.E[i] + obb_size.E[i]) / f.E[i];
+ t[i * 2 + 1] = (e.E[i] - obb_size.E[i]) / f.E[i];
+ }
+
+ if (result != -1) {
+ f32 tmin = MAX(MAX(MIN(t[0], t[1]), MIN(t[2], t[3])), MIN(t[4], t[5]));
+ f32 tmax = MIN(MIN(MAX(t[0], t[1]), MAX(t[2], t[3])), MAX(t[4], t[5]));
+ if (tmax >= 0 && tmin <= tmax) {
+ result = tmin > 0 ? tmin : tmax;
+ } else {
+ result = -1;
+ }
+ }
+
+ return result;
+}
diff --git a/shaders/render_3d.frag.glsl b/shaders/render_3d.frag.glsl
@@ -35,7 +35,7 @@ void main()
smp = 1 - smp;
}
- if (bounding_box_test(test_texture_coordinate, u_bb_fraction)) {
+ if (u_solid_bb || bounding_box_test(test_texture_coordinate, u_bb_fraction)) {
out_colour = u_bb_colour;
} else {
out_colour = vec4(smp, smp, smp, 1);
diff --git a/static.c b/static.c
@@ -456,6 +456,7 @@ setup_beamformer(BeamformerCtx *ctx, BeamformerInput *input, Arena *memory)
"layout(location = " str(FRAME_VIEW_LOG_SCALE_LOC) ") uniform bool u_log_scale;\n"
"layout(location = " str(FRAME_VIEW_BB_COLOUR_LOC) ") uniform vec4 u_bb_colour = vec4(" str(FRAME_VIEW_BB_COLOUR) ");\n"
"layout(location = " str(FRAME_VIEW_BB_FRACTION_LOC) ") uniform float u_bb_fraction = " str(FRAME_VIEW_BB_FRACTION) ";\n"
+ "layout(location = " str(FRAME_VIEW_SOLID_BB_LOC) ") uniform bool u_solid_bb;\n"
"\n"
"layout(binding = 0) uniform sampler3D u_texture;\n");
diff --git a/ui.c b/ui.c
@@ -163,6 +163,7 @@ typedef enum {
VT_UI_REGION_SPLIT,
VT_UI_TEXT_BOX,
VT_UI_VIEW,
+ VT_X_PLANE_SHIFT,
} VariableType;
typedef enum {
@@ -228,6 +229,11 @@ typedef struct {
VariableType store_type;
} BeamformerVariable;
+typedef struct {
+ v3 start_point;
+ v3 end_point;
+} XPlaneShift;
+
typedef enum {
V_INPUT = 1 << 0,
V_TEXT = 1 << 1,
@@ -250,6 +256,7 @@ struct Variable {
UIView view;
VariableCycler cycler;
VariableGroup group;
+ XPlaneShift x_plane_shift;
scaled_f32 scaled_real32;
b32 bool32;
i32 signed32;
@@ -274,10 +281,16 @@ typedef enum {
typedef struct BeamformerFrameView {
union {
- Variable plane_offsets[2];
+ struct {
+ Variable x_plane_shifts[2];
+ v3 hit_test_point;
+ f32 rotation;
+ };
struct {
Variable lateral_scale_bar;
Variable axial_scale_bar;
+ v3 min_coordinate;
+ v3 max_coordinate;
};
};
@@ -292,10 +305,6 @@ typedef struct BeamformerFrameView {
Variable *cycler;
u32 cycler_state;
- v3 min_coordinate;
- v3 max_coordinate;
- f32 rotation;
-
Ruler ruler;
Variable threshold;
@@ -781,7 +790,7 @@ resize_frame_view(BeamformerFrameView *view, uv2 dim)
/* NOTE(rnp): work around raylib's janky texture sampling */
glTextureParameteri(view->textures[0], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTextureParameteri(view->textures[0], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
- glTextureParameterfv(view->textures[0], GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 1});
+ glTextureParameterfv(view->textures[0], GL_TEXTURE_BORDER_COLOR, (f32 []){0, 0, 0, 0});
/* TODO(rnp): better choice when depth component is included */
glTextureParameteri(view->textures[0], GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(view->textures[0], GL_TEXTURE_MIN_FILTER, GL_NEAREST);
@@ -1086,8 +1095,10 @@ add_beamformer_frame_view(BeamformerUI *ui, Variable *parent, Arena *arena,
switch (kind) {
case BeamformerFrameViewKind_3DXPlane:{
resize_frame_view(bv, (uv2){{1024, 1024}});
- fill_variable(&bv->plane_offsets[0], var, s8("XZ Offset"), V_INPUT|V_IMAGING_PARAM, VT_F32, ui->small_font);
- fill_variable(&bv->plane_offsets[1], var, s8("YZ Offset"), V_INPUT|V_IMAGING_PARAM, VT_F32, ui->small_font);
+ fill_variable(bv->x_plane_shifts + 0, var, s8("XZ Shift"), V_INPUT|V_IMAGING_PARAM,
+ VT_X_PLANE_SHIFT, ui->small_font);
+ fill_variable(bv->x_plane_shifts + 1, var, s8("YZ Shift"), V_INPUT|V_IMAGING_PARAM,
+ VT_X_PLANE_SHIFT, ui->small_font);
bv->demo = add_variable(ui, menu, arena, s8("Demo Mode"), V_INPUT|V_UPDATE_VIEW|V_RADIO_BUTTON,
VT_B32, ui->small_font);
}break;
@@ -1282,6 +1293,15 @@ x_plane_size(BeamformerUI *ui)
return result;
}
+function v2
+normalized_p_in_rect(Rect r, v2 p, b32 invert_y)
+{
+ v2 result = v2_div(v2_scale(v2_sub(p, r.pos), 2.0f), r.size);
+ if (invert_y) result = (v2){{result.x - 1.0f, 1.0f - result.y}};
+ else result = v2_sub(result, (v2){{1.0f, 1.0f}});
+ return result;
+}
+
function v3
x_plane_position(BeamformerUI *ui)
{
@@ -1318,10 +1338,86 @@ projection_matrix_for_x_plane_view(BeamformerFrameView *view)
return result;
}
+function ray
+ray_for_x_plane_view(BeamformerUI *ui, BeamformerFrameView *view, v2 uv)
+{
+ assert(view->kind == BeamformerFrameViewKind_3DXPlane);
+ ray result = {.origin = camera_for_x_plane_view(ui, view)};
+ v4 ray_clip = {{uv.x, uv.y, -1.0f, 1.0f}};
+
+ /* TODO(rnp): combine these so we only do one matrix inversion */
+ m4 proj_m = projection_matrix_for_x_plane_view(view);
+ m4 view_m = view_matrix_for_x_plane_view(ui, view, result.origin);
+ m4 proj_inv = m4_inverse(proj_m);
+ m4 view_inv = m4_inverse(view_m);
+
+ v4 ray_eye = {.z = -1};
+ ray_eye.x = v4_dot(m4_row(proj_inv, 0), ray_clip);
+ ray_eye.y = v4_dot(m4_row(proj_inv, 1), ray_clip);
+ result.direction = v3_normalize(m4_mul_v4(view_inv, ray_eye).xyz);
+
+ return result;
+}
+
+function BeamformerViewPlaneTag
+view_plane_tag_from_x_plane_shift(BeamformerFrameView *view, Variable *x_plane_shift)
+{
+ assert(BETWEEN(x_plane_shift, view->x_plane_shifts + 0, view->x_plane_shifts + 1));
+ BeamformerViewPlaneTag result = BeamformerViewPlaneTag_XZ;
+ if (x_plane_shift == view->x_plane_shifts + 1)
+ result = BeamformerViewPlaneTag_YZ;
+ return result;
+}
+
+function f32
+x_plane_rotation_for_view_plane(BeamformerFrameView *view, BeamformerViewPlaneTag tag)
+{
+ f32 result = view->rotation;
+ if (tag == BeamformerViewPlaneTag_YZ)
+ result += 0.25f;
+ return result;
+}
+
+function void
+render_single_xplane(BeamformerUI *ui, BeamformerFrameView *view, Variable *x_plane_shift,
+ u32 program, f32 rotation_turns, v3 translate, BeamformerViewPlaneTag tag)
+{
+ u32 texture = 0;
+ if (ui->latest_plane[tag])
+ texture = ui->latest_plane[tag]->texture;
+
+ v3 scale = x_plane_size(ui);
+ m4 model_transform = y_aligned_volume_transform(scale, translate, rotation_turns);
+
+ v4 colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, x_plane_shift->hover_t);
+ glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
+ glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, colour.E);
+ glProgramUniform1ui(program, FRAME_VIEW_SOLID_BB_LOC, 0);
+ glBindTextureUnit(0, texture);
+ glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT,
+ (void *)ui->unit_cube_model.elements_offset);
+
+ XPlaneShift *xp = &x_plane_shift->x_plane_shift;
+ v3 xp_delta = v3_sub(xp->end_point, xp->start_point);
+ if (!f32_cmp(v3_magnitude(xp_delta), 0)) {
+ m4 x_rotation = m4_rotation_about_y(rotation_turns);
+ v3 Z = x_rotation.c[2].xyz;
+ v3 f = v3_scale(Z, v3_dot(Z, v3_sub(xp->end_point, xp->start_point)));
+
+ /* TODO(rnp): there is no reason to compute the rotation matrix again */
+ model_transform = y_aligned_volume_transform(scale, v3_add(f, translate), rotation_turns);
+
+ glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
+ glProgramUniform1ui(program, FRAME_VIEW_SOLID_BB_LOC, 1);
+ glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, HOVERED_COLOUR.E);
+ glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT,
+ (void *)ui->unit_cube_model.elements_offset);
+ }
+}
+
function void
-render_3D_xplane(BeamformerUI *ui, BeamformerFrameView *view, FrameViewRenderContext *ctx)
+render_3D_xplane(BeamformerUI *ui, BeamformerFrameView *view, u32 program)
{
- u32 program = ctx->shaders[1];
glBindVertexArray(ui->unit_cube_model.vao);
if (view->demo->bool32) {
@@ -1333,37 +1429,15 @@ render_3D_xplane(BeamformerUI *ui, BeamformerFrameView *view, FrameViewRenderCon
m4 view_m = view_matrix_for_x_plane_view(ui, view, camera);
m4 projection = projection_matrix_for_x_plane_view(view);
- v3 scale = x_plane_size(ui);
- v3 model_translate = x_plane_position(ui);
- m4 model_transform = y_aligned_volume_transform(scale, model_translate, view->rotation);
-
- v4 colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, view->plane_offsets[0].hover_t);
-
- u32 texture = 0;
- if (ui->latest_plane[BeamformerViewPlaneTag_XZ])
- texture = ui->latest_plane[BeamformerViewPlaneTag_XZ]->texture;
-
glProgramUniformMatrix4fv(program, FRAME_VIEW_VIEW_MATRIX_LOC, 1, 0, view_m.E);
glProgramUniformMatrix4fv(program, FRAME_VIEW_PROJ_MATRIX_LOC, 1, 0, projection.E);
- glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
- glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, colour.E);
- glBindTextureUnit(0, texture);
- glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT,
- (void *)ui->unit_cube_model.elements_offset);
+ v3 model_translate = x_plane_position(ui);
+ render_single_xplane(ui, view, view->x_plane_shifts + 0, program, view->rotation,
+ model_translate, BeamformerViewPlaneTag_XZ);
model_translate.y -= 0.0001;
- model_transform = y_aligned_volume_transform(scale, model_translate, view->rotation + 0.25f);
- colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, view->plane_offsets[1].hover_t);
-
- texture = 0;
- if (ui->latest_plane[BeamformerViewPlaneTag_YZ])
- texture = ui->latest_plane[BeamformerViewPlaneTag_YZ]->texture;
-
- glProgramUniformMatrix4fv(program, FRAME_VIEW_MODEL_MATRIX_LOC, 1, 0, model_transform.E);
- glProgramUniform4fv(program, FRAME_VIEW_BB_COLOUR_LOC, 1, colour.E);
- glBindTextureUnit(0, texture);
- glDrawElements(GL_TRIANGLES, ui->unit_cube_model.elements, GL_UNSIGNED_SHORT,
- (void *)ui->unit_cube_model.elements_offset);
+ render_single_xplane(ui, view, view->x_plane_shifts + 1, program, view->rotation + 0.25f,
+ model_translate, BeamformerViewPlaneTag_YZ);
}
function b32
@@ -1429,7 +1503,7 @@ update_frame_views(BeamformerUI *ui, Rect window)
glProgramUniform1ui(program, FRAME_VIEW_LOG_SCALE_LOC, view->log_scale->bool32);
if (view->kind == BeamformerFrameViewKind_3DXPlane) {
- render_3D_xplane(ui, view, ctx);
+ render_3D_xplane(ui, view, ctx->shaders[1]);
} else {
glBindVertexArray(ctx->vao);
glBindTextureUnit(0, view->frame->texture);
@@ -2083,7 +2157,35 @@ draw_3D_xplane_frame_view(BeamformerUI *ui, Arena arena, Variable *var, Rect dis
}
vr.pos = v2_add(vr.pos, v2_scale(v2_sub(display_rect.size, vr.size), 0.5));
- hover_interaction(ui, mouse, auto_interaction(vr, var));
+ i32 id = -1;
+ if (hover_interaction(ui, mouse, auto_interaction(vr, var))) {
+ ray mouse_ray = ray_for_x_plane_view(ui, view, normalized_p_in_rect(vr, mouse, 0));
+ v3 x_position = x_plane_position(ui);
+ v3 x_size = v3_scale(x_plane_size(ui), 0.5f);
+ m4 x_rotation = m4_rotation_about_y(view->rotation);
+
+ f32 test[2] = {0};
+ test[0] = obb_raycast(x_rotation, x_size, x_position, mouse_ray);
+ x_rotation = m4_rotation_about_y(view->rotation + 0.25);
+ test[1] = obb_raycast(x_rotation, x_size, x_position, mouse_ray);
+
+ if (test[0] >= 0 && test[1] >= 0) id = test[1] < test[0]? 1 : 0;
+ else if (test[0] >= 0) id = 0;
+ else if (test[1] >= 0) id = 1;
+
+ if (id != -1) {
+ view->hit_test_point = v3_add(mouse_ray.origin, v3_scale(mouse_ray.direction, test[id]));
+ }
+ }
+
+ for (i32 i = 0; i < countof(view->x_plane_shifts); i++) {
+ Variable *var = view->x_plane_shifts + i;
+ Interaction interaction = auto_interaction(vr, var);
+ if (id == i) ui->next_interaction = interaction;
+ if (interaction_is_hot(ui, interaction)) var->hover_t += HOVER_SPEED * dt_for_frame;
+ else var->hover_t -= HOVER_SPEED * dt_for_frame;
+ var->hover_t = CLAMP01(var->hover_t);
+ }
Rectangle tex_r = {0, 0, view->texture_dim.w, view->texture_dim.h};
NPatchInfo tex_np = {tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH};
@@ -3050,6 +3152,17 @@ ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll)
if (scroll) hot->kind = InteractionKind_Scroll;
else hot->kind = InteractionKind_Nop;
}break;
+ case VT_X_PLANE_SHIFT:{
+ if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {
+ assert(hot->var->parent && hot->var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
+ BeamformerFrameView *bv = hot->var->parent->generic;
+ XPlaneShift *xp = &hot->var->x_plane_shift;
+ xp->start_point = xp->end_point = bv->hit_test_point;
+ hot->kind = InteractionKind_Drag;
+ } else {
+ hot->kind = InteractionKind_Nop;
+ }
+ }break;
case VT_BEAMFORMER_FRAME_VIEW:{
if (scroll) {
hot->kind = InteractionKind_Scroll;
@@ -3125,7 +3238,25 @@ ui_end_interact(BeamformerUI *ui, v2 mouse)
b32 start_compute = (it->var->flags & V_CAUSES_COMPUTE) != 0;
switch (it->kind) {
case InteractionKind_Nop:{}break;
- case InteractionKind_Drag:{ EnableCursor(); }break;
+ case InteractionKind_Drag:{
+ EnableCursor();
+ switch (it->var->type) {
+ case VT_X_PLANE_SHIFT:{
+ assert(it->var->parent && it->var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
+ XPlaneShift *xp = &it->var->x_plane_shift;
+ BeamformerFrameView *view = it->var->parent->generic;
+ BeamformerViewPlaneTag plane = view_plane_tag_from_x_plane_shift(view, it->var);
+ f32 rotation = x_plane_rotation_for_view_plane(view, plane);
+ m4 x_rotation = m4_rotation_about_y(rotation);
+ v3 Z = x_rotation.c[2].xyz;
+ f32 delta = v3_dot(Z, v3_sub(xp->end_point, xp->start_point));
+ /* TODO(rnp): commit to image parameters shared memory */
+ (void)delta;
+ xp->start_point = xp->end_point;
+ }break;
+ default:{}break;
+ }
+ }break;
case InteractionKind_Set:{
switch (it->var->type) {
case VT_B32:{ it->var->bool32 = !it->var->bool32; }break;
@@ -3232,7 +3363,19 @@ ui_interact(BeamformerUI *ui, BeamformerInput *input, Rect window_rect)
v2 ws = window_rect.size;
v2 dMouse = v2_sub(input->mouse, input->last_mouse);
- switch (ui->interaction.var->type) {
+ switch (it->var->type) {
+ case VT_X_PLANE_SHIFT:{
+ assert(it->var->parent && it->var->parent->type == VT_BEAMFORMER_FRAME_VIEW);
+ v2 mouse = clamp_v2_rect(input->mouse, it->rect);
+ XPlaneShift *xp = &it->var->x_plane_shift;
+ ray mouse_ray = ray_for_x_plane_view(ui, it->var->parent->generic,
+ normalized_p_in_rect(it->rect, mouse, 0));
+ /* NOTE(rnp): project start point onto ray */
+ v3 s = v3_sub(xp->start_point, mouse_ray.origin);
+ v3 r = v3_sub(mouse_ray.direction, mouse_ray.origin);
+ f32 scale = v3_dot(s, r) / v3_magnitude_squared(r);
+ xp->end_point = v3_add(mouse_ray.origin, v3_scale(r, scale));
+ }break;
case VT_BEAMFORMER_FRAME_VIEW:{
BeamformerFrameView *bv = it->var->generic;
switch (bv->kind) {
diff --git a/util.c b/util.c
@@ -627,5 +627,3 @@ lookup_file_watch_directory(FileWatchContext *ctx, u64 hash)
}
return result;
}
-
-#include "math.c"
diff --git a/util.h b/util.h
@@ -206,6 +206,12 @@ typedef union {
f32 E[16];
} m4;
+/* TODO(rnp): delete raylib */
+typedef struct {
+ v3 origin;
+ v3 direction;
+} ray;
+
typedef union {
struct { v2 pos, size; };
Rectangle rl;
@@ -339,5 +345,6 @@ struct OS {
#define LABEL_GL_OBJECT(type, id, s) {s8 _s = (s); glObjectLabel(type, id, _s.len, (c8 *)_s.data);}
#include "util.c"
+#include "math.c"
#endif /* _UTIL_H_ */