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/Exo2.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, wave_warp_start : rl.Vector2, wave_warp_end : rl.Vector2, wave_warp_amp : f32, wave_path_start : rl.Vector2, wave_path_end : rl.Vector2, wave_path_amp : f32, centerline_thickness : 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.4, 0.0} waveform_b := [?]f32{0.0, 0.0, 0.0, 0.2, 0.4, 0.2, -0.2, 0.0, 0.1, 0.3, 0.1, 0.0} waveform_warped : [len(waveform_a)]f32 waveform_warped_target : [len(waveform_a)]f32 waveform_warped_frames : [dynamic][len(waveform_a)]f32 waveform_path : [len(waveform_a)]f32 waveform_path_target : [len(waveform_a)]f32 waveform_path_frames : [dynamic][len(waveform_a)]f32 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, billing = f32(len(waveform_a)), grid_start = {-600, 350}, grid_end = { 200, -450}, } wave_warp : [len(waveform_a)]f32 wave_path : [len(waveform_a)]f32 d : [len(waveform_a)*len(waveform_b)]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} c.wave_warp_start = c.grid_start + {0, 300} c.wave_warp_end = {c.grid_end.x, c.grid_start.y} + {0, 300} c.wave_warp_amp = 0 c.wave_path_start = c.grid_start + {0, 300} c.wave_path_end = {c.grid_end.x, c.grid_start.y} + {0, 300} c.wave_path_amp = 0 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Move waveforms into place c.wave_a_start = {c.grid_start.x, c.grid_start.y+120} c.wave_a_end = {c.grid_end.x , c.grid_start.y+120} c.wave_b_start = {c.grid_start.x-120, c.grid_start.y} c.wave_b_end = {c.grid_start.x-120, c.grid_end.y} c.wave_a_amp = 120 c.wave_b_amp = 130 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Show grid c.grid_opacity = 1 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Show data numbers c.grid_data_opacity = 1 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### 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) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### FIRST CELL d[i_from_xy(1,1)].stepped_in = 1 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) d[i_from_xy(1,1)].stepped_in = 0 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) d[i_from_xy(1,1)].cost = distance(waveform_a[1], waveform_b[1]) c.heatmap_opacity = 0.7 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Look to the three preceding cells d[i_from_xy(0,1)].stepped_in = 1 d[i_from_xy(0,0)].stepped_in = 1 d[i_from_xy(1,0)].stepped_in = 1 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) d[i_from_xy(0,1)].stepped_in = 0 d[i_from_xy(0,0)].stepped_in = 0 d[i_from_xy(1,0)].stepped_in = 0 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Detecting minimum direction for first cost calculation d[i_from_xy(1,1)].direction = .DIAGONAL append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### SECOND CELL d[i_from_xy(2,1)].stepped_in = 1 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) d[i_from_xy(2,1)].stepped_in = 0 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) d[i_from_xy(2,1)].cost = distance(waveform_a[2], waveform_b[1]) append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Detecting minimum direction for second cost calculation d[i_from_xy(2,1)].direction = .LEFT append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### 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) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### THIRD CELL d[i_from_xy(1,2)].stepped_in = 1 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) d[i_from_xy(1,2)].stepped_in = 0 d[i_from_xy(1,2)].cost += distance(waveform_a[1], waveform_b[2]) append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Detect minimum d[i_from_xy(1,2)].direction = .DOWN append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Add minimum d[i_from_xy(1,2)].cost += d[i_from_xy(1,1)].cost append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### FOURTH CELL d[i_from_xy(2,2)].cost += distance(waveform_a[2], waveform_b[2]) append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### 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) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### FIFTH CELL full_calc(d[:], 1, 3) append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### SIXTH CELL full_calc(d[:], 3, 1) append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### WAVEFRONT first half for i in 4.. 0.0001 do break } p -= f32(x) } append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### ...as deviations from the middle line ... c.centerline_thickness = 5 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### ... resulting in this offset series. // squishing down grid c.grid_start -= {0, 300} c.wave_a_start = {c.grid_start.x, c.grid_start.y+120} c.wave_a_end = {c.grid_end.x , c.grid_start.y+120} c.wave_b_start = {c.grid_start.x-120, c.grid_start.y} c.wave_b_end = {c.grid_start.x-120, c.grid_end.y} c.wave_a_amp = 150 c.wave_b_amp = 130 // bringing up offset series c.wave_path_start = {c.wave_a_start.x, c.wave_a_start.y + 220} c.wave_path_end = {c.wave_a_end.x, c.wave_a_end.y + 220} c.wave_path_amp = 30 append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### Which is ripe for smoothing smooth_waveform(wave_path[:]) append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) // ############################### append(&slides, c) append(&data_frames, d) append(&waveform_path_frames, wave_path) append(&waveform_warped_frames, wave_warp) } rect_from_index :: proc(i : int, thickness : f32 = 0) -> rl.Rectangle { // rect is top left corener and dimensions width := abs(state.grid_end.x - state.grid_start.x) height := abs(state.grid_end.y - state.grid_start.y) intergrid_dimensions : rl.Vector2 = {width, height} cell_dimensions := intergrid_dimensions/f32(len(waveform_a)-1) position_zero := state.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)) } smooth_waveform :: proc(wave : []f32) { output_waveform := make_slice([]f32, len(wave)) for i in 1.. f32 { sum : f32 = 0 for i in numbers { sum += i } return sum/f32(len(numbers)) } draw_waveform :: proc(wave : []f32, start : rl.Vector2, end : rl.Vector2, amp : f32, name : cstring, alt_color := false) { if amp<0.01 do return samples := len(wave) line_color := ORANGE dot_color := YELLOW if alt_color { line_color = rl.BLUE dot_color = rl.BLUE dot_color.g += 80 } lines := samples-1 rl.DrawLineEx(start, end, 2, rl.DARKGRAY) rl.DrawCircleV(start, 5, 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)) if i != 0 { 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, line_color) rl.DrawCircleV(start_pos, 5, dot_color) } // Text on each point point_font_size :: 24 end_value := wave[i+1] end_text : cstring 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, dot_color) name_font_size :: 34 name_pos := (start - (rl.MeasureTextEx(font, name, name_font_size, 0)*{1,0})) rl.DrawTextEx(font, name, name_pos, name_font_size, 0, rl.LIGHTGRAY) } draw_centerline :: proc() { if state.centerline_thickness < 0.5 do return line_direction := state.grid_end - state.grid_start centerline_start := state.grid_start + ( line_direction * (1.0/(f32(len(waveform_a))-1)) ) color := rl.WHITE color.a = u8(math.min(state.centerline_thickness*0.3, 1)*255) rl.DrawCircleV(centerline_start, state.centerline_thickness, color) rl.DrawLineEx( centerline_start, state.grid_end, state.centerline_thickness, color) rl.DrawCircleV(state.grid_end, state.centerline_thickness, color) } 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 :: 30 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, cell.stepped_in) } } } 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, whitening : f32=0) { thickness := state.arrow_thickness if thickness < 0.1 do return opacity : u8 = u8(255*min(thickness/2, 1)) core_color := ORANGE core_color.a = opacity outline := thickness*0.8 outline_color := BLACK outline_color.a = opacity white := rl.WHITE white.a = u8(255*whitening*min(thickness/2, 1)) 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, outline_color) rl.DrawLineEx(end, right_hand, thickness+outline, outline_color) rl.DrawLineEx(end, left_hand, thickness+outline, outline_color) rl.DrawCircleV(start, (thickness+outline)*0.5, outline_color) rl.DrawCircleV(end, (thickness+outline)*0.5, outline_color) rl.DrawCircleV(right_hand, (thickness+outline)*0.5, outline_color) rl.DrawCircleV(left_hand, (thickness+outline)*0.5, outline_color) rl.DrawLineEx(start, end, thickness, core_color) rl.DrawLineEx(end, right_hand, thickness, core_color) rl.DrawLineEx(end, left_hand, thickness, core_color) rl.DrawCircleV(start, thickness*0.5, core_color) rl.DrawCircleV(end, thickness*0.5, core_color) rl.DrawCircleV(right_hand, thickness*0.5, core_color) rl.DrawCircleV(left_hand, thickness*0.5, core_color) if whitening > 0 { rl.DrawLineEx(start, end, thickness, white) rl.DrawLineEx(end, right_hand, thickness, white) rl.DrawLineEx(end, left_hand, thickness, white) rl.DrawCircleV(start, thickness*0.5, white) rl.DrawCircleV(end, thickness*0.5, white) rl.DrawCircleV(right_hand, thickness*0.5, white) rl.DrawCircleV(left_hand, thickness*0.5, white) } } 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) if rl.IsKeyReleased(.HOME) do slide = 0 if rl.IsKeyReleased(.END) do slide = len(slides)-1 if rl.IsKeyReleased(.ENTER) { rl.ToggleBorderlessWindowed() height := f32(rl.GetScreenHeight()) width := f32(rl.GetScreenWidth()) camera.offset = {width / 2, height / 2} camera.zoom = height/1080 } // 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] waveform_warped_target = waveform_warped_frames[slide] waveform_path_target = waveform_path_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_waveform(waveform_warped[:], state.wave_warp_start, state.wave_warp_end, state.wave_warp_amp, "Lav Warped") draw_waveform(waveform_path[:], state.wave_path_start, state.wave_path_end, state.wave_path_amp, "Offset", true) draw_grid(state.grid_start, state.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) draw_centerline() 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, }