package main import rl "vendor:raylib" import "core:fmt" import "core:math" import "core:os" import "core:strconv" import "core:reflect" import "core:strings" MONO_FONT :: #load("../res/RedHatMono.ttf") font : rl.Font BLACK : rl.Color = {16, 16, 16, 255} YELLOW : rl.Color = {254, 175, 1, 255} ORANGE : rl.Color = {238, 123, 26, 255} RED : rl.Color = {222, 73, 50, 255} LERP_SPEED :: 0.10 HIGHLIGHT_THICKNESS :: 4 layout :: struct { wave_a_start : rl.Vector2, wave_a_end : rl.Vector2, wave_a_amp : f32, wave_b_start : rl.Vector2, wave_b_end : rl.Vector2, wave_b_amp : f32, grid_opacity : f32, grid_data_opacity : f32, highlight_opacity : f32, highlight_index : int, highlight_position : rl.Vector2, highlight_dimensions : rl.Vector2, arrow_thickness : f32, arrow_chopping : f32, heatmap_opacity : f32, billing : f32, camera_position : rl.Vector2, camera_zoom : f32, grid_start : rl.Vector2 grid_end : rl.Vector2 } waveform_a := [?]f32{0.0, 0.1, 0.5, 0.3, 0.1, -0.1, 0.0, 0.0, 0.0, 0.1, 0.3, 0.0} waveform_b := [?]f32{0.0, 0.0, 0.0, 0.2, 0.4, 0.2, -0.1, 0.0, 0.1, 0.3, 0.1, 0.0} data_frames : [dynamic][len(waveform_a)*len(waveform_b)]cell slides : [dynamic]layout state : layout target : layout data_state : [len(waveform_a)*len(waveform_b)]cell data_target : [len(waveform_a)*len(waveform_b)]cell delta : f32 init_slides :: proc() { // ---------------------------- Waveforms c : layout = { wave_a_start = {-600, -200}, wave_a_end = {600, -200}, wave_a_amp = 260, wave_b_start = {-600, 200}, wave_b_end = {600, 200}, wave_b_amp = 260, grid_opacity = 0, grid_data_opacity = 0, highlight_opacity = 0, highlight_position = 0, arrow_thickness = 3, arrow_chopping = 0.3, heatmap_opacity = 0.5, billing = f32(len(waveform_a)), grid_start = {-400, 350}, grid_end = { 400, -450}, } d : [12*12]cell highlight_rect := rect_from_index(0, HIGHLIGHT_THICKNESS) c.highlight_dimensions = {highlight_rect.width, highlight_rect.height} c.highlight_position = {highlight_rect.x, highlight_rect.y} append(&slides, c) append(&data_frames, d) // ---------------------------- Move waveforms into place c.wave_a_start = {grid_start.x, grid_start.y+120} c.wave_a_end = {grid_end.x , grid_start.y+120} c.wave_b_start = {grid_start.x-120, grid_start.y} c.wave_b_end = {grid_start.x-120, grid_end.y} c.wave_a_amp = 120 c.wave_b_amp = 130 append(&slides, c) append(&data_frames, d) // ---------------------------- Show grid c.grid_opacity = 1 append(&slides, c) append(&data_frames, d) // ---------------------------- Show data numbers c.grid_data_opacity = 1 append(&slides, c) append(&data_frames, d) // ---------------------------- Infinity coating for _, i in waveform_a { if i==0 do continue d[i_from_xy(i, 0)].cost = math.INF_F32 d[i_from_xy(0, i)].cost = math.INF_F32 } append(&slides, c) append(&data_frames, d) // ---------------------------- FIRST CELL d[i_from_xy(1,1)].cost = distance(waveform_a[1], waveform_b[1]) append(&slides, c) append(&data_frames, d) // ---------------------------- Detecting minimum direction for first cost calculation d[i_from_xy(1,1)].direction = .DIAGONAL append(&slides, c) append(&data_frames, d) // ---------------------------- SECOND CELL d[i_from_xy(2,1)].cost = distance(waveform_a[2], waveform_b[1]) append(&slides, c) append(&data_frames, d) // ---------------------------- Detecting minimum direction for second cost calculation d[i_from_xy(2,1)].direction = .LEFT append(&slides, c) append(&data_frames, d) // ---------------------------- Adding previous minimum to second cost calculation d[i_from_xy(2,1)].cost += d[i_from_xy(1,1)].cost append(&slides, c) append(&data_frames, d) // ---------------------------- THIRD CELL d[i_from_xy(1,2)].cost += distance(waveform_a[1], waveform_b[2]) append(&slides, c) append(&data_frames, d) // ---------------------------- Detect minimum d[i_from_xy(1,2)].direction = .DOWN append(&slides, c) append(&data_frames, d) // ---------------------------- Add minimum d[i_from_xy(1,2)].cost += d[i_from_xy(1,1)].cost append(&slides, c) append(&data_frames, d) // ---------------------------- FOURTH CELL d[i_from_xy(2,2)].cost += distance(waveform_a[2], waveform_b[2]) append(&slides, c) append(&data_frames, d) // ---------------------------- Detect minimum a_cost, a_direction := three_min(d[i_from_xy(1,2)], d[i_from_xy(1,1)], d[i_from_xy(2,1)]) d[i_from_xy(2,2)].cost += a_cost d[i_from_xy(2,2)].direction = a_direction append(&slides, c) append(&data_frames, d) // ---------------------------- FIFTH CELL full_calc(d[:], 1, 3) append(&slides, c) append(&data_frames, d) // ---------------------------- SIXTH CELL full_calc(d[:], 3, 1) append(&slides, c) append(&data_frames, d) // ---------------------------- WAVEFRONT first half for i in 4.. rl.Rectangle { // rect is top left corener and dimensions width := abs(grid_end.x - grid_start.x) height := abs(grid_end.y - grid_start.y) intergrid_dimensions : rl.Vector2 = {width, height} cell_dimensions := intergrid_dimensions/f32(len(waveform_a)-1) position_zero := grid_start-(cell_dimensions/2) output := position_zero + cell_dimensions * {f32(i%len(waveform_a)), -f32(i/len(waveform_b))} thickness_offset := thickness/2 return {output.x-thickness_offset, output.y-thickness_offset, cell_dimensions.x+thickness_offset, cell_dimensions.y+thickness_offset} } i_from_xy :: proc(x : int, y : int) -> int { return y * len(waveform_a) + x } lerp :: proc() { layout_info := type_info_of(layout) // Handle the named type wrapper actual_info := layout_info if named_info, ok := layout_info.variant.(reflect.Type_Info_Named); ok { actual_info = named_info.base } // Now cast to struct struct_info := actual_info.variant.(reflect.Type_Info_Struct) for i in 0.. rl.Vector2 { return v / math.sqrt((v.x*v.x) + (v.y*v.y)) } draw_waveform :: proc(wave : []f32, start : rl.Vector2, end : rl.Vector2, amp : f32, name : cstring) { samples := len(wave) lines := samples-1 rl.DrawLineEx(start, end, 2, rl.DARKGRAY) total_direction := end - start step := total_direction / f32(lines) perpendicular : rl.Vector2 = normalize({total_direction.y, -total_direction.x}) end_pos : rl.Vector2 for value, i in wave[:lines] { start_base := start + (step*f32(i)) end_base := start + (step*f32(i+1)) start_pos : rl.Vector2 = start_base + (perpendicular*wave[i]*amp) end_pos = end_base + (perpendicular*wave[i+1]*amp) rl.DrawLineEx(start_pos, end_pos, 4, ORANGE) rl.DrawCircleV(start_pos, 5, YELLOW) // Text on each point point_font_size :: 24 end_value := wave[i+1] end_text := fmt.caprintf("%.1f", end_value, allocator = context.temp_allocator) value_pos := (end_base + (perpendicular*(wave[i+1]*amp-25))) - (rl.MeasureTextEx(font, end_text, point_font_size, 0)*0.5) rl.DrawTextEx(font, end_text, value_pos, point_font_size, 0, rl.GRAY) } rl.DrawCircleV(end_pos, 5, YELLOW) rl.DrawTextEx(font, name, start, 32, 0, rl.LIGHTGRAY) } draw_grid :: proc(first : rl.Vector2, last : rl.Vector2, data : []cell) { highest_cost : f32 = 0 for c in data { if c.cost > highest_cost && c.cost != math.INF_F32 { highest_cost = c.cost } } color := rl.DARKGRAY color.a = u8(255.0*state.grid_opacity) text_color := rl.WHITE text_color.a = u8(255.0*state.grid_data_opacity) cost_font_size :: 28 for cell, i in data { // Cell bg color cell_bg := RED cell_bg.a = u8(255*cell.cost/highest_cost*state.heatmap_opacity) if cell.cost == math.INF_F32 do cell_bg = YELLOW rect := rect_from_index(i, 2) rl.DrawRectangleRec(rect, cell_bg) // Stepped in stepped_bg := rl.WHITE stepped_bg.a = u8(255*cell.stepped_in*0.8) rl.DrawRectangleRec(rect, stepped_bg) // Outline rl.DrawRectangleLinesEx(rect, 2*state.grid_opacity, color) // Cost value text cost_string := fmt.caprintf("%.2f", cell.cost, allocator = context.temp_allocator) text_dim := rl.MeasureTextEx(font, cost_string, cost_font_size, 0) text_draw_point := rl.Vector2{rect.x, rect.y}+(({rect.width, rect.height}-text_dim)*0.5) rl.DrawTextEx(font, fmt.ctprintf("%.2f", cell.cost), text_draw_point, cost_font_size, 0, text_color) if cell.direction != .NONE { target_point : rl.Vector2 #partial switch cell.direction { case .LEFT: target_point = middle_of_rect(rect_from_index(i-1)) case .DIAGONAL: target_point = middle_of_rect(rect_from_index(i-len(waveform_a)-1)) case .DOWN: target_point = middle_of_rect(rect_from_index(i-len(waveform_a))) } arrow_start := middle_of_rect(rect) arrow_end := target_point diff := arrow_end - arrow_start arrow_start += diff*state.arrow_chopping arrow_end -= diff*state.arrow_chopping draw_arrow(arrow_start, arrow_end) } } } middle_of_rect :: proc(rect : rl.Rectangle) -> rl.Vector2 { return {rect.x+(rect.width*0.5), rect.y+(rect.height*0.5)} } draw_arrow :: proc(start, end : rl.Vector2) { thickness := state.arrow_thickness outline := thickness*0.8 total_direction := end - start perpendicular : rl.Vector2 = normalize({total_direction.y, -total_direction.x}) reverse : rl.Vector2 = normalize({-total_direction.x, -total_direction.y}) right_hand : rl.Vector2 = end + (reverse*10) + (perpendicular*-10) left_hand : rl.Vector2 = end + (reverse*10) + (perpendicular*10) rl.DrawLineEx(start, end, thickness+outline, BLACK) rl.DrawLineEx(end, right_hand, thickness+outline, BLACK) rl.DrawLineEx(end, left_hand, thickness+outline, BLACK) rl.DrawCircleV(start, (thickness+outline)*0.5, BLACK) rl.DrawCircleV(end, (thickness+outline)*0.5, BLACK) rl.DrawCircleV(right_hand, (thickness+outline)*0.5, BLACK) rl.DrawCircleV(left_hand, (thickness+outline)*0.5, BLACK) rl.DrawLineEx(start, end, thickness, ORANGE) rl.DrawLineEx(end, right_hand, thickness, ORANGE) rl.DrawLineEx(end, left_hand, thickness, ORANGE) rl.DrawCircleV(start, thickness*0.5, ORANGE) rl.DrawCircleV(end, thickness*0.5, ORANGE) rl.DrawCircleV(right_hand, thickness*0.5, ORANGE) rl.DrawCircleV(left_hand, thickness*0.5, ORANGE) } main :: proc() { // Initialization //-------------------------------------------------------------------------------------- slide : int = 0 if len(os.args) > 1 { slide, _ = strconv.parse_int(os.args[1]) } rotation : f32 = 0.0 cameraX : f32 = 0.0 cameraY : f32 = 0.0 camera : rl.Camera2D = { zoom=1 } init_slides() rl.SetConfigFlags({.WINDOW_RESIZABLE}) rl.InitWindow(1920, 1080, "BSC 2025 Presentation") rl.SetTargetFPS(60) rl.SetExitKey(nil) camera.offset = {f32(rl.GetScreenWidth()) / 2, f32(rl.GetScreenHeight()) / 2} camera.zoom = f32(rl.GetScreenHeight())/1080 font = rl.LoadFontFromMemory(".ttf", raw_data(MONO_FONT), i32(len(MONO_FONT)), 128, nil, 0) //rl.ToggleBorderlessWindowed() for !rl.WindowShouldClose() { delta = rl.GetFrameTime() if rl.IsWindowResized() { height := f32(rl.GetScreenHeight()) width := f32(rl.GetScreenWidth()) camera.offset = {width / 2, height / 2} camera.zoom = height/1080 } // Input //---------------------------------------------------------------------------------- go_forward := false go_back := false mousePosition := rl.GetMousePosition() left_clicked := rl.IsMouseButtonReleased(rl.MouseButton(0)) right_clicked := rl.IsMouseButtonReleased(rl.MouseButton(1)) go_forward = left_clicked || rl.IsKeyReleased(.RIGHT) || rl.IsKeyReleased(.PAGE_DOWN) go_back = right_clicked || rl.IsKeyReleased(.LEFT) || rl.IsKeyReleased(.PAGE_UP) // Process //---------------------------------------------------------------------------------- if go_forward && slide < len(slides)-1 { slide += 1 fmt.printfln("Forward! To slide #{}", slide) rect_from_index(1) } else if go_back && slide > 0 { slide -= 1 fmt.printfln("Back! To slide #{}", slide) } target = slides[slide] data_target = data_frames[slide] lerp() // Draw //---------------------------------------------------------------------------------- rl.BeginDrawing() rl.ClearBackground(BLACK) rl.BeginMode2D(camera) // World-space drawing draw_waveform(waveform_a[:], state.wave_a_start, state.wave_a_end, state.wave_a_amp, "Boom") draw_waveform(waveform_b[:], state.wave_b_start, state.wave_b_end, state.wave_b_amp, "Lav") draw_grid(grid_start, grid_end, data_state[:]) highlight_color := YELLOW highlight_color.a = u8(255*state.highlight_opacity) rl.DrawRectangleLinesEx({state.highlight_position.x, state.highlight_position.y, state.highlight_dimensions.x, state.highlight_dimensions.y}, HIGHLIGHT_THICKNESS, highlight_color) rl.EndMode2D() // Screen-space drawing rl.DrawFPS(rl.GetScreenWidth() - 95, 10) rl.EndDrawing() //---------------------------------------------------------------------------------- free_all(context.temp_allocator) } rl.CloseWindow() } distance :: proc(a, b : f32) -> f32 { //return (a - b)*(a - b); return math.abs(a - b); } three_min :: proc(left, diagonal, down : cell) -> (f32, DIRECTION) { if (left.cost <= diagonal.cost && left.cost <= down.cost) do return left.cost, .LEFT if (down.cost <= diagonal.cost && down.cost <= left.cost) do return down.cost, .DOWN return diagonal.cost, .DIAGONAL; } full_calc :: proc(d: []cell, x, y : int) { d[i_from_xy(x,y)].cost = distance(waveform_a[x], waveform_b[y]) a_cost, a_direction := three_min(d[i_from_xy(x-1, y )], d[i_from_xy(x-1, y-1)], d[i_from_xy(x , y-1)]) d[i_from_xy(x,y)].cost += a_cost d[i_from_xy(x,y)].direction = a_direction } cell :: struct { cost : f32, direction : DIRECTION, stepped_in : f32, } DIRECTION :: enum { NONE, LEFT, DIAGONAL, DOWN, }