VTree_Node :: struct { index: int; value: int; depth: int; // Euclidian x, y: float; } vtree_init :: (tree_copy: []int, stat: *Statistics) -> []VTree_Node { tree = tree_copy; assign_positions(0, -(SCREEN_WIDTH / 2), SCREEN_WIDTH - (SCREEN_WIDTH / 2), 0); create_stats(stat); copy := array_copy(nodes); copy.count = node_count; return copy; } vtree_fit_zoom :: () -> Camera2D { min, max: Vector2; min.x = nodes[0].x - NODE_RADIUS; max.x = nodes[0].x + NODE_RADIUS; min.y = nodes[0].y - NODE_RADIUS; max.y = nodes[0].y + NODE_RADIUS; for i: 1..node_count-1 { if nodes[i].x - NODE_RADIUS < min.x then min.x = nodes[i].x - NODE_RADIUS; if nodes[i].x + NODE_RADIUS > max.x then max.x = nodes[i].x + NODE_RADIUS; if nodes[i].y - NODE_RADIUS < min.y then min.y = nodes[i].y - NODE_RADIUS; if nodes[i].y + NODE_RADIUS > max.y then max.y = nodes[i].y + NODE_RADIUS; } tree_w := max.x - min.x; tree_h := max.y - min.y; zoom_x := (SCREEN_WIDTH * 0.9) / tree_w; zoom_y := (SCREEN_HEIGHT * 0.9) / tree_h; fit_zoom := ifx zoom_x < zoom_y then zoom_x else zoom_y; cam := camera; cam.zoom = fit_zoom; cam.offset = { SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0 }; cam.target = { (min.x + max.x) / 2.0, (min.y + max.y) / 2.0 }; camera = cam; return cam; } vtree_draw_2d :: () { for i: 0..node_count-1 { n := nodes[i]; idx_left := 2 * n.index + 1; idx_right := 2 * n.index + 2; found_l, lc := find_node(idx_left); found_r, rc := find_node(idx_right); if found_l { DrawLineEx( { n.x, n.y }, { lc.x, lc.y }, 2.0, { 100, 180, 255, 160 } ); } if found_r { DrawLineEx( { n.x, n.y }, { rc.x, rc.y }, 2.0, { 255, 140, 100, 160 } ); } node_scale := pow(DEPTH_SCALE_NODE, cast(float)n.depth); font_scale := pow(DEPTH_SCALE_FONT, cast(float)n.depth); node_r := NODE_RADIUS * node_scale; font1_sz := max(6.0, FONT1_SIZE * font_scale); font2_sz := max(4.0, FONT2_SIZE * font_scale); tn_x := cast(s32, n.x); tn_y := cast(s32, n.y); is_root := n.index == 0; is_internal := n.value == NODE_INTERNAL; fill: Color; glow: Color; border: Color; if is_root { fill = COLOR_NODE_ROOT; } else { if is_internal then fill = COLOR_NODE_INTERNAL; else fill = COLOR_NODE_VALUE; } glow = fill + { 40, 40, 40, 0 }; glow -= { 0, 0, 0, 190 }; border = fill + { 80, 80, 80, 0 }; DrawCircle(tn_x, tn_y, node_r + 6.0, glow); if CheckCollisionPointCircle(mouse_world, { n.x, n.y }, node_r) { fill += { 30, 30, 30, 0 }; } DrawCircle(tn_x, tn_y, xx node_r, fill); DrawCircleLines(tn_x, tn_y, xx node_r, border); label: string; if is_root { label = "R"; } else if is_internal { label = "N"; } else { label = tprint("%", n.value); } label_c := to_c_string(label); tw := MeasureTextEx(GetFontDefault(), label_c, xx font1_sz, 1); DrawText( label_c, xx (tn_x - tw.x / 2), xx (tn_y - tw.y / 2), xx font1_sz, WHITE ); idx_label := to_c_string(tprint("[%]", n.index)); sw := MeasureTextEx(GetFontDefault(), idx_label, xx font2_sz, 1); DrawText( idx_label, xx (tn_x - sw.x / 2), xx (tn_y - (node_r * 2)), xx font2_sz, { 180, 180, 180, 180 } ); } } vtree_draw_screen :: (stat: *Statistics) { gui_statistics(stat); for i: 0..node_count-1 { n := nodes[i]; node_scale := pow(DEPTH_SCALE_NODE, cast(float)n.depth); node_r := NODE_RADIUS * node_scale; n_screen := GetWorldToScreen2D({ n.x, n.y }, camera); if CheckCollisionPointCircle(mouse_screen, n_screen, node_r * camera.zoom) { text := tooltip_text(n.index, n.value); tooltip(mouse_screen, text); } } } #scope_file camera_local: Camera2D; tree: []int; nodes: [NODES_MAX]VTree_Node; node_count: int; NODE_RADIUS :: 52; FONT1_SIZE :: 69; FONT2_SIZE :: 30; LEVEL_GAP_Y :: 110; LEVEL_GAP_X :: 2; PADDING :: 60; DEPTH_SCALE_NODE :: 0.8; DEPTH_SCALE_FONT :: 0.75; Range :: struct { left: float; right: float; } get_depth :: (idx: int) -> int { d: int; while idx > 0 { idx = (idx - 1) / 2; d += 1; } return d; } subtree_width :: (idx: int) -> int { if idx >= tree.count || tree[idx] == NODE_EMPTY then return 0; return 1 + subtree_width(2 * idx + 1) + subtree_width(2 * idx + 2); } assign_positions :: (idx: int, left: float, right: float, depth: int) { if idx >= tree.count || tree[idx] == NODE_EMPTY return; cx := (left + right) / 2.0; cy := cast(float)depth; if node_count < NODES_MAX { nodes[node_count].index = idx; nodes[node_count].value = tree[idx]; nodes[node_count].x = cx * LEVEL_GAP_X; nodes[node_count].y = cy * LEVEL_GAP_Y; nodes[node_count].depth = depth; node_count += 1; } // TODO: Error handling assert(node_count < NODES_MAX, "Too many nodes"); mid := (left + right) / 2.0; assign_positions(2 * idx + 1, left, mid, depth + 1); assign_positions(2 * idx + 2, mid, right, depth + 1); } find_node :: (idx: int) -> (found: bool, node: VTree_Node) { for nodes { if it.index == idx return true, it; } return false, {}; } create_stats :: (stat: *Statistics) { for i: 0..node_count-1 { if nodes[i].value == { case NODE_INTERNAL; stat.nodes += 1; case NODE_EMPTY; stat.empty += 1; case; stat.children += 1; } } }