commit e6a2fbe4ebe81476419047e78597d5b0ac075265 Author: george Date: Sat Jun 20 16:40:30 2026 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cb2d7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +libs/ +.build/ diff --git a/build.jai b/build.jai new file mode 100644 index 0000000..ca98353 --- /dev/null +++ b/build.jai @@ -0,0 +1,26 @@ +#run { + w := compiler_create_workspace(); + bo := get_build_options(w); + + bo.output_executable_name = "metaballs"; + args := bo.compile_time_command_line; + for arg : args { + if arg == "-release" { + set_optimization(*bo, .OPTIMIZED); + bo.stack_trace = false; + } + } + + import_paths: [..]string; + for bo.import_path array_add(*import_paths, it); + array_add(*import_paths, "libs"); + bo.import_path = import_paths; + + set_build_options(bo, w); + add_build_file("main.jai", w); + + set_build_options_dc(.{do_output=false}); +} + +#import "Basic"; +#import "Compiler"; diff --git a/main.jai b/main.jai new file mode 100644 index 0000000..07be2ea --- /dev/null +++ b/main.jai @@ -0,0 +1,205 @@ +circle :: struct { + pos: Vector2; + vel: Vector2; + radius: float; + r2: float; +} + +corner :: struct { + pos: Vector2; + height: float; +} + +resolution : s32 = 8; +min_res :: 1; +max_res :: 128; +num_circles :: 10; +show_raw_metaballs := false; +show_circles := false; +show_grid := false; +show_fps := false; +do_lerp := true; +fps_high := true; + +BACKGROUND_COL :Color: .{0x11, 0x11, 0x11, 0x11}; +GRID_COL :Color: .{0x33, 0x33, 0x33, 0x88}; +CIRCLE_COL :Color: .{0xaa, 0xaa, 0xaa, 0xaa}; +METABALL_COL :Color: SKYBLUE; + +main :: () { + InitWindow(800, 600, "Metaballs"); + defer CloseWindow(); + SetTargetFPS(240); + width := GetScreenWidth(); + height := GetScreenHeight(); + + circles : [..]circle; + + for 0..num_circles { + r := cast(float) GetRandomValue(10, 75); + x := cast(float) GetRandomValue(xx (0 + r), xx (width - r)); + y := cast(float) GetRandomValue(xx (0 + r), xx (height - r)); + vx := GetRandomValue(0, 2000) / 10.0; + vy := GetRandomValue(0, 2000) / 10.0; + + h := GetRandomValue(5, height / 2); + w := GetRandomValue(5, height / 2); + r2 := r * r; + c := circle.{.{x, y}, .{vx, vy}, r, r2}; + array_add(*circles, c); + } + + while !WindowShouldClose() { + if IsMouseButtonPressed(.MOUSE_BUTTON_MIDDLE) { + if fps_high SetTargetFPS(30); else SetTargetFPS(240); + fps_high = !fps_high; + } + + if IsKeyPressed(.KEY_R) show_raw_metaballs = !show_raw_metaballs; + if IsKeyPressed(.KEY_C) show_circles = !show_circles; + if IsKeyPressed(.KEY_G) show_grid = !show_grid; + if IsKeyPressed(.KEY_L) do_lerp = !do_lerp; + if IsKeyPressed(.KEY_F) show_fps = !show_fps; + if IsKeyPressed(.KEY_LEFT) && resolution != min_res resolution >>= 1; + if IsKeyPressed(.KEY_RIGHT) && resolution != max_res resolution <<= 1; + + cols := width / resolution + 1; + rows := height / resolution + 1; + corner_count := cols * rows; + corners := NewArray(corner_count, float); + defer array_free(corners); + + dt := GetFrameTime(); + if show_fps DrawFPS(10, 10); + for *c : circles { + using c; + if pos.x + radius > xx width || pos.x - radius < 0 + vel.x = -vel.x; + if pos.y + radius > xx height || pos.y - radius < 0 + vel.y = -vel.y; + + pos += vel * dt; + } + + for x : 0..cols - 1 { + for y : 0..rows - 1 { + corners[x + (y * cols)] = field(.{xx (x * resolution), xx (y * resolution)}, circles); + } + } + + BeginDrawing(); + ClearBackground(BACKGROUND_COL); + + if show_grid { + for col : 0..cols - 1 { + y := col * resolution; + DrawLine(y, 0, y, height, GRID_COL); + } + for row : 0..rows - 1 { + x := row * resolution; + DrawLine(0, x, width, x, GRID_COL); + } + } + + for col : 0..cols - 2 { + x := col * resolution; + for row : 0..rows - 2 { + y := row * resolution; + if show_raw_metaballs && point_in_circles_sum(.{xx x, xx y}, circles) DrawRectangle(x, y, resolution, resolution, WHITE); + if show_raw_metaballs && point_in_circles_max(.{xx x, xx y}, circles) DrawRectangle(x, y, resolution, resolution, GRAY); + fx0 := corners[col + ((row ) * cols)]; + fx1 := corners[col + ((row + 1) * cols)]; + fy0 := corners[col + 1 + ((row ) * cols)]; + fy1 := corners[col + 1 + ((row + 1) * cols)]; + draw_corner_line(.{xx x, xx y}, fx0, fx1, fy0, fy1); + } + } + + if show_circles draw_circles(circles); + + EndDrawing(); + } +} + +draw_circles :: (circles: []circle) { + for c : circles { + using c; + DrawCircleLinesV(pos, radius, CIRCLE_COL); + } +} + +draw_corner_line :: (pos: Vector2, fx0: float, fx1: float, fy0: float, fy1: float){ + corners: s64; + corners |= ifx fx0 >= 1 then 0x1; + corners |= ifx fx1 >= 1 then 0x2; + corners |= ifx fy0 >= 1 then 0x4; + corners |= ifx fy1 >= 1 then 0x8; + + if corners == 0 || corners == 15 return; + + using pos; + x0 := Vector2.{x, y}; + x1 := Vector2.{x, y + resolution}; + y0 := Vector2.{x + resolution, y}; + y1 := Vector2.{x + resolution, y + resolution}; + + calc_intersect :: (a: Vector2, fa: float, b: Vector2, fb: float) -> Vector2 #expand { + t := ifx do_lerp then (1 - fa) / (fb - fa) else 0.5; + return lerp(a, b, t); + } + + left :: () -> Vector2 #expand { return calc_intersect(`x0, `fx0, `x1, `fx1); } + right :: () -> Vector2 #expand { return calc_intersect(`y0, `fy0, `y1, `fy1); } + up :: () -> Vector2 #expand { return calc_intersect(`x0, `fx0, `y0, `fy0); } + down :: () -> Vector2 #expand { return calc_intersect(`x1, `fx1, `y1, `fy1); } + + if corners == { + case 1; DrawLineV(up(), left(), METABALL_COL); + case 2; DrawLineV(left(), down(), METABALL_COL); + case 3; DrawLineV(up(), down(), METABALL_COL); + case 4; DrawLineV(up(), right(), METABALL_COL); + case 5; DrawLineV(left(), right(), METABALL_COL); + case 6; DrawLineV(up(), right(), METABALL_COL); DrawLineV(left(), down(), METABALL_COL); + case 7; DrawLineV(right(), down(), METABALL_COL); + case 8; DrawLineV(right(), down(), METABALL_COL); + case 9; DrawLineV(up(), left(), METABALL_COL); DrawLineV(right(), down(), METABALL_COL); + case 10; DrawLineV(left(), right(), METABALL_COL); + case 11; DrawLineV(up(), right(), METABALL_COL); + case 12; DrawLineV(up(), down(), METABALL_COL); + case 13; DrawLineV(left(), down(), METABALL_COL); + case 14; DrawLineV(up(), left(), METABALL_COL); + } +} + +// heightmap at given point based on circle proximity +field :: (point: Vector2, circles: []circle) -> float { + sum := 0.0; + for c : circles { + using c; + v := r2 / (((point.x - pos.x) * (point.x - pos.x)) + ((point.y - pos.y) * (point.y - pos.y))); + sum += v; + } + return sum; +} + +// used to draw metaballs at given resolution using field calculation +point_in_circles_sum :: (point: Vector2, circles: []circle) -> bool { + f := field(point, circles); + //return f <= 1.05 && f >= 0.95; // Nicer edges! + return f >= 1; +} + +// used to draw circles at given resolution +point_in_circles_max :: (point: Vector2, circles: []circle) -> bool { + for c : circles { + using c; + v := r2 / (((point.x - pos.x) * (point.x - pos.x)) + ((point.y - pos.y) * (point.y - pos.y))); + if v >= 1 return true; + } + return false; +} + +#import "raylib"; + +#import "Basic"; +#import "Math";