package main import rl "libraries/raylib" import "base:runtime" import "core:math" import "core:math/linalg" import "core:fmt" WINDOW_WIDTH :: 1280 WINDOW_HEIGHT :: 720 WINDOW_FRAMERATE :: 30 WINDOW_CAPTION :: "Transformation Visualizer" MATRIX_FIELD_COUNT :: 1024 MATRIX_FIELD_BYTES :: 8 program: Program FontStyle :: struct { font: rl.Font, size, spacing: f32 } Program :: struct { ui_state: UiState, matrices: [dynamic; MATRIX_FIELD_COUNT]f32, matrix_fields: [dynamic; MATRIX_FIELD_COUNT][MATRIX_FIELD_BYTES]byte, matrix_type: MatrixType, grid: Grid, } Grid :: struct { area: rect2, inner_area: rect2, zoom: f32, } grid_matrix_to_world :: proc(area: rect2, inner_area: rect2) -> mat3 { return ( matrix3_translate2(rect_get_tl(area)) * linalg.matrix3_scale(v3{**(rect_get_size(inner_area) / rect_get_size(area)), 1.0}) * matrix3_translate2(-rect_get_tl(inner_area))) } grid_get_cell_size :: proc(area: v2, lines_min: f32) -> f32 { max_area := math.max(area.x, area.y) check := math.pow10(math.floor(math.log10(max_area))) for { if max_area / check >= lines_min { return check } check_halved := check / 2.0 if max_area / check_halved >= lines_min { return check_halved } check_quartered := check / 4.0 if max_area / check_quartered >= lines_min { return check_quartered } check /= 10.0 } } grid_get_inner_size_from_zoom :: proc(zoom_amount: f32) -> f32 { return math.pow(2.0, zoom_amount) } draw_grid :: proc(grid: Grid, font_style: FontStyle) { grid_to_world_matrix := grid_matrix_to_world(grid.area, grid.inner_area) grid_cell_size := grid_get_cell_size(rect_get_size(grid.area), 5.0) grid_cell := v2{grid_cell_size, grid_cell_size} grid_subgrid_cell := grid_cell / 5.0 draw_grid_transformed(grid.area, {0.0, 0.0}, grid_cell / 5.0, grid_to_world_matrix, rl.DARKGRAY) draw_grid_transformed_annotated(grid.area, {0.0, 0.0}, grid_cell, grid_to_world_matrix, rl.GRAY, font_style) } draw_grid_transformed :: proc(area: rect2, align: v2, step: v2, transform: mat3, color: rl.Color) { grid_start := linalg.floor((v2{area.x, area.y} + align) / step) * step + step grid_stop := rect_get_br(area) for x := grid_start.x; x < grid_stop.x; x += step.x { rl.DrawLineV(matrix3_transform_v2(transform, {x, area.y}), matrix3_transform_v2(transform, {x, grid_stop.y}), color) } for y := grid_start.y; y < grid_stop.y; y += step.y { rl.DrawLineV(matrix3_transform_v2(transform, {area.x, y}), matrix3_transform_v2(transform, {grid_stop.x, y}), color) } } draw_grid_transformed_annotated :: proc(area: rect2, align: v2, step: v2, transform: mat3, color: rl.Color, style: FontStyle, temp_allocator := context.temp_allocator) { grid_outside := matrix3_transform_v2(transform, {area.x, area.y}) - 4.0 grid_start := linalg.floor((v2{area.x, area.y} + align) / step) * step + step grid_stop := rect_get_br(area) for x := grid_start.x; x < grid_stop.x; x += step.x { p1 := matrix3_transform_v2(transform, {x, area.y}) p2 := matrix3_transform_v2(transform, {x, grid_stop.y}) rl.DrawLineV(p1, p2, color) draw_text_aligned(fmt.ctprintf("%.2f", x), {p1.x, grid_outside.y}, style, .Middle, .Bottom, color) } for y := grid_start.y; y < grid_stop.y; y += step.y { p1 := matrix3_transform_v2(transform, {area.x, y}) p2 := matrix3_transform_v2(transform, {grid_stop.x, y}) rl.DrawLineV(p1, p2, color) draw_text_aligned(fmt.ctprintf("%.2f", y), {grid_outside.x, p1.y}, style, .Right, .Center, color) } } draw_text :: proc(text: cstring, position: v2, style: FontStyle, color: rl.Color) { rl.DrawTextEx(style.font, text, position, style.size, style.spacing, color) } TextHalign :: enum { Left, Middle, Right, } TextValign :: enum { Top, Center, Bottom, } draw_text_aligned :: proc(text: cstring, position: v2, style: FontStyle, halign: TextHalign, valign: TextValign, color: rl.Color) { size := measure_text(text, style) position := position switch halign { case .Left: case .Middle: position.x -= size.x / 2.0 case .Right: position.x -= size.x } switch valign { case .Top: case .Center: position.y -= size.y / 2.0 case .Bottom: position.y -= size.y } draw_text(text, position, style, color) } measure_text :: proc(text: cstring, style: FontStyle) -> v2 { return rl.MeasureTextEx(style.font, text, style.size, style.spacing) } MatrixType :: enum { Mat2, Mat3, Mat4, } @rodata matrix_type_table := [MatrixType]typeid { .Mat2 = mat2, .Mat3 = mat3, .Mat4 = mat4, } init :: proc() { rl.SetTraceLogLevel(.WARNING) rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT}) rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) rl.SetTargetFPS(WINDOW_FRAMERATE) ui_style := UiStyle{ margin = 4.0, padding = 4.0, font_style = { font = rl.GetFontDefault(), size = 16.0, spacing = 1.0 } } ui_init(&program.ui_state, ui_style, {0, 0}, context.temp_allocator) } matrix_add :: proc() { matrices := &program.matrices append(matrices, 1) append(matrices, 0) append(matrices, 1) append(matrices, 0) fields := &program.matrix_fields append(fields, [MATRIX_FIELD_BYTES]byte{}) append(fields, [MATRIX_FIELD_BYTES]byte{}) append(fields, [MATRIX_FIELD_BYTES]byte{}) append(fields, [MATRIX_FIELD_BYTES]byte{}) } update :: proc() { if rl.IsMouseButtonPressed(.LEFT) { ui_element_focus_reset(&program.ui_state) } ui_button_size_top_bar := v2{32.0, 32.0} ui_start(&program.ui_state) ui_container_begin({label = ""}) ui_button(ui_button_size_top_bar, {label = "#10#", callback = matrix_add}) ui_same_line() ui_button(ui_button_size_top_bar, {label = "#11#"}) ui_same_line() ui_button(ui_button_size_top_bar, {label = "#12#"}) ui_container_end() ui_container_begin({label = "Matrices"}) if len(program.matrices) == 0 { ui_text({text = "Add matrices", color = rl.WHITE}) } else { ui_input_size_matrix := v2{48.0, 32.0} switch program.matrix_type { case .Mat2: mat := cast(^mat2)&program.matrices[0] fields := &program.matrix_fields ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[0][:]}) ui_same_line() ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[1][:]}) ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[2][:]}) ui_same_line() ui_input(ui_input_size_matrix, {label = "", min = 0, max = 1000, input = fields[3][:]}) case .Mat3, .Mat4: } } ui_container_end() ui_elements := ui_end() rl.BeginDrawing() defer rl.EndDrawing() rl.ClearBackground(rl.BLACK) for element in ui_elements { switch element.type { case .Button: data := element.data.(UiElementData_Button) if rl.GuiButton(element.area, data.label) && data.callback != nil { data.callback() } case .Input: data := element.data.(UiElementData_Input) edit_mode := ui_element_is_editing(program.ui_state, element.id) input_cstr := cstring(raw_data(data.input)) if (rl.GuiTextBox(element.area, input_cstr, i32(len(data.input) - 1), edit_mode)) { ui_element_focus(&program.ui_state, element.id) } case .Container: data := element.data.(UiElementData_Container) if len(data.label) == 0 { rl.DrawRectangleRoundedLinesEx(element.area, 0.1, 2, 1.0, rl.WHITE) } else { rl.GuiGroupBox(element.area, data.label) } case .Text: data := element.data.(UiElementData_Text) style := program.ui_state.style draw_text(data.text, rect_get_tl(element.area), style.font_style, data.color) } } free_all(context.temp_allocator) } parent_window_size_changed :: proc(w, h: int) { rl.SetWindowSize(i32(w), i32(h)) } shutdown :: proc() { rl.CloseWindow() } should_run :: proc() -> bool { when ODIN_OS != .JS { if rl.WindowShouldClose() { return false } } return true }