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";