const handle_len_rate = 3;
for (var i = 0; i < 30; i++) {
position: createVector(random(width), random(height)),
radius: i==0 ? 120 : random(100, 120),
vel: createVector(random(-2, 2), random(-2, 2))
circlePaths[0].radius = 0;
let c1 = color(247, 159, 121);
let c2 = color(247, 208, 138);
let c3 = color(227, 240, 155);
let gradient = drawingContext.createLinearGradient(100, 200, 500, 400);
gradient.addColorStop(0.0, c1);
gradient.addColorStop(0.5, c2);
gradient.addColorStop(1.0, c3);
drawingContext.fillStyle = gradient;
circlePaths.forEach((circle, index) => {
if(position.x > width) position.x = position.x - width;
else if(position.x < 0) position.x = width - position.x;
if(position.y > height) position.y = position.y - height;
else if(position.y < 0) position.y = height - position.y;
ellipse(position.x,position.y,radius,radius);
for (var i = 0, l = circlePaths.length; i < l; i++) {
for (var j = i - 1; j >= 0; j--) {
var path = metaball(circlePaths[i], circlePaths[j], 0.5, handle_len_rate, maxDistance);
connections.forEach(path => {
for(var j = 0; j < 4; j++){
if(j==0) vertex(path.segments[j].x, path.segments[j].y);
vertex(path.segments[(j+1)%4].x, path.segments[(j+1)%4].y);
path.segments[j].x+path.handles[j].x, path.segments[j].y+path.handles[j].y,
path.segments[(j+1)%4].x+path.handles[(j+1)%4].x, path.segments[(j+1)%4].y+path.handles[(j+1)%4].y,
path.segments[(j+1)%4].x, path.segments[(j+1)%4].y
function metaball(ball1, ball2, v, handle_len_rate, maxDistance) {
var radius1 = ball1.radius/2;
var radius2 = ball2.radius/2;
var center1 = ball1.position;
var center2 = ball2.position;
var d = center1.dist(center2);
if (d > maxDistance || d <= abs(radius1 - radius2)) {
} else if (d < radius1 + radius2) {
u1 = acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d));
u2 = acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d));
var angle1 = atan2(center2.y - center1.y, center2.x - center1.x);
var angle2 = acos((radius1 - radius2) / d);
var angle1a = angle1 + u1 + (angle2 - u1) * v;
var angle1b = angle1 - u1 - (angle2 - u1) * v;
var angle2a = angle1 + PI - u2 - (PI - u2 - angle2) * v;
var angle2b = angle1 - PI + u2 + (PI - u2 - angle2) * v;
var p1a = p5.Vector.add(center1, p5.Vector.fromAngle(angle1a, radius1));
var p1b = p5.Vector.add(center1, p5.Vector.fromAngle(angle1b, radius1));
var p2a = p5.Vector.add(center2, p5.Vector.fromAngle(angle2a, radius2));
var p2b = p5.Vector.add(center2, p5.Vector.fromAngle(angle2b, radius2));
var d2 = min(v * handle_len_rate, dist(p1a.x, p1a.y, p2a.x, p2a.y) / (radius1 + radius2));
d2 *= min(1, d * 2 / (radius1 + radius2));
segments: [p1a, p2a, p2b, p1b],
p5.Vector.fromAngle(angle1a - HALF_PI, radius1),
p5.Vector.fromAngle(angle2a + HALF_PI, radius2),
p5.Vector.fromAngle(angle2b - HALF_PI, radius2),
p5.Vector.fromAngle(angle1b + HALF_PI, radius1)