12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)(* See Emitter.mli *)typerecipe={count:int;speed:float*float;direction:float;spread:float;life:float*float;size:float*float;spin:float;gravity:float;drag:float;}type'aparticle={x:float;y:float;vx:float;vy:float;angle:float;spin:float;size:float;age:float;life:float;gravity:float;drag:float;data:'a;}(* the particles newest first, as a burst adds them; [draws]: how many
* random numbers were drawn so far, the next one's point for Hash *)type'at={seed:int;cap:int;draws:int;newest_first:'aparticlelist}letempty?(cap=400)~(seed:int)():'at={seed;cap;draws=0;newest_first=[]}(* the i-th draw from now, in [0, 1] *)letdraw(t:'at)(i:int):float=Hash.unit~seed:t.seed(t.draws+i)letbetween((lo,hi):float*float)(u:float):float=Tween.lerplohiuletborn(r:recipe)(data:float->'a)(x:float)(y:float)(t:'at)(k:int):'aparticle=(* six draws a particle *)letui=drawt((6*k)+i)inletspeed=betweenr.speed(u0)inleta=(r.direction+.(r.spread*.(u1-.0.5)))*.Float.pi/.180.in{x;y;vx=speed*.cosa;vy=speed*.sina;angle=0.;spin=r.spin*.((2.*.u2)-.1.);size=betweenr.size(u3);age=0.;life=betweenr.life(u4);gravity=r.gravity;drag=r.drag;data=data(u5);}letburst(r:recipe)~(data:float->'a)(x:float)(y:float)(t:'at):'at=letfresh=List.initr.count(bornrdataxyt)inletall=List.rev_appendfresht.newest_firstin{twithdraws=t.draws+(6*r.count);newest_first=List.filteri(funi_->i<t.cap)all}(* semi-implicit Euler: the velocity first, then the position with it *)letmove~(dt:float)(p:'aparticle):'aparticle=letslow=Float.max0.(1.-.(p.drag*.dt))inletvx=p.vx*.slowandvy=(p.vy+.(p.gravity*.dt))*.slowin{pwithvx;vy;x=p.x+.(vx*.dt);y=p.y+.(vy*.dt);angle=p.angle+.(p.spin*.dt);age=p.age+.dt}letstep~(dt:float)(t:'at):'at={twithnewest_first=List.filter(funp->p.age<p.life)(List.map(move~dt)t.newest_first)}letparticles(t:'at):'aparticlelist=List.revt.newest_first