Skip to content

Instantly share code, notes, and snippets.

@Hammer2900
Created May 18, 2025 07:24
Show Gist options
  • Save Hammer2900/2e00e383b8f5db97a5090713f0b9c7fc to your computer and use it in GitHub Desktop.
Save Hammer2900/2e00e383b8f5db97a5090713f0b9c7fc to your computer and use it in GitHub Desktop.
self-organizing particle swarm with heading-based interaction in lobster
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