Created
May 18, 2025 07:24
-
-
Save Hammer2900/2e00e383b8f5db97a5090713f0b9c7fc to your computer and use it in GitHub Desktop.
self-organizing particle swarm with heading-based interaction in lobster
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std | |
import vec | |
import color | |
import gl | |
// --- System Parameters (Global Constants) --- | |
let SIM_WIDTH = 200.0 | |
let SIM_HEIGHT = 100.0 | |
let PARTICLE_COUNT = 300 | |
let INTERACTION_RADIUS = 5.0 | |
let ALPHA_PARAM = 180.0 // 'alpha' for heading change (degrees) | |
let BETA_PARAM = 17.0 // 'beta' for heading change (degrees) | |
let VELOCITY_PARAM = 0.7 | |
let DT_PARAM = 1.1 // Time step | |
let COLOR_GREEN = float4 { 0.0, 255.0/255.0, 0.0, 1.0 } | |
let COLOR_MAGENTA = float4 { 255.0/255.0, 0.0, 255.0/255.0, 1.0 } | |
let COLOR_BROWN = float4 { 165.0/255.0, 42.0/255.0, 42.0/255.0, 1.0 } | |
let COLOR_BLUE = float4 { 0.0, 0.0, 255.0/255.0, 1.0 } | |
let COLOR_YELLOW = float4 { 255.0/255.0, 255.0/255.0, 0.0, 1.0 } | |
let COLOR_BLACK = color_black | |
let COLOR_WHITE = color_white | |
let FONT_PATH = "/home/izot/.local/share/fonts/InputMono-Regular.ttf" | |
let FONT_SIZE = 20 | |
class Particle: | |
x:float | |
y:float | |
heading_deg:float | |
current_color:float4 = COLOR_GREEN | |
def get_neighbors(all_particles:[Particle]) -> int, int, int: | |
var left_count = 0 | |
var right_count = 0 | |
var total_count = 0 | |
for(all_particles) other_particle: | |
if other_particle != this: | |
var dx = other_particle.x - x | |
var dy = other_particle.y - y | |
// Toroidal wrapping | |
if dx > SIM_WIDTH / 2.0: dx -= SIM_WIDTH | |
elif dx < -SIM_WIDTH / 2.0: dx += SIM_WIDTH | |
if dy > SIM_HEIGHT / 2.0: dy -= SIM_HEIGHT | |
elif dy < -SIM_HEIGHT / 2.0: dy += SIM_HEIGHT | |
let distance_sq = dx * dx + dy * dy | |
if distance_sq <= INTERACTION_RADIUS * INTERACTION_RADIUS: | |
total_count++ | |
let angle_to_other_deg = atan2(float2{dx, dy}) | |
var relative_angle_deg = angle_to_other_deg - heading_deg | |
while relative_angle_deg <= -180.0: relative_angle_deg += 360.0 | |
while relative_angle_deg > 180.0: relative_angle_deg -= 360.0 | |
if relative_angle_deg > 0.0: | |
right_count++ | |
else: | |
left_count++ | |
return left_count, right_count, total_count | |
def update_state(all_particles:[Particle]): | |
let left_count, right_count, total_count = get_neighbors(all_particles) | |
var turn_direction = 0.0 | |
if left_count > right_count: turn_direction = 1.0 | |
elif right_count > left_count: turn_direction = -1.0 | |
let delta_heading_deg = (ALPHA_PARAM * turn_direction + BETA_PARAM * float(total_count)) | |
heading_deg = (heading_deg + delta_heading_deg * DT_PARAM) | |
while heading_deg < 0.0: heading_deg += 360.0 | |
heading_deg = heading_deg % 360.0 | |
x += VELOCITY_PARAM * cos(heading_deg) * DT_PARAM | |
y += VELOCITY_PARAM * sin(heading_deg) * DT_PARAM | |
x = (x % SIM_WIDTH + SIM_WIDTH) % SIM_WIDTH // Ensure positive result for modulo | |
y = (y % SIM_HEIGHT + SIM_HEIGHT) % SIM_HEIGHT | |
if 15 < total_count and total_count <= 35: | |
current_color = COLOR_BLUE | |
elif total_count > 35: | |
current_color = COLOR_YELLOW | |
elif 13 <= total_count and total_count <= 15: | |
current_color = COLOR_BROWN | |
elif total_count > 15: | |
current_color = COLOR_MAGENTA | |
else: | |
current_color = COLOR_GREEN | |
def draw_particle(): | |
gl.color(current_color) | |
gl.translate(float2{x, y}): | |
gl.circle(2.0, 8) | |
def main(): | |
fatal(gl.window("Lobster Primordial Particle System", int(SIM_WIDTH), int(SIM_HEIGHT))) | |
gl.set_target_delta_time(1.0 / 60.0) // Target 60 FPS | |
check(gl.set_font_name(FONT_PATH) and gl.set_font_size(FONT_SIZE), "can\'t load font!") | |
var particles:[Particle] = [] | |
for(PARTICLE_COUNT): | |
particles.push(Particle{x: rnd_float() * SIM_WIDTH, y: rnd_float() * SIM_HEIGHT, heading_deg: rnd_float() * 360.0}) | |
while gl.frame() and gl.button("escape") != 1: | |
for(particles) p: | |
p.update_state(particles) | |
gl.clear(COLOR_BLACK) | |
for(particles) p: | |
p.draw_particle() | |
gl.color(COLOR_WHITE) | |
gl.translate(float2{10, 10}): | |
let fps = if gl.delta_time() > 0.0: 1.0 / gl.delta_time() else: 0.0 | |
gl.text("FPS: " + string(int(fps))) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment