Files
george e6a2fbe4eb init
2026-06-20 16:40:30 +01:00

206 lines
6.8 KiB
Plaintext

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