aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig30
-rw-r--r--src/config.zig18
-rw-r--r--src/main.zig160
-rw-r--r--src/particle.zig138
4 files changed, 182 insertions, 164 deletions
diff --git a/build.zig b/build.zig
index e1266e6..98107fc 100644
--- a/build.zig
+++ b/build.zig
@@ -2,7 +2,6 @@ const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
-
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
@@ -12,6 +11,23 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
+ const raylib_dep = b.dependency("raylib-zig", .{
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const raylib = raylib_dep.module("raylib"); // main raylib module
+ const raygui = raylib_dep.module("raygui"); // raygui module
+ const raylib_artifact = raylib_dep.artifact("raylib"); // raylib C library
+
+ raylib_artifact.linkLibC();
+ raylib.link_libc = true;
+ raygui.link_libc = true;
+ exe.linkLibC();
+ exe.linkLibrary(raylib_artifact);
+ exe.root_module.addImport("raylib", raylib);
+ exe.root_module.addImport("raygui", raygui);
+
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
@@ -35,16 +51,4 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
-
- const raylib_dep = b.dependency("raylib-zig", .{
- .target = target,
- .optimize = optimize,
- });
-
- const raylib = raylib_dep.module("raylib"); // main raylib module
- const raygui = raylib_dep.module("raygui"); // raygui module
- const raylib_artifact = raylib_dep.artifact("raylib"); // raylib C library
- exe.linkLibrary(raylib_artifact);
- exe.root_module.addImport("raylib", raylib);
- exe.root_module.addImport("raygui", raygui);
}
diff --git a/src/config.zig b/src/config.zig
new file mode 100644
index 0000000..1f72ad4
--- /dev/null
+++ b/src/config.zig
@@ -0,0 +1,18 @@
+const rl = @import("raylib");
+
+pub const screenWidth = 2560;
+pub const screenHeight = 1440;
+pub const particleMax = 4000;
+pub const initialParticles = 3000;
+pub const radius = 100.0;
+pub const minDistance = 20.0;
+pub const colors = [_]rl.Color{
+ rl.Color.red,
+ rl.Color.green,
+ rl.Color.blue,
+ rl.Color.yellow,
+ rl.Color.magenta,
+ rl.Color.brown,
+ rl.Color.orange,
+};
+pub const colorAmnt = colors.len;
diff --git a/src/main.zig b/src/main.zig
index ec8e87c..47906e2 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,37 +1,13 @@
const std = @import("std");
const rl = @import("raylib");
-
-const particle = struct {
- colorId: u32,
- attrs: particleAttrs,
- x: i32,
- y: i32,
- xvel: f32,
- yvel: f32,
-};
-
-const particleAttrs = struct {};
-const screenWidth = 2560;
-const screenHeight = 1440;
-const particleMax = 5000;
-const radius = 100.0;
-const minDistance = 20.0;
-const colors = [_]rl.Color{
- rl.Color.red,
- rl.Color.green,
- rl.Color.blue,
- rl.Color.yellow,
- rl.Color.magenta,
- rl.Color.brown,
- rl.Color.orange,
-};
-const colorAmnt = colors.len;
+const part = @import("particle.zig");
+const cfg = @import("config.zig");
pub fn main() !void {
- const rules = ruleMatrix(colors.len);
- printRules(rules);
+ const rules = part.ruleMatrix();
+ part.printRules(rules);
- rl.initWindow(screenWidth, screenHeight, "Particle Simulator");
+ rl.initWindow(cfg.screenWidth, cfg.screenHeight, "Particle Simulator");
defer rl.closeWindow();
rl.setTargetFPS(60);
@@ -39,7 +15,7 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
- var particles = try initParticles(gpa.allocator(), 3000);
+ var particles = try part.initParticles(gpa.allocator(), cfg.initialParticles);
defer particles.deinit(gpa.allocator());
while (!rl.windowShouldClose()) {
@@ -48,128 +24,10 @@ pub fn main() !void {
defer rl.endDrawing();
- updateVelocities(particles, rules);
-
- for (particles.items(.y), particles.items(.yvel)) |*y, yvel|
- y.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(y.*)) + yvel)))), screenHeight);
-
- for (particles.items(.x), particles.items(.xvel)) |*x, xvel|
- x.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(x.*)) + xvel)))), screenWidth);
-
- for (particles.items(.y), particles.items(.x), particles.items(.colorId)) |*y, *x, colorId|
- rl.drawRectangle(x.*, y.*, 5, 5, colors[colorId]);
+ part.updateVelocities(particles, rules);
+ part.updatePosition(particles);
+ part.draw(particles);
rl.clearBackground(rl.Color.black);
}
}
-
-fn colorToString(c: usize) []const u8 {
- return switch (c) {
- 0 => "R",
- 1 => "G",
- 2 => "Bl",
- 3 => "Y",
- 4 => "M",
- 5 => "Br",
- 6 => "O",
- else => " ",
- };
-}
-
-fn printRules(rules: [colorAmnt][colorAmnt]f32) void {
- std.debug.print("\n|{s:^6}", .{"Rules"});
- for (0..colors.len) |c|
- std.debug.print("| {s:^4} ", .{colorToString(c)});
-
- std.debug.print("|\n", .{});
- for (rules, 0..) |row, i| {
- std.debug.print("| {s:^4} ", .{colorToString(i)});
- for (row) |col|
- std.debug.print("| {d:^4.1} ", .{col});
-
- std.debug.print("|\n", .{});
- }
-}
-
-fn force(distance: f32, attraction: f32) f32 {
- const beta = minDistance / radius;
- const r: f32 = distance / radius;
- if (r < beta)
- return -(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;
-}
-
-fn updateVelocities(particles: std.MultiArrayList(particle), rules: [colorAmnt][colorAmnt]f32) void {
- 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 forceX: f32 = 0.0;
- var forceY: f32 = 0.0;
-
- for (particles.items(.x), particles.items(.y), 0..) |x2, y2, j| {
- if (i == j) continue;
- const rx: f32 = @floatFromInt(x - x2);
- const ry: f32 = @floatFromInt(y - y2);
- var r = @sqrt(rx * rx + ry * ry);
- if (r == 0) {
- r = 0.0001;
- }
- if (r > 0 and r < radius) {
- const f = force(r, rules[colorList[i]][colorList[j]]);
- forceX = forceX + rx / r * f;
- forceY = forceY + ry / r * f;
- }
- }
-
- forceX = forceX * minDistance / radius;
- forceY = forceY * minDistance / radius;
-
- xvel[i] = xvel[i] * 0.95 + forceX;
- yvel[i] = yvel[i] * 0.95 + forceY;
- }
-}
-
-/// Generates a particle with a random Color and Location
-pub fn createParticle(attrs: particleAttrs) 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, screenWidth);
- const y = prng.random().uintLessThan(u32, screenHeight);
- const color = prng.random().uintLessThan(u32, colorAmnt);
- return particle{
- .colorId = color,
- .attrs = attrs,
- .x = @intCast(x),
- .y = @intCast(y),
- .xvel = 0,
- .yvel = 0,
- };
-}
-
-fn ruleMatrix(comptime size: u32) [size][size]f32 {
- const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
- var prng = std.rand.DefaultPrng.init(seed);
- var rules: [size][size]f32 = undefined;
- for (0..size) |i| {
- for (0..size) |j| {
- var val = prng.random().float(f32);
- const isNeg = prng.random().uintAtMost(u8, 1);
- if (isNeg == 1) val = 0 - val;
- rules[i][j] = val;
- }
- }
- return rules;
-}
-
-/// 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){};
- try particles.setCapacity(allocator, 10000);
- for (0..amnt) |_| {
- try particles.append(allocator, createParticle(.{}));
- }
- return particles;
-}
diff --git a/src/particle.zig b/src/particle.zig
new file mode 100644
index 0000000..cb1a323
--- /dev/null
+++ b/src/particle.zig
@@ -0,0 +1,138 @@
+const cfg = @import("config.zig");
+const std = @import("std");
+const rl = @import("raylib");
+
+/// Generate the set of rules the particles will abide by
+pub fn ruleMatrix() [cfg.colorAmnt][cfg.colorAmnt]f32 {
+ const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
+ var prng = std.rand.DefaultPrng.init(seed);
+ var rules: [cfg.colorAmnt][cfg.colorAmnt]f32 = undefined;
+ for (0..cfg.colorAmnt) |i| {
+ for (0..cfg.colorAmnt) |j| {
+ var val = prng.random().float(f32);
+ const isNeg = prng.random().uintAtMost(u8, 1);
+ if (isNeg == 1) val = 0 - val;
+ rules[i][j] = val;
+ }
+ }
+ return rules;
+}
+
+/// Prints rules generated from ruleMatrix()
+pub fn printRules(rules: [cfg.colorAmnt][cfg.colorAmnt]f32) void {
+ std.debug.print("\n|{s:^6}", .{"Rules"});
+ for (0..cfg.colors.len) |c|
+ std.debug.print("| {s:^4} ", .{colorToString(c)});
+
+ std.debug.print("|\n", .{});
+ for (rules, 0..) |row, i| {
+ std.debug.print("| {s:^4} ", .{colorToString(i)});
+ for (row) |col|
+ std.debug.print("| {d:^4.1} ", .{col});
+
+ std.debug.print("|\n", .{});
+ }
+}
+
+/// 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){};
+ try particles.setCapacity(allocator, cfg.particleMax);
+ for (0..amnt) |_| {
+ try particles.append(allocator, createParticle());
+ }
+ return particles;
+}
+
+/// Applies forces from the ruleset to each particle
+pub fn updateVelocities(particles: std.MultiArrayList(particle), rules: [cfg.colorAmnt][cfg.colorAmnt]f32) void {
+ 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 forceX: f32 = 0.0;
+ var forceY: f32 = 0.0;
+
+ for (particles.items(.x), particles.items(.y), 0..) |x2, y2, j| {
+ if (i == j) continue;
+ const rx: f32 = @floatFromInt(x - x2);
+ const ry: f32 = @floatFromInt(y - y2);
+ var r = @sqrt(rx * rx + ry * ry);
+ 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;
+ }
+ }
+
+ forceX = forceX * cfg.minDistance / cfg.radius;
+ forceY = forceY * cfg.minDistance / cfg.radius;
+
+ xvel[i] = xvel[i] * 0.95 + forceX;
+ yvel[i] = yvel[i] * 0.95 + forceY;
+ }
+}
+
+/// 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(.x), particles.items(.xvel)) |*x, xvel|
+ x.* = @mod(@as(i32, @intFromFloat(@round((@as(f32, @floatFromInt(x.*)) + xvel)))), cfg.screenWidth);
+}
+
+/// Draw the particles onto the screen using raylib
+pub fn draw(particles: std.MultiArrayList(particle)) void {
+ for (particles.items(.y), particles.items(.x), particles.items(.colorId)) |*y, *x, colorId|
+ rl.drawRectangle(x.*, y.*, 5, 5, cfg.colors[colorId]);
+}
+
+const particle = struct {
+ colorId: u32,
+ x: i32,
+ y: i32,
+ xvel: f32,
+ yvel: f32,
+};
+
+fn colorToString(c: usize) []const u8 {
+ return switch (c) {
+ 0 => "R",
+ 1 => "G",
+ 2 => "Bl",
+ 3 => "Y",
+ 4 => "M",
+ 5 => "Br",
+ 6 => "O",
+ else => " ",
+ };
+}
+
+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);
+ if (beta <= r and r < 1)
+ return attraction * (1 - @abs(2.0 * r - 1.0 - beta) / (1.0 - beta));
+ return 0;
+}
+
+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 color = prng.random().uintLessThan(u32, cfg.colorAmnt);
+ return particle{
+ .colorId = color,
+ .x = @intCast(x),
+ .y = @intCast(y),
+ .xvel = 0,
+ .yvel = 0,
+ };
+}