Specimen · flow-field.ptl
Flow Field
500 particles steered by a field of layered sine waves. Never clears — the trails are the drawing.
compiling petal…
Source
1// Nature of Code - Flow Field2// Inspired by Chapter 6: Autonomous Agents - Flow Fields3// Particles follow a vector field defined by layered sine functions4 5state particles = []6state time = 0.07state field_time = 0.08 9let delta = dt()10let sw = float(screen_width())11let sh = float(screen_height())12let PI = pi()13time += delta14field_time += delta * 0.315 16let num_particles = 50017let field_scale = 0.00818 19// Initialize particles20if len(particles) == 0 then21 for i in range(0, num_particles) do22 push(particles, {23 x: random(0.0, sw),24 y: random(0.0, sh),25 prev_x: random(0.0, sw),26 prev_y: random(0.0, sh),27 speed: random(40.0, 100.0),28 hue: random(0.0, 1.0)29 })30 end31end32 33// Flow field function - returns angle at position34fn field_angle(px, py, t)35 let n = sin(px * field_scale + t) * cos(py * field_scale * 0.7 + t * 0.5)36 n += sin((px + py) * field_scale * 0.5 + t * 0.8) * 0.537 n += cos(px * field_scale * 1.5 - py * field_scale + t * 0.3) * 0.338 n * PI * 2.039end40 41// Update particles42let new_particles = []43for p in particles do44 let angle = field_angle(p.x, p.y, field_time)45 46 let vx = cos(angle) * p.speed47 let vy = sin(angle) * p.speed48 49 let nx = p.x + vx * delta50 let ny = p.y + vy * delta51 52 let alive = true53 // Respawn if off screen54 if nx < 0.0 || nx > sw || ny < 0.0 || ny > sh then55 nx = random(0.0, sw)56 ny = random(0.0, sh)57 end58 59 push(new_particles, {60 x: nx, y: ny,61 prev_x: p.x, prev_y: p.y,62 speed: p.speed,63 hue: p.hue64 })65end66particles = new_particles67 68// === Drawing ===69 70// Semi-transparent background for trail effect (draw dark rect with low opacity)71// Since we can't do alpha, we use a very dark rect each frame for fading trails72draw_rect(0, 0, int(sw), int(sh), 5, 5, 12)73 74// Draw particles as short lines (from previous to current position)75for p in particles do76 // Color based on hue + position77 let h = p.hue + field_angle(p.x, p.y, field_time) * 0.0578 let hmod = ((h * 6.0) % 6.0 + 6.0) % 6.079 let cr = 080 let cg = 081 let cb = 082 83 if hmod < 1.0 then cr = 255; cg = int(hmod * 255.0); cb = 5084 elsif hmod < 2.0 then cr = int((2.0 - hmod) * 255.0); cg = 255; cb = 5085 elsif hmod < 3.0 then cr = 50; cg = 255; cb = int((hmod - 2.0) * 255.0)86 elsif hmod < 4.0 then cr = 50; cg = int((4.0 - hmod) * 255.0); cb = 25587 elsif hmod < 5.0 then cr = int((hmod - 4.0) * 200.0) + 50; cg = 50; cb = 25588 else cr = 255; cg = 50; cb = int((6.0 - hmod) * 200.0) + 5089 end90 91 draw_line(int(p.prev_x), int(p.prev_y), int(p.x), int(p.y), cr, cg, cb)92 draw_circle(int(p.x), int(p.y), 1, min(255, cr + 50), min(255, cg + 50), min(255, cb + 50))93end94 95// Title (with background for readability)96draw_rect(5, 5, 200, 50, 0, 0, 0)97draw_text("Flow Field", 10, 10, 16, 100, 100, 120)98draw_text("Particles follow a vector field", 10, 30, 12, 60, 60, 80)The whole program is the source above — there is no hidden runtime. Petal re-runs it every frame: state values persist between frames, draw calls paint to the canvas, and edits take effect live. Open it in the playground to poke at the numbers and watch the picture change.