Author:ptrace Comitter:ptrace Date:2026-05-05 04:59:35 UTC

region zoom; tree nodes smaller with higher depth; mode indicator

diff --git a/src/cells.jai b/src/cells.jai new file mode 100644 index 0000000..8eb0395 --- /dev/null +++ b/src/cells.jai @@ -0,0 +1,204 @@ cells_init :: (tree_copy: []int) {     tree = tree_copy;     row_count, col_count = dimensions(tree.count); } cells_fit_zoom :: ($update_camera: bool = true) -> Camera2D {     width  := col_count * CELL_WIDTH_HEIGHT;     height := row_count * CELL_WIDTH_HEIGHT;     center := Vector2.{ width / 2.0, height / 2.0 };     zoom_x := (SCREEN_WIDTH  * 0.7) / width;     zoom_y := (SCREEN_HEIGHT * 0.7) / height;     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 = center;     #if update_camera then camera = cam;     return cam; } cells_draw_2d :: () {     code :: #code {         label: string;         color: Color;         if it == {         case 0;             label = "R";             color = COLOR_NODE_ROOT;         case NODE_INTERNAL;             label = "N";             color = COLOR_NODE_INTERNAL;         case NODE_EMPTY;             color = COLOR_NODE_EMPTY;         case;             label = tprint("%", it);             color = COLOR_NODE_VALUE;         }         if CheckCollisionPointRec(mouse_world, rect) {             color += Color.{ 30, 30, 30, 0 };         }         DrawRectangleRec(rect, color);         DrawRectangleLinesEx(rect, 1.0, COLOR_CELLS_BORDER);         do_text(label, { rect.x, rect.y }, 30, .CENTER, WHITE);         do_text(             tprint("%", it_index),             { rect.x, rect.y },             10,             .NORTH_WEST,             WHITE         );     }     traverse_tree_array(code); } cells_draw_screen :: () {     info := to_c_string(tprint("Array Items %", tree.count));     DrawText(         info,         SCREEN_WIDTH - MeasureText(info, 18) - 16,         SCREEN_HEIGHT - 24,         18,         { 160, 160, 200, 220 }     );     code :: #code {         if CheckCollisionPointRec(             mouse_world, rect         ) {             text := tooltip_text(it_index, it);             tooltip(mouse_screen, text);         }     }     traverse_tree_array(code); } #scope_file tree: []int; row_count, col_count: int; CELL_WIDTH_HEIGHT :: 100.0; CELL_ASPECT_RATIO :float: #run 16.0 / 9.0; CELL_GAP :: 5.0; /** Overkill, but maybe we need it */ Anchor :: enum {     CENTER;     NORTH;     EAST;     SOUTH;     WEST;     NORTH_EAST;     NORTH_WEST;     SOUTH_EAST;     SOUTH_WEST; } dimensions :: (length: int) -> (row: int, col: int) {     flen := cast(float, length);     row  := ceil(sqrt(flen / CELL_ASPECT_RATIO));     col  := ceil(flen / row);     return cast(int, row), cast(int, col); } traverse_tree_array :: ($code1: Code) {     rect: Rectangle;     rect.x = 0.0;     rect.y = 0.0;     rect.width  = CELL_WIDTH_HEIGHT;     rect.height = CELL_WIDTH_HEIGHT;     idx_col := col_count;     for tree {         if idx_col {             #insert,scope() code1;             rect.x += CELL_WIDTH_HEIGHT + CELL_GAP;             idx_col -= 1;         } else {             rect.y += CELL_WIDTH_HEIGHT + CELL_GAP;             rect.x  = 0.0;             idx_col = col_count;         }     } } /** Overkill, but maybe we need it */ do_text :: (text: string, pos: Vector2, size: s32, anchor: Anchor, color: Color) {     label_c := to_c_string(text);     tw := MeasureTextEx(GetFontDefault(), label_c, xx size, 1);     local_pos: Vector2;     PADDING :: Vector2.{ 6.0, 4.0 };     if anchor == {     case .CENTER;         local_pos.x = pos.x + (CELL_WIDTH_HEIGHT / 2.0) - (tw.x / 2.0);         local_pos.y = pos.y + (CELL_WIDTH_HEIGHT / 2.0) - (tw.y / 2.0);     case .NORTH;         local_pos = pos + { CELL_WIDTH_HEIGHT / 2.0 - (tw.x / 2.0), PADDING.y };     case .EAST;         local_pos = pos + {             CELL_WIDTH_HEIGHT - (tw.x + PADDING.x),             CELL_WIDTH_HEIGHT / 2.0 - (tw.y / 2.0)         };     case .SOUTH;         local_pos = pos + {             CELL_WIDTH_HEIGHT / 2.0 - (tw.x / 2.0),             (CELL_WIDTH_HEIGHT - PADDING.y - tw.y)         };     case .WEST;         local_pos = pos + {             PADDING.x,             CELL_WIDTH_HEIGHT / 2.0 - (tw.y / 2.0)         };     case .NORTH_EAST;         local_pos = pos + { CELL_WIDTH_HEIGHT - PADDING.x - tw.x, PADDING.y };     case .NORTH_WEST;         local_pos = pos + PADDING;     case .SOUTH_EAST;         local_pos = pos + {             CELL_WIDTH_HEIGHT - PADDING.x - tw.x,             CELL_WIDTH_HEIGHT - PADDING.y - tw.y         };     case .SOUTH_WEST;         local_pos = pos + {             PADDING.x,             CELL_WIDTH_HEIGHT - PADDING.y - tw.y         };     }     DrawText(         label_c,         xx local_pos.x,         xx local_pos.y,         size,         color     ); } diff --git a/src/gui.jai b/src/gui.jai new file mode 100644 index 0000000..ddf4902 --- /dev/null +++ b/src/gui.jai @@ -0,0 +1,59 @@ gui_init :: () { } gui_draw_screen :: () {     using container;     x = (GetScreenWidth() / 2.0) - (WIDTH / 2.0);     y = GetScreenHeight() - HEIGHT;     width  = WIDTH;     height = HEIGHT;     left  := rcut_from_left(*container, 0.333333);     mid   := rcut_from_left(*container, 0.5);     right := rcut_from_left(*container, 1.0);     SIZE :: 30.0;     draw_indicator(left,  .TRAD_TREE);     draw_indicator(mid,   .HYPER_TREE);     draw_indicator(right, .CELLS);     draw_text(left,  "vTree", SIZE, .TRAD_TREE);     draw_text(mid,   "hTree", SIZE, .HYPER_TREE);     draw_text(right, "Cells", SIZE, .CELLS); } #scope_file container: Rectangle; WIDTH  :: 420.0; HEIGHT :: 60.0; draw_text :: (rect: Rectangle, text: string, size: float, m: Mode) {     color := ifx mode == m then COLOR_BACKGROUND else WHITE;     ctext := to_c_string(text);     mt := MeasureTextEx(GetFontDefault(), ctext, size, 1.0);     x := rect.x + (rect.width  / 2.0) - (mt.x / 2.0) - 3.0;     y := rect.y + (rect.height / 2.0) - (mt.y / 2.0);     DrawText(ctext, xx x, xx y, xx size, color); } draw_indicator :: (rect: Rectangle, m: Mode) {     if mode == m     then DrawRectangleRec(rect, WHITE);     else DrawRectangleLinesEx(rect, 1.0, WHITE); } diff --git a/src/input.jai b/src/input.jai new file mode 100644 index 0000000..9c5b5d5 --- /dev/null +++ b/src/input.jai @@ -0,0 +1,152 @@ Mode :: enum #specified {     TRAD_TREE   :: 0;     HYPER_TREE  :: 1;     CELLS       :: 2; } input :: () {     /* ----------- *      * Zoom & Drag *      * ----------- */     if IsKeyDown(.LEFT_SHIFT) {         dragging = false;         if !selecting then SetMouseCursor(.DEFAULT);         if IsMouseButtonPressed(.LEFT) {             selecting = true;             selection.x = mouse_world.x;             selection.y = mouse_world.y;             SetMouseCursor(.RESIZE_NESW);         }     }     if IsMouseButtonPressed(.LEFT) {         drag_start = mouse_screen;         dragging   = true;         SetMouseCursor(.RESIZE_ALL);     }     if IsMouseButtonReleased(.LEFT) {         dragging = false;         SetMouseCursor(.DEFAULT);         if selecting {             selecting = false;             zoom_to_selection();         }     }     if IsMouseButtonPressed(.RIGHT) {         if selecting then selecting = false;     }     if dragging {         camera.target.x -= (mouse_screen.x - drag_start.x) / camera.zoom;         camera.target.y -= (mouse_screen.y - drag_start.y) / camera.zoom;         drag_start = mouse_screen;     }     wheel := GetMouseWheelMove();     if wheel != 0.0 {         before := Vector2.{             camera.target.x + (mouse_screen.x - camera.offset.x) / camera.zoom,             camera.target.y + (mouse_screen.y - camera.offset.y) / camera.zoom         };         camera.zoom *= (1.0 + wheel * 0.1);         // TODO: Maybe lerp?         // t := 1.0 - exp(-8.0 * frame_time);         // camera.zoom *= lerp(thing1, thing2, t);         camera.zoom  = clamp(camera.zoom, 0.1, 5.0);         camera.target.x = before.x - (mouse_screen.x - camera.offset.x) / camera.zoom;         camera.target.y = before.y - (mouse_screen.y - camera.offset.y) / camera.zoom;     }     /* ----- *      * Modes *      * ----- */     if IsKeyPressed(.F1) {         if mode == .TRAD_TREE {             vtree_fit_zoom();         } else {             mode = .TRAD_TREE;             state_switch(mode);         }     }     else if IsKeyPressed(.F2) {         if mode == .HYPER_TREE {             // tbd         } else {             mode = .HYPER_TREE;             // state_switch(mode);         }     }     else if IsKeyPressed(.F3) {         if mode == .CELLS {             cells_fit_zoom();         } else {             mode = .CELLS;             state_switch(mode);         }     } } input_draw_selection :: () {     if !selecting return;     using selection;     width = mouse_world.x - x;     height = mouse_world.y - y;     DrawRectangleLinesEx(selection, 2.0, GREEN); } // flag_has :: (flag: Mode) -> bool { //     return cast(bool, mode & flag); // } // flag_add :: (flag: Mode) { //     mode |= flag; // } // flag_remove :: (flag: Mode) { //     mode &= ~flag; // } #scope_file drag_start: Vector2; dragging: bool; selection: Rectangle; selecting: bool; zoom_to_selection :: () {     using selection;     center := Vector2.{ x + width / 2.0, y + height / 2.0 };     zoom_x := cast(float, GetScreenWidth()  / width);     zoom_y := cast(float, GetScreenHeight() / height);     new_zoom := min(zoom_x, zoom_y) * 0.9;     camera.offset = { GetScreenWidth() / 2.0, GetScreenHeight() / 2.0 };     camera.target = center;     camera.zoom   = new_zoom; } diff --git a/src/main.jai b/src/main.jai index 170e051..8a26c95 100644 --- a/src/main.jai +++ b/src/main.jai @@ -5,29 +5,26 @@ #import "Print_Vars"; #import "raylib"; #load "tree_viz.jai"; #load "rect_cut.jai"; #load "gui.jai"; #load "input.jai"; #load "tooltip.jai"; #load "state.jai"; #load "vtree.jai"; #load "cells.jai"; /*         (0)         / \       (1) (2)       /     \  (3, 4)     (5, 6) l = 2 * i + 1 r = 2 * i + 2 parent = floor((i - 1) / 2) [     0, 1, 2,  ] /** Ideas / Todo     - Check the CELLS array, it looks scuffed!     - Hot reload a binary dump     - Update the binary dump     - Half the size of the nodes with every depth     - Smooth zoom (maybe)     - Different font     - Generic approach for other types of info data in the tooltip     - Second option for visualizing the tree -> https://en.wikipedia.org/wiki/Hyperbolic_tree */ @@ -47,8 +44,14 @@ rects := Rectangle.[ tree: [..]int; bvs: [..]Aabb; mode: Mode; camera: Camera2D; frame_time: float; mouse_screen: Vector2; mouse_world: Vector2; color_choice := Color.[     ORANGE,     YELLOW, @@ -60,6 +63,13 @@ NODE_ROOT     :: -9; NODE_INTERNAL :: -7; NODE_EMPTY    :: -1; COLOR_NODE_ROOT     :: Color.{ 138, 62, 81, 255 }; COLOR_NODE_INTERNAL :: Color.{ 30, 60, 120, 255 }; COLOR_NODE_EMPTY    :: #run COLOR_BACKGROUND + { 10, 10, 10, 0 }; COLOR_NODE_VALUE    :: Color.{ 30, 110, 70, 255 }; COLOR_CELLS_BORDER  :: #run COLOR_BACKGROUND + { 70, 70, 75, 0 }; COLOR_BACKGROUND    :: Color.{ 42, 42, 60, 255 }; DRAW_BV :: true; @@ -71,6 +81,7 @@ SCREEN_HEIGHT :: 1080; TREE_VIEW :: true; Aabb :: struct {     upper_bound: Vector2;     lower_bound: Vector2; @@ -116,78 +127,81 @@ Node :: struct {         // Refit     }     bounds: Treev_Bounds;     tree_copy := array_copy(tree);     #if TREE_VIEW then treev_init(tree, *bounds);     vtree_init(tree_copy);     cells_init(tree_copy);     state_vtree: State;     state_cells: State;     {         using bounds;         camera.zoom   = fit_zoom;         camera.offset = { SCREEN_WIDTH / 2.0, SCREEN_HEIGHT / 2.0 };         camera.target = { (min.x + max.x) / 2.0, (min.y + max.y) / 2.0 };         using state_cells;         id = .CELLS;         camera = cells_fit_zoom();     }     drag_start: Vector2;     dragging: bool;     {         using state_vtree;         id = .TRAD_TREE;         camera = vtree_fit_zoom();     }     state_init(mode, state_vtree, state_cells);     push_allocator(temp);     while !WindowShouldClose() {         if IsMouseButtonPressed(.LEFT) {             drag_start = GetMousePosition();             dragging   = true;         }         if IsMouseButtonReleased(.LEFT) then dragging = false;         if dragging {             current := GetMousePosition();             camera.target.x -= (current.x - drag_start.x) / camera.zoom;             camera.target.y -= (current.y - drag_start.y) / camera.zoom;             drag_start    = current;         }     gui_init();         wheel := GetMouseWheelMove();         if wheel != 0.0 {             mouse_screen := GetMousePosition();             before := Vector2.{                 camera.target.x + (mouse_screen.x - camera.offset.x) / camera.zoom,                 camera.target.y + (mouse_screen.y - camera.offset.y) / camera.zoom             };             camera.zoom *= (1.0 + wheel * 0.1);             if camera.zoom < 0.1 then camera.zoom = 0.1;             if camera.zoom > 5.0 then camera.zoom = 5.0;     push_allocator(temp);     while !WindowShouldClose() {         frame_time = GetFrameTime();             camera.target.x = before.x - (mouse_screen.x - camera.offset.x) / camera.zoom;             camera.target.y = before.y - (mouse_screen.y - camera.offset.y) / camera.zoom;         }         mouse_screen = GetMousePosition();         mouse_world  = GetScreenToWorld2D(mouse_screen, camera);         input();         BeginDrawing();         defer EndDrawing();         ClearBackground({ 42, 42, 60, 255 });         ClearBackground(COLOR_BACKGROUND);         gx: s32;         while gx < SCREEN_WIDTH {             DrawLine(gx, 0, gx, SCREEN_HEIGHT, { 255,255,255,12 });             DrawLine(gx, 0, gx, SCREEN_HEIGHT, { 255, 255, 255, 12 });             gx += 40;         }         gy: s32;         while gy < SCREEN_HEIGHT {             DrawLine(0, gy, SCREEN_WIDTH, gy, { 255,255,255,12 });             DrawLine(0, gy, SCREEN_WIDTH, gy, { 255, 255, 255, 12 });             gy += 40;         }         {             BeginMode2D(camera);             defer EndMode2D();             #if TREE_VIEW then treev_draw();             if mode == {             case .TRAD_TREE;  vtree_draw_2d();             case .HYPER_TREE; // Todo;             case .CELLS;      cells_draw_2d();             }             input_draw_selection();         }         #if TREE_VIEW then treev_screen_draw();         if mode == {         case .TRAD_TREE;  vtree_draw_screen();         case .HYPER_TREE; // Todo;         case .CELLS;      cells_draw_screen();         }         DrawFPS(10, 10);         DrawText(TextFormat("Frame Time: % s", GetFrameTime()), 10, 40, 20, WHITE);         DrawText(TextFormat("Frame Time: % s", frame_time), 10, 40, 20, WHITE);         DrawText(TextFormat("Mouse (World):  %", mouse_world), 10, 80, 20, WHITE);         DrawText(TextFormat("Mouse (Screen): %", mouse_screen), 10, 120, 20, WHITE);         gui_draw_screen();         reset_temporary_storage();     } @@ -357,3 +371,14 @@ aabb_get_sa :: (a: Aabb, b: Aabb) -> float {     return sa; } operator + :: (a: Color, b: Color) -> Color {     c: Color = ---;     c.r = a.r + b.r;     c.g = a.g + b.g;     c.b = a.b + b.b;     c.a = a.a + b.a;     return c; } diff --git a/src/rect_cut.jai b/src/rect_cut.jai new file mode 100644 index 0000000..fa2c8d0 --- /dev/null +++ b/src/rect_cut.jai @@ -0,0 +1,42 @@ /** C R E D I T     Idea from: https://halt.software/p/rectcut-for-dead-simple-ui-layouts     Code from: https://solarium.technology/ */ rcut_from_right  :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T { return rcut_from_right_fixed  (rect, rect.width  * percentage); } rcut_from_left   :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T { return rcut_from_left_fixed   (rect, rect.width  * percentage); } rcut_from_top    :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T { return rcut_from_top_fixed    (rect, rect.height * percentage); } rcut_from_bottom :: inline (using rect: *$T/interface Rectangle, percentage: float) -> T { return rcut_from_bottom_fixed (rect, rect.height * percentage); } rcut_from_right_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {     result := T.{ x + width - n, y, n, height };     width = max(width - n, 0);     return result; } rcut_from_left_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {     result := T.{ x, y, n, height };     x     += n;     width = max(width - n, 0);     return result; } rcut_from_top_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {     result := T.{ x, y, width, n };     y      += n;     height = max(height - n, 0);     return result; } rcut_from_bottom_fixed :: inline (using rect: *$T/interface Rectangle, n: float) -> T {     result := T.{ x, y + height - n, width, n };     height = max(height - n, 0);     return result; } rcut_shrink :: inline (using rect: $T/interface Rectangle, n: float) -> T {     return { x + n, y + n, width - n*2, height - n*2 }; } diff --git a/src/state.jai b/src/state.jai new file mode 100644 index 0000000..9d73a38 --- /dev/null +++ b/src/state.jai @@ -0,0 +1,43 @@ State :: struct {     id: Mode;     camera: Camera2D; } state_init :: (current_mode: Mode, st: ..State) {     for st array_add(*states, it);     current = state_find(current_mode); } state_switch :: (state_id: Mode) {     idx       := state_find(state_id);     new_state := *states[idx];     old_state := *states[current];     old_state.camera = camera;     camera = new_state.camera;     current = idx; } #scope_file states: [..]State; current: int; state_find :: (id: Mode) -> idx: int {     ok, idx := array_find(states, { id, {} }, find_by_id);     assert(ok, "Should not happen @ state_find");     return idx; } find_by_id :: (a: State, b: State) -> bool {     if a.id == b.id return true;     return false; } diff --git a/src/tooltip.jai b/src/tooltip.jai new file mode 100644 index 0000000..af87eff --- /dev/null +++ b/src/tooltip.jai @@ -0,0 +1,268 @@ tooltip :: (mouse: Vector2, text: string) {     update_text_render :: () #expand {         array_reset_keeping_memory(*`codepoints);         array_reset_keeping_memory(*`offsets);         `rect_padded = rect;         `rect_padded.x += PADDING;         `rect_padded.y += PADDING;         `rect_padded.width -= PADDING;         `text_height = wrap_text(             GetFontDefault(),             to_c_string(`text),             *`codepoints,             *`offsets,             `rect_padded,             `FONT_SIZE,             `SPACING,             `MY_COLOR         );     }     BACKGROUND :: Color.{ 34, 31, 45, 242 };     OFFSET     :: Vector2.{ 24.0, 33.0 };     LINES      :: Color.{ 99, 90, 122, 240 };     FONT_SIZE  :: 20.0;     SPACING    :: 4.0;     PADDING    :: 16.0;     MY_COLOR   :: WHITE;     rect: Rectangle;     rect.x = mouse.x + OFFSET.x;     rect.y = mouse.y + OFFSET.y;     rect.width  = #run 14.0 * 34;     rect.height = #run PADDING * 1.5;     codepoints: [..]s32;     offsets: [..]Vector2;     rect_padded: Rectangle;     text_height: float;     update_text_render();     rect.height += text_height;     screen_w := cast(float) GetScreenWidth();     screen_h := cast(float) GetScreenHeight();     if rect.x + rect.width > screen_w {         rect.x = mouse.x - OFFSET.x - rect.width;         update_text_render();     }     if rect.y + rect.height > screen_h {         rect.y = mouse.y - OFFSET.y - rect.height;         update_text_render();     }     DrawRectangleRounded(rect, 0.1, 6, BACKGROUND);     DrawRectangleRoundedLinesEx(rect, 0.1, 6, 1.0, LINES);     draw_text(         GetFontDefault(),         codepoints,         offsets,         FONT_SIZE,         SPACING,         MY_COLOR     ); } tooltip_text :: (index: int, value: int) -> string {     node_type: string;     bv_upper: string;     bv_lower: string;     sa_info: string;     if index == 0 {         node_type = "Root Node";     } else {         if value == {         case NODE_EMPTY;             node_type = "Empty Node";         case NODE_INTERNAL;             node_type = "Internal Node";         case;             node_type = "Child Node";             bv := bvs[value];             bv_upper = tprint("\nBV Upper Bound: %", bv.upper_bound);             bv_lower = tprint("\nBV Lower Bound: %", bv.lower_bound);             sa := aabb_bv_compute_surface_area(bv);             sa_info = tprint("\nSurface Area: %", sa);         }     }     sb: String_Builder;     print_to_builder(*sb, "Type:  %\n", node_type);     print_to_builder(*sb, "Index: %\n", index);     append(*sb, bv_upper);     append(*sb, bv_lower);     append(*sb, sa_info);     text := builder_to_string(*sb);     return text; } #scope_file /** C R E D I T Original proc `wrap_text` is from:     - @Raysan (Original Author), https://github.com/raysan5     - @ahmedqarmout2 (Jai Adoption), https://github.com/ahmedqarmout2 */ wrap_text :: (     font: Font,     text: *u8,     codepoints: *[..]s32,     offsets: *[..]Vector2,     rec: Rectangle,     font_size: float,     spacing: float,     tint: Color,     word_wrap: bool = true )     -> text_height: float {     length: s32 = cast(s32)TextLength(text);     text_offset_y: float = 0.0;     text_offset_x: float = 0.0;     scale_factor: float = font_size / cast(float)font.baseSize;     my_enum :: enum { MEASURE_STATE :: 0; DRAW_STATE :: 1; }     state: my_enum = ifx word_wrap then .MEASURE_STATE else .DRAW_STATE;     start_line: s32 = -1;  // Index where to begin drawing (where a line begins)     end_line: s32 = -1;    // Index where to stop drawing (where a line ends)     lastk: s32 = -1;       // Holds last value of the character position     i: s32 = 0;     k: s32 = 0;     while i < length {         defer i += 1;         defer k += 1;         codepoint_byte_count: s32 = 0;         codepoint: s32 = GetCodepoint(*text[i], *codepoint_byte_count);         index: s32 = GetGlyphIndex(font, codepoint);         if codepoint == 0x3f then codepoint_byte_count = 1;         i += (codepoint_byte_count - 1);         glyph_width: float = 0;         if codepoint != #char "\n" {             glyph_width = ifx (font.glyphs[index].advanceX == 0)                           then font.recs[index].width * scale_factor                           else font.glyphs[index].advanceX * scale_factor;             if (i + 1 < length) glyph_width = glyph_width + spacing;         }         if (state == .MEASURE_STATE) {             if codepoint == #char " "             || codepoint == #char "\t"             || codepoint == #char "\n"             then end_line = i;             if text_offset_x + glyph_width > rec.width {                 end_line = ifx (end_line < 1) then i else end_line;                 if i == end_line then end_line -= codepoint_byte_count;                 if (start_line + codepoint_byte_count) == end_line                 then end_line = (i - codepoint_byte_count);                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }             else if i + 1 == length             {                 end_line = i;                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }             else if codepoint == #char "\n"             {                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }             if state == .DRAW_STATE {                 text_offset_x = 0;                 i = start_line;                 glyph_width = 0;                 tmp: s32 = lastk;                 lastk = k - 1;                 k = tmp;             }         } else {             if codepoint == #char "\n" {                 if !word_wrap {                     text_offset_y += (font.baseSize + font.baseSize / 2) * scale_factor;                     text_offset_x = 0;                 }             } else {                 if !word_wrap && text_offset_x + glyph_width > rec.width {                     text_offset_y += (font.baseSize + font.baseSize / 2) * scale_factor;                     text_offset_x = 0;                 }                 if codepoint != #char " " && codepoint != #char "\t" {                     array_add(codepoints, codepoint);                     array_add(offsets, { rec.x + text_offset_x, rec.y + text_offset_y });                 }             }             if word_wrap && i == end_line {                 text_offset_y += (font.baseSize + font.baseSize / 2) * scale_factor;                 text_offset_x  = 0;                 start_line     = end_line;                 end_line       = -1;                 glyph_width    = 0;                 k              = lastk;                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }         }         if text_offset_x != 0 || codepoint != #char " " then text_offset_x += glyph_width;     }     return text_offset_y; } draw_text :: (     font: Font,     codepoints: [..]s32,     offsets: [..]Vector2,     font_size: float,     spacing: float,     tint: Color ) {     assert(codepoints.count == offsets.count);     for codepoints {         x := offsets[it_index].x;         y := offsets[it_index].y;         DrawTextCodepoint(             font,             it,             { x, y },             font_size,             tint         );     } } diff --git a/src/tree_viz.jai b/src/tree_viz.jai deleted file mode 100644 index 29a543a..0000000 --- a/src/tree_viz.jai +++ /dev/null @@ -1,482 +0,0 @@ Treev_Bounds :: struct {     min, max: Vector2;     fit_zoom: float = 1.0; } treev_init :: (t: [..]int, bounds: *Treev_Bounds) {     tree = array_copy(t);     tree_size = tree.count;     assign_positions(0, xx PADDING, xx (SCREEN_WIDTH - PADDING), 0);     // for i: 0..node_count-1 log_vars(nodes[i]);     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.90) / tree_w;     zoom_y   := (SCREEN_HEIGHT * 0.90) / tree_h;     fit_zoom := ifx zoom_x < zoom_y then zoom_x else zoom_y;     bounds.min = min;     bounds.max = max;     bounds.fit_zoom = fit_zoom; } treev_draw :: () {     mouse := GetMousePosition();     mouse_world := GetScreenToWorld2D(mouse, camera);     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 }             );         }         tn_x := cast(s32, n.x);         tn_y := cast(s32, n.y);         is_internal := n.value == NODE_INTERNAL;         glow := ifx is_internal             then Color.{ 80, 160, 255, 60 }             else Color.{ 80, 255, 160, 60 };         DrawCircle(tn_x, tn_y, NODE_RADIUS + 6.0, glow);         fill := ifx is_internal             then Color.{ 30, 60, 120, 255 }             else Color.{ 30, 110, 70, 255 };         if CheckCollisionPointCircle(mouse_world, { n.x, n.y }, NODE_RADIUS) {             fill += Color.{ 30, 30, 30, 0 };         }         DrawCircle(tn_x, tn_y, xx NODE_RADIUS, fill);         border := ifx is_internal             then Color.{ 100, 180, 255, 255 }             else Color.{ 80, 255, 160, 255 };         DrawCircleLines(tn_x, tn_y, xx NODE_RADIUS, border);         label := ifx is_internal then "N" else tprint("%", n.value);         label_c := to_c_string(label);         fs := cast(s32, FONT1_SIZE);         tw := MeasureTextEx(GetFontDefault(), label_c, FONT1_SIZE, 1);         DrawText(             label_c,             xx (tn_x - tw.x / 2),             xx (tn_y - tw.y / 2),             FONT1_SIZE,             WHITE         );         idx_label := to_c_string(tprint("[%]", n.index));         sw := MeasureTextEx(GetFontDefault(), idx_label, FONT2_SIZE, 1);         DrawText(             idx_label,             xx (tn_x - sw.x / 2),             xx (tn_y - (NODE_RADIUS * 2)),             FONT2_SIZE,             { 180, 180, 180, 180 }         );     } } treev_screen_draw :: () {     info := to_c_string(tprint("Nodes %", node_count));     DrawText(         info,         SCREEN_WIDTH - MeasureText(info, 14) - 16,         SCREEN_HEIGHT - 24,         14,         { 160, 160, 200, 220 }     );     mouse := GetMousePosition();     for i: 0..node_count-1 {         n := nodes[i];         n_screen := GetWorldToScreen2D({ n.x, n.y }, camera);         if CheckCollisionPointCircle(mouse, n_screen, NODE_RADIUS * camera.zoom) {             text := tooltip_text(n);             tooltip(mouse, text);         }     } } #scope_file tree: []int; tree_size: int; nodes: [NODES_MAX]Treev_Node; node_count: int; NODES_MAX   :: 128; NODE_RADIUS :: 28; FONT1_SIZE  :: 34; FONT2_SIZE  :: 16; LEVEL_GAP_Y :: 160; LEVEL_GAP_X :: 2; PADDING     :: 60; Treev_Node :: struct {     index: int;     value: int;     x, y: float;     depth: int; } 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_size || 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_size || tree[idx] == NODE_EMPTY return;     cx := (left + right) / 2.0;     cy := 80.0 + 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;     }     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: Treev_Node) {     for nodes {         if it.index == idx return true, it;     }     return false, {}; } tooltip_text :: (tvn: Treev_Node) -> string {     node_type: string;     bv_upper: string;     bv_lower: string;     sa_info: string;     if tvn.index == 0 {         node_type = "Root Node";     } else {         if tvn.value == {         case NODE_EMPTY;    node_type = "Empty Node";         case NODE_INTERNAL;             node_type = "Internal Node";         case;             node_type = "Child Node";             bv := bvs[tvn.value];             bv_upper = tprint("\nBV Upper Bound: %", bv.upper_bound);             bv_lower = tprint("\nBV Lower Bound: %", bv.lower_bound);             sa := aabb_bv_compute_surface_area(bv);             sa_info = tprint("\nSurface Area: %", sa);         }     }     sb: String_Builder;     print_to_builder(*sb, "Type:  %\n", node_type);     print_to_builder(*sb, "Index: %\n", tvn.index);     append(*sb, bv_upper);     append(*sb, bv_lower);     append(*sb, sa_info);     text := builder_to_string(*sb);     return text; } tooltip :: (mouse: Vector2, text: string) {     update_text_render :: () #expand {         array_reset_keeping_memory(*`codepoints);         array_reset_keeping_memory(*`offsets);         `rect_padded = rect;         `rect_padded.x += PADDING;         `rect_padded.y += PADDING;         `rect_padded.width -= PADDING;         `text_height = wrap_text(             GetFontDefault(),             to_c_string(`text),             *`codepoints,             *`offsets,             `rect_padded,             `FONT_SIZE,             `SPACING,             `MY_COLOR         );     }     BACKGROUND :: Color.{ 34, 31, 45, 242 };     OFFSET     :: Vector2.{ 24.0, 33.0 };     LINES      :: Color.{ 99, 90, 122, 240 };     FONT_SIZE  :: 20.0;     SPACING    :: 4.0;     PADDING    :: 16.0;     MY_COLOR   :: WHITE;     rect: Rectangle;     rect.x = mouse.x + OFFSET.x;     rect.y = mouse.y + OFFSET.y;     rect.width  = #run 14.0 * 34;     rect.height = #run PADDING * 1.5;     codepoints: [..]s32;     offsets: [..]Vector2;     rect_padded: Rectangle;     text_height: float;     update_text_render();     rect.height += text_height;     screen_w := cast(float) GetScreenWidth();     screen_h := cast(float) GetScreenHeight();     if rect.x + rect.width > screen_w         rect.x = mouse.x - OFFSET.x - rect.width;     if rect.y + rect.height > screen_h         rect.y = mouse.y - OFFSET.y - rect.height;     update_text_render();     DrawRectangleRounded(rect, 0.1, 6, BACKGROUND);     DrawRectangleRoundedLinesEx(rect, 0.1, 6, 1.0, LINES);     draw_text(         GetFontDefault(),         codepoints,         offsets,         FONT_SIZE,         SPACING,         MY_COLOR     ); } /** C R E D I T Original proc `wrap_text` is from:     - @Raysan (Original Author), https://github.com/raysan5     - @ahmedqarmout2 (Jai Adoption), https://github.com/ahmedqarmout2 */ wrap_text :: (     font: Font,     text: *u8,     codepoints: *[..]s32,     offsets: *[..]Vector2,     rec: Rectangle,     font_size: float,     spacing: float,     tint: Color,     word_wrap: bool = true )     -> text_height: float {     length: s32 = cast(s32)TextLength(text);     text_offset_y: float = 0.0;     text_offset_x: float = 0.0;     scale_factor: float = font_size / cast(float)font.baseSize;     my_enum :: enum { MEASURE_STATE :: 0; DRAW_STATE :: 1; }     state: my_enum = ifx word_wrap then .MEASURE_STATE else .DRAW_STATE;     start_line: s32 = -1;  // Index where to begin drawing (where a line begins)     end_line: s32 = -1;    // Index where to stop drawing (where a line ends)     lastk: s32 = -1;       // Holds last value of the character position     i: s32 = 0;     k: s32 = 0;     while i < length {         defer i += 1;         defer k += 1;         codepoint_byte_count: s32 = 0;         codepoint: s32 = GetCodepoint(*text[i], *codepoint_byte_count);         index: s32 = GetGlyphIndex(font, codepoint);         if codepoint == 0x3f then codepoint_byte_count = 1;         i += (codepoint_byte_count - 1);         glyph_width: float = 0;         if codepoint != #char "\n" {             glyph_width = ifx (font.glyphs[index].advanceX == 0)                           then font.recs[index].width * scale_factor                           else font.glyphs[index].advanceX * scale_factor;             if (i + 1 < length) glyph_width = glyph_width + spacing;         }         if (state == .MEASURE_STATE) {             if codepoint == #char " "             || codepoint == #char "\t"             || codepoint == #char "\n"             then end_line = i;             if text_offset_x + glyph_width > rec.width {                 end_line = ifx (end_line < 1) then i else end_line;                 if i == end_line then end_line -= codepoint_byte_count;                 if (start_line + codepoint_byte_count) == end_line                 then end_line = (i - codepoint_byte_count);                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }             else if i + 1 == length             {                 end_line = i;                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }             else if codepoint == #char "\n"             {                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }             if state == .DRAW_STATE {                 text_offset_x = 0;                 i = start_line;                 glyph_width = 0;                 tmp: s32 = lastk;                 lastk = k - 1;                 k = tmp;             }         } else {             if codepoint == #char "\n" {                 if !word_wrap {                     text_offset_y += (font.baseSize + font.baseSize / 2) * scale_factor;                     text_offset_x = 0;                 }             } else {                 if !word_wrap && text_offset_x + glyph_width > rec.width {                     text_offset_y += (font.baseSize + font.baseSize / 2) * scale_factor;                     text_offset_x = 0;                 }                 if codepoint != #char " " && codepoint != #char "\t" {                     array_add(codepoints, codepoint);                     array_add(offsets, { rec.x + text_offset_x, rec.y + text_offset_y });                 }             }             if word_wrap && i == end_line {                 text_offset_y += (font.baseSize + font.baseSize / 2) * scale_factor;                 text_offset_x  = 0;                 start_line     = end_line;                 end_line       = -1;                 glyph_width    = 0;                 k              = lastk;                 state = ifx (state == .MEASURE_STATE) then .DRAW_STATE else .MEASURE_STATE;             }         }         if text_offset_x != 0 || codepoint != #char " " then text_offset_x += glyph_width;     }     return text_offset_y; } draw_text :: (     font: Font,     codepoints: [..]s32,     offsets: [..]Vector2,     font_size: float,     spacing: float,     tint: Color ) {     assert(codepoints.count == offsets.count);     for codepoints {         x := offsets[it_index].x;         y := offsets[it_index].y;         DrawTextCodepoint(             font,             it,             { x, y },             font_size,             tint         );     } } operator + :: (a: Color, b: Color) -> Color {     c: Color = ---;     c.r = a.r + b.r;     c.g = a.g + b.g;     c.b = a.b + b.b;     c.a = a.a + b.a;     return c; } diff --git a/src/vtree.jai b/src/vtree.jai new file mode 100644 index 0000000..309b448 --- /dev/null +++ b/src/vtree.jai @@ -0,0 +1,219 @@ vtree_init :: (tree_copy: []int) {     tree = tree_copy;     assign_positions(0, -(SCREEN_WIDTH / 2), SCREEN_WIDTH - (SCREEN_WIDTH / 2), 0); } vtree_fit_zoom :: ($update_camera: bool = true) -> 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 };     #if update_camera then 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 }             );         }         // CLANK         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_internal := n.value == NODE_INTERNAL;         glow := ifx is_internal             then Color.{ 80, 160, 255, 60 }             else Color.{ 80, 255, 160, 60 };         DrawCircle(tn_x, tn_y, node_r + 6.0, glow);         fill := ifx is_internal             then COLOR_NODE_INTERNAL             else COLOR_NODE_VALUE;         if CheckCollisionPointCircle(mouse_world, { n.x, n.y }, node_r) {             fill += Color.{ 30, 30, 30, 0 };         }         DrawCircle(tn_x, tn_y, xx node_r, fill);         border := ifx is_internal             then Color.{ 100, 180, 255, 255 }             else Color.{ 80, 255, 160, 255 };         DrawCircleLines(tn_x, tn_y, xx node_r, border);         label := ifx is_internal then "N" else 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 :: () {     info := to_c_string(tprint("Nodes %", node_count));     DrawText(         info,         SCREEN_WIDTH - MeasureText(info, 18) - 16,         SCREEN_HEIGHT - 24,         18,         { 160, 160, 200, 220 }     );     for i: 0..node_count-1 {         n := nodes[i];         // CLANK         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; NODES_MAX   :: 128; 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; VTree_Node :: struct {     index: int;     value: int;     x, y: float;     depth: int; } 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;     }     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, {}; }