Specimen · flow-field.ptl
Flow Field
400 particles drift along a field of layered sine waves, each trailing a few fading dots of light.
compiling petal…
Source
1// Flow Field — particles drift along a field of layered sine waves.2// Each particle is a soft dot trailing a few fading dots behind it, so the3// motion reads as flowing strands of light. Trails are reset when a particle4// wraps to a new spot, so a dot never streaks a line across the screen.5 6state particles = []7state field_time = 0.08 9let delta = dt()10let sw = float(screen_width())11let sh = float(screen_height())12let PI = pi()13field_time += delta * 0.314 15let num_particles = 40016let field_scale = 0.00817let trail_len = 518 19// Flow field — the angle a particle should travel at a given point + time.20fn field_angle(px, py, t)21 let n = sin(px * field_scale + t) * cos(py * field_scale * 0.7 + t * 0.5)22 n += sin((px + py) * field_scale * 0.5 + t * 0.8) * 0.523 n += cos(px * field_scale * 1.5 - py * field_scale + t * 0.3) * 0.324 n * PI * 2.025end26 27// Hue (0..1) -> warm-to-cool rgb record, riding around the color wheel.28fn hue_color(h)29 let hmod = ((h * 6.0) % 6.0 + 6.0) % 6.030 let cr = 031 let cg = 032 let cb = 033 if hmod < 1.0 then cr = 255; cg = int(hmod * 255.0); cb = 6034 elsif hmod < 2.0 then cr = int((2.0 - hmod) * 255.0); cg = 255; cb = 6035 elsif hmod < 3.0 then cr = 60; cg = 255; cb = int((hmod - 2.0) * 255.0)36 elsif hmod < 4.0 then cr = 60; cg = int((4.0 - hmod) * 255.0); cb = 25537 elsif hmod < 5.0 then cr = int((hmod - 4.0) * 195.0) + 60; cg = 60; cb = 25538 else cr = 255; cg = 60; cb = int((6.0 - hmod) * 195.0) + 6039 end40 {r: cr, g: cg, b: cb}41end42 43// Seed particles on the first frame.44if len(particles) == 0 then45 for i in range(0, num_particles) do46 push(particles, {47 x: random(0.0, sw),48 y: random(0.0, sh),49 speed: random(28.0, 88.0),50 hue: random(0.0, 1.0),51 trail: []52 })53 end54end55 56// Advance every particle one step along the field.57let new_particles = []58for p in particles do59 let angle = field_angle(p.x, p.y, field_time)60 let nx = p.x + cos(angle) * p.speed * delta61 let ny = p.y + sin(angle) * p.speed * delta62 63 // Build the new trail: current position first, then the old trail,64 // capped at trail_len. Wrapping particles start a fresh trail so the65 // jump to a new position is never connected to the old one.66 let new_trail = []67 let wrapped = nx < 0.0 || nx > sw || ny < 0.0 || ny > sh68 if wrapped then69 nx = random(0.0, sw)70 ny = random(0.0, sh)71 else72 push(new_trail, {x: p.x, y: p.y})73 for t in p.trail do74 if len(new_trail) < trail_len then75 push(new_trail, t)76 end77 end78 end79 80 push(new_particles, {81 x: nx, y: ny,82 speed: p.speed,83 hue: p.hue,84 trail: new_trail85 })86end87particles = new_particles88 89// === Drawing ===90clear(8, 8, 16)91 92for p in particles do93 let col = hue_color(p.hue + field_angle(p.x, p.y, field_time) * 0.05)94 95 // Trailing dots: older points are smaller and dimmer.96 for k in range(0, len(p.trail)) do97 let t = p.trail[k]98 let fade = 1.0 - float(k + 1) / float(trail_len + 1)99 draw_circle(int(t.x), int(t.y), 1,100 int(float(col.r) * fade * 0.7),101 int(float(col.g) * fade * 0.7),102 int(float(col.b) * fade * 0.7))103 end104 105 // Head: a soft halo under a bright core.106 draw_circle(int(p.x), int(p.y), 2, col.r / 4, col.g / 4, col.b / 4)107 draw_circle(int(p.x), int(p.y), 1,108 min(255, col.r + 40), min(255, col.g + 40), min(255, col.b + 40))109end110 111draw_text("Flow Field", 16, 12, 14, 120, 120, 150)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.