aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig.zon2
-rw-r--r--src/config.zig24
-rw-r--r--src/imgui.zig8
-rw-r--r--src/main.zig14
-rw-r--r--src/particle.zig86
5 files changed, 89 insertions, 45 deletions
diff --git a/build.zig.zon b/build.zig.zon
index c045dcc..1f22a93 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,6 +1,6 @@
.{
.name = "particle-sim",
- .version = "0.1.2",
+ .version = "0.2.0",
.paths = .{ "src", "vendor" },
.dependencies = .{
.@"raylib-zig" = .{
diff --git a/src/config.zig b/src/config.zig
index 98eab62..36de99a 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -1,16 +1,18 @@
+const std = @import("std");
const rl = @import("raylib");
const part = @import("particle.zig");
-pub const screenWidth = 2880;
-pub const screenHeight = 1620;
-pub const particleMax = 5000;
+pub const screenWidth = 1920;
+pub const screenHeight = 1080;
+pub const particleMax = 10000;
pub const initialParticles = 2000;
pub const colorAmnt = colors.len;
+pub const numThreads = 12;
pub var particleCount: i32 = initialParticles;
pub var radius: f32 = 100.0;
pub var minDistance: f32 = 20.0;
-pub const colors = [_]rl.Color{
+pub var colors = [_]rl.Color{
rl.Color.red,
rl.Color.green,
rl.Color.blue,
@@ -20,4 +22,18 @@ pub const colors = [_]rl.Color{
rl.Color.orange,
rl.Color.gray,
};
+
+pub fn customColors() [8]rl.Color {
+ return .{
+ rl.getColor(0xF38BA8FF),
+ rl.getColor(0xA6E3A1FF),
+ rl.getColor(0x89B4FAFF),
+ rl.getColor(0xF9E2AFFF),
+ rl.getColor(0xF5C2E7FF),
+ rl.getColor(0x94E2D5FF),
+ rl.getColor(0xBAC2DEFF),
+ rl.getColor(0xCBA6F7FF),
+ };
+}
+
pub var rules: [colorAmnt][colorAmnt]f32 = undefined;
diff --git a/src/imgui.zig b/src/imgui.zig
index 163ee0f..0796cdb 100644
--- a/src/imgui.zig
+++ b/src/imgui.zig
@@ -24,8 +24,8 @@ pub fn update(alloc: std.mem.Allocator, buf: [:0]u8) !void {
cfg.minDistance = 20.0;
}
_ = z.sliderInt("Particles", .{ .v = &cfg.particleCount, .min = 1, .max = cfg.particleMax });
- _ = z.sliderFloat("Radius", .{ .v = &cfg.radius, .min = 1, .max = 500 });
- _ = z.sliderFloat("Minimum Distance", .{ .v = &cfg.minDistance, .min = 1.0, .max = 100.0 });
+ _ = z.sliderFloat("Radius", .{ .v = &cfg.radius, .min = cfg.minDistance, .max = 500 });
+ _ = z.sliderFloat("Minimum Distance", .{ .v = &cfg.minDistance, .min = 1.0, .max = cfg.radius });
}
if (z.collapsingHeader("Ruleset", .{ .default_open = true })) {
_ = z.beginTable("Rules", .{
@@ -34,7 +34,6 @@ pub fn update(alloc: std.mem.Allocator, buf: [:0]u8) !void {
.outer_size = .{ 0, 0 },
.inner_width = 0,
});
- defer z.endTable();
_ = z.tableNextRow(.{});
_ = z.tableSetColumnIndex(0);
z.text("Rules", .{});
@@ -58,6 +57,9 @@ pub fn update(alloc: std.mem.Allocator, buf: [:0]u8) !void {
_ = z.popItemWidth();
}
}
+ z.endTable();
+ if (z.button("Randomize", .{}))
+ cfg.rules = rul.ruleMatrix();
}
if (z.collapsingHeader("Load / Save", .{ .default_open = true })) {
_ = z.inputText("Save Path", .{ .buf = buf });
diff --git a/src/main.zig b/src/main.zig
index 6898890..b0705be 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -12,6 +12,7 @@ const c = @cImport({
});
pub fn main() !void {
+ cfg.colors = cfg.customColors();
cfg.rules = rules.ruleMatrix();
rules.printRules(cfg.rules);
@@ -22,6 +23,7 @@ pub fn main() !void {
defer rl.closeWindow();
rl.setTargetFPS(60);
+ rl.setWindowState(rl.ConfigFlags{ .window_resizable = true });
c.rlImGuiSetup(true);
defer c.rlImGuiShutdown();
@@ -39,6 +41,8 @@ pub fn main() !void {
const buf = try gpa.allocator().allocSentinel(u8, 128, 0);
std.mem.copyForwards(u8, buf, "Absolute File Path" ++ .{0});
defer gpa.allocator().free(buf);
+ const pool = try gpa.allocator().alloc(std.Thread, cfg.numThreads);
+ defer gpa.allocator().free(pool);
while (!rl.windowShouldClose()) {
if (particles.items(.x).len < cfg.particleCount) {
@@ -55,9 +59,15 @@ pub fn main() !void {
rl.beginDrawing();
defer rl.endDrawing();
if (rl.isKeyPressed(rl.KeyboardKey.key_q)) break;
- rl.clearBackground(rl.Color.black);
+ rl.clearBackground(rl.getColor(0x1E1E2EFF));
- part.updateVelocities(particles, cfg.rules);
+ for (pool, 0..) |*thread, i|
+ thread.* = try std.Thread.spawn(.{}, part.updateVelocities, .{ &particles, i });
+
+ for (pool) |thread|
+ thread.join();
+
+ // part.updateVelocities(particles, cfg.rules);
part.updatePosition(particles);
part.draw(particles);
try img.update(gpa.allocator(), buf);
diff --git a/src/particle.zig b/src/particle.zig
index 5c97ee5..70261b8 100644
--- a/src/particle.zig
+++ b/src/particle.zig
@@ -2,6 +2,13 @@ const cfg = @import("config.zig");
const std = @import("std");
const rl = @import("raylib");
+pub const particle = struct {
+ colorId: u32,
+ x: i32,
+ y: i32,
+ xvel: f32,
+ yvel: f32,
+};
/// Initialize a MultiArrayList of size amnt with particles created by createParticle
pub fn initParticles(allocator: std.mem.Allocator, amnt: u32) !std.MultiArrayList(particle) {
var particles = std.MultiArrayList(particle){};
@@ -13,38 +20,46 @@ pub fn initParticles(allocator: std.mem.Allocator, amnt: u32) !std.MultiArrayLis
}
/// Applies forces from the ruleset to each particle
-pub fn updateVelocities(particles: std.MultiArrayList(particle), rules: [cfg.colorAmnt][cfg.colorAmnt]f32) void {
+pub fn updateVelocities(
+ particles: *std.MultiArrayList(particle),
+ threadidx: u64,
+) void {
+ const rules = cfg.rules;
const colorList = particles.items(.colorId);
var xvel = particles.items(.xvel);
var yvel = particles.items(.yvel);
- for (particles.items(.x), particles.items(.y), 0..) |x, y, i| {
+ var i: usize = threadidx;
+ while (i <= particles.len) : (i += cfg.numThreads) {
+ const p = particles.get(i);
var forceX: f32 = 0.0;
var forceY: f32 = 0.0;
- for (particles.items(.x), particles.items(.y), 0..) |x2, y2, j| {
+ var j: usize = threadidx;
+ while (j <= particles.len) : (j += 1) {
+ const p2 = particles.get(j);
if (i == j) continue;
- var check2x = x - cfg.screenWidth;
- var check2y = y - cfg.screenWidth;
- if (x < cfg.screenWidth / 2) check2x = x + cfg.screenWidth;
- if (y < cfg.screenHeight / 2) check2y = y + cfg.screenHeight;
+ var check2x = p.x - rl.getScreenWidth();
+ var check2y = p.y - rl.getScreenHeight();
+ if (p.x < @divExact(rl.getScreenWidth(), 2)) check2x = p.x + rl.getScreenWidth();
+ if (p.y < @divExact(rl.getScreenHeight(), 2)) check2y = p.y + rl.getScreenHeight();
- var rx: f32 = @floatFromInt(x - x2);
- var ry: f32 = @floatFromInt(y - y2);
- const check2rx: f32 = @floatFromInt(check2x - x2);
- const check2ry: f32 = @floatFromInt(check2y - y2);
+ var distance_x: f32 = @floatFromInt(p.x - p2.x);
+ var distance_y: f32 = @floatFromInt(p.y - p2.y);
+ const check2rx: f32 = @floatFromInt(check2x - p2.x);
+ const check2ry: f32 = @floatFromInt(check2y - p2.y);
- if (@abs(rx) > @abs(check2rx)) rx = check2rx;
- if (@abs(ry) > @abs(check2ry)) ry = check2ry;
+ if (@abs(distance_x) > @abs(check2rx)) distance_x = check2rx;
+ if (@abs(distance_y) > @abs(check2ry)) distance_y = check2ry;
- if (rx > cfg.radius or ry > cfg.radius) continue;
+ if (distance_x > cfg.radius or distance_y > cfg.radius) continue;
- var r = @sqrt(rx * rx + ry * ry);
+ var distance = @sqrt(distance_x * distance_x + distance_y * distance_y);
- if (r == 0) r = 0.0001;
- if (r > 0 and r < cfg.radius) {
- const f = force(r, rules[colorList[i]][colorList[j]]);
- forceX = forceX + rx / r * f;
- forceY = forceY + ry / r * f;
+ if (distance == 0) distance = 0.0001;
+ if (distance > 0 and distance < cfg.radius) {
+ const f = -force(distance, rules[colorList[i]][colorList[j]]);
+ forceX += (distance_x / distance) * f;
+ forceY += (distance_y / distance) * f;
}
}
@@ -58,11 +73,11 @@ pub fn updateVelocities(particles: std.MultiArrayList(particle), rules: [cfg.col
/// Applies the particles velocity and updates position
pub fn updatePosition(particles: std.MultiArrayList(particle)) void {
- for (particles.items(.y), particles.items(.yvel)) |*y, yvel|
- y.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(y.*)) + yvel)))), cfg.screenHeight);
+ for (particles.items(.y), particles.items(.yvel)) |*y, yvel| // (y + yvel) % screenHeight
+ y.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(y.*)) + yvel)))), rl.getScreenHeight());
- for (particles.items(.x), particles.items(.xvel)) |*x, xvel|
- x.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(x.*)) + xvel)))), cfg.screenWidth);
+ for (particles.items(.x), particles.items(.xvel)) |*x, xvel| // (x + xvel) % screenWidth
+ x.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(x.*)) + xvel)))), rl.getScreenWidth());
}
/// Draw the particles onto the screen using raylib
@@ -71,19 +86,11 @@ pub fn draw(particles: std.MultiArrayList(particle)) void {
rl.drawRectangle(x.*, y.*, 5, 5, cfg.colors[colorId]);
}
-const particle = struct {
- colorId: u32,
- x: i32,
- y: i32,
- xvel: f32,
- yvel: f32,
-};
-
fn force(distance: f32, attraction: f32) f32 {
const beta = cfg.minDistance / cfg.radius;
const r: f32 = distance / cfg.radius;
if (r < beta)
- return -(r / beta - 1.0);
+ return ((beta - r) / (beta - 1.0));
if (beta <= r and r < 1)
return attraction * (1 - @abs(2.0 * r - 1.0 - beta) / (1.0 - beta));
return 0;
@@ -92,8 +99,8 @@ fn force(distance: f32, attraction: f32) f32 {
pub fn createParticle() particle {
const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
var prng = std.rand.DefaultPrng.init(seed);
- const x = prng.random().uintLessThan(u32, cfg.screenWidth);
- const y = prng.random().uintLessThan(u32, cfg.screenHeight);
+ const x = prng.random().uintLessThan(u32, @intCast(rl.getScreenWidth()));
+ const y = prng.random().uintLessThan(u32, @intCast(rl.getScreenHeight()));
const color = prng.random().uintLessThan(u32, cfg.colorAmnt);
return particle{
.colorId = color,
@@ -105,3 +112,12 @@ pub fn createParticle() particle {
}
//TODO: Create tests
+test "Force values" {
+ const expect = std.testing.expect;
+ cfg.radius = 50;
+ cfg.minDistance = 20;
+ const belowMin = force(5.0, 0.5);
+ const aboveMin = force(25.0, 0.5);
+ try expect(aboveMin > 0);
+ try expect(belowMin < 0);
+}