Metaballs animation

This commit is contained in:
senchpimy 2025-09-06 16:11:02 -06:00
parent 5924db58e1
commit 8a42fbd63a
3 changed files with 135 additions and 0 deletions

View File

@ -0,0 +1,129 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Metaballs = @This();
const math = std.math;
const Vec2 = @Vector(2, f32);
const num_metaballs = 5;
const min_radius: f32 = 5.0;
const max_radius: f32 = 12.0;
const max_speed: f32 = 0.2;
const threshold: f32 = 0.7;
const Metaball = struct {
pos: Vec2,
vel: Vec2,
radius: f32,
};
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
balls: [num_metaballs]Metaball,
palette: [5]Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Metaballs {
var self = Metaballs{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.balls = undefined,
.palette = [_]Cell{
Cell.init(' ', 0x2c0000, 0x4f0000),
Cell.init(0x2591, 0x8b0000, 0xae0000),
Cell.init(0x2592, 0xff4500, 0xff6347),
Cell.init(0x2593, 0xffa500, 0xffd700),
Cell.init(0x2588, 0xffff00, 0xffffe0),
},
};
self.initBalls();
return self;
}
fn initBalls(self: *Metaballs) void {
const width_f = @as(f32, @floatFromInt(self.terminal_buffer.width));
const height_f = @as(f32, @floatFromInt(self.terminal_buffer.height));
const rand = self.terminal_buffer.random;
for (&self.balls) |*ball| {
ball.* = .{
.pos = .{
rand.float(f32) * width_f,
rand.float(f32) * height_f,
},
.vel = .{
(rand.float(f32) - 0.5) * 2.0 * max_speed,
(rand.float(f32) - 0.5) * 2.0 * max_speed,
},
.radius = min_radius + (rand.float(f32) * (max_radius - min_radius)),
};
}
}
pub fn animation(self: *Metaballs) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(_: *Metaballs) void {}
fn realloc(self: *Metaballs) anyerror!void {
self.initBalls();
}
fn draw(self: *Metaballs) void {
const width = self.terminal_buffer.width;
const height = self.terminal_buffer.height;
const width_f = @as(f32, @floatFromInt(width));
const height_f = @as(f32, @floatFromInt(height));
for (&self.balls) |*ball| {
ball.pos += ball.vel;
if (ball.pos[0] < 0 or ball.pos[0] > width_f) {
ball.vel[0] *= -1.0;
ball.pos[0] = math.clamp(ball.pos[0], 0, width_f);
}
if (ball.pos[1] < 0 or ball.pos[1] > height_f) {
ball.vel[1] *= -1.0;
ball.pos[1] = math.clamp(ball.pos[1], 0, height_f);
}
}
for (0..height) |y| {
for (0..width) |x| {
const cell_pos = Vec2{
@as(f32, @floatFromInt(x)) + 0.5,
@as(f32, @floatFromInt(y)) + 0.5,
};
var sum_influence: f32 = 0.0;
for (self.balls) |ball| {
const dist_vec = cell_pos - ball.pos;
const dist_sq = (dist_vec[0] * dist_vec[0]) + (dist_vec[1] * dist_vec[1]);
if (dist_sq == 0) {
sum_influence += 1000.0;
} else {
sum_influence += (ball.radius * ball.radius) / dist_sq;
}
}
if (sum_influence > threshold * 1.1) {
self.palette[4].put(x, y);
} else if (sum_influence > threshold * 1.0) {
self.palette[3].put(x, y);
} else if (sum_influence > threshold * 0.9) {
self.palette[2].put(x, y);
} else if (sum_influence > threshold * 0.8) {
self.palette[1].put(x, y);
} else {
self.palette[0].put(x, y);
}
}
}
}

View File

@ -4,6 +4,7 @@ pub const Animation = enum {
matrix,
colormix,
gameoflife,
metaballs
};
pub const DisplayServer = enum {

View File

@ -11,6 +11,7 @@ const interop = @import("interop.zig");
const ColorMix = @import("animations/ColorMix.zig");
const Doom = @import("animations/Doom.zig");
const Dummy = @import("animations/Dummy.zig");
const Metaballs = @import("animations/Metaballs.zig");
const Matrix = @import("animations/Matrix.zig");
const GameOfLife = @import("animations/GameOfLife.zig");
const Animation = @import("tui/Animation.zig");
@ -456,6 +457,10 @@ pub fn main() !void {
var game_of_life = try GameOfLife.init(allocator, &buffer, config.gameoflife_fg, config.gameoflife_entropy_interval, config.gameoflife_frame_delay, config.gameoflife_initial_density);
animation = game_of_life.animation();
},
.metaballs => {
var metaballs = try Metaballs.init(allocator, &buffer);
animation = metaballs.animation();
},
}
defer animation.deinit();