function drawMetaball(data, sampleRate=50, intersections){
let metaball='';
if (data.length===1)
{
metaball+=`
M ${data[0]['x']-data[0]['r']},${data[0]['y']}
a ${data[0]['r']},${data[0]['r']} 0 1, 0 ${data[0]['r']*2},0
a ${data[0]['r']},${data[0]['r']} 0 1, 0 ${-data[0]['r']*2},0
Z`;
metaball=metaball.replace(/\n/g,'').replace(/\s\s/g,'');
return metaball;
}
else if (data.length===2)
{
data.forEach(d=>d.c=[d.x,d.y]);
const pointsAndHandles1=curvesBetweenCircles(data[0].r, data[1].r, data[0].c, data[1].c);
const pointsAndHandles2=curvesBetweenCircles(data[1].r, data[0].r, data[1].c, data[0].c);
metaball+=`M ${pointsAndHandles1.p[1][0]},${pointsAndHandles1.p[1][1]} `;
metaball+=`
C ${pointsAndHandles1.h[1][0]},${pointsAndHandles1.h[1][1]}
${pointsAndHandles1.h[3][0]},${pointsAndHandles1.h[3][1]}
${pointsAndHandles1.p[3][0]},${pointsAndHandles1.p[3][1]} `;
metaball+=`
A ${pointsAndHandles1.r},${pointsAndHandles1.r}
0 1,1
${pointsAndHandles2.p[1][0]},${pointsAndHandles2.p[1][1]} `;
// 2ndt Bezier Curve
metaball+=`
C ${pointsAndHandles2.h[1][0]},${pointsAndHandles2.h[1][1]}
${pointsAndHandles2.h[3][0]},${pointsAndHandles2.h[3][1]}
${pointsAndHandles2.p[3][0]},${pointsAndHandles2.p[3][1]} `;
// 2nd arc
metaball+=`
A ${pointsAndHandles2.r},${pointsAndHandles2.r}
0 1,1
${pointsAndHandles1.p[1][0]},${pointsAndHandles1.p[1][1]} `;
metaball=metaball.replace(/\n/g,'').replace(/\s\s/g,'');
return metaball;
}
else
{
// Add "c" property (center) to all data elements
data.forEach(d=>d.c=[d.x,d.y]);
// Init subsetData
let subsetData = data;
// Can't do convex hull with less than 3 points
if (data.length>2)
{
// Calculate convex hull
const convex_hull = d3.polygonHull(data.map(d=>([d.x,d.y])));
// Subset the data according to convex hull
subsetData = convex_hull.reverse().map(d=>{
const elm=data.find(dd=>dd.x===d[0]&&dd.y===d[1]);
return elm;
});
}
// Save segments
metaball=metaballSegments(subsetData);
intersections.selectAll('*').remove();
// Do a second iteration:
// check for intersections with circles not part of subsetData
const check_intersections = data.filter(d=>subsetData.indexOf(d)<0);
for (let i=0; i<check_intersections.length; i++){
// Look for intersections
const this_circle = check_intersections[i];
var pathToSample = document.createElementNS('http://www.w3.org/2000/svg','path');
pathToSample.setAttribute('d',metaball);
const length = pathToSample.getTotalLength();
const rate = sampleRate;
const unit = length/rate;
const sampledPoints = [];
for (let j=0; j<rate; j++) {
sampledPoints.push(pathToSample.getPointAtLength(unit*j))
}
// const polygon = ShapeInfo.polygon(sampledPoints);
const path = ShapeInfo.path(metaball); // looks like it has a bug
const circle = ShapeInfo.circle({center: {x:this_circle.x, y:this_circle.y}, radius:this_circle.r});
const intersections_data = Intersection.intersect(path, circle);
console.log(intersections_data)
intersections_data.points.forEach(d=>{
intersections.append('circle').attr('r',2).attr('cx',d.x).attr('cy',d.y)
})
// d3.select
// If true
if (intersections_data.status==="Intersection") {
// Identify the two adjacent circles
const adjacent_circles = intersections_data.points.map(d=>{
const subset_with_distances = subsetData.map( (dd,i)=>{
const a = d.x - dd.x;
const b = d.y - dd.y;
dd.distance = Math.sqrt( a*a + b*b );
dd.position=i;
return dd;
}).sort((a,b)=>a.distance-b.distance);
return subset_with_distances[0];
})
// Insert the new circle between its two adjacent mates
if ( (adjacent_circles[0].position===0&&adjacent_circles[1].position===subsetData.length-1) || (adjacent_circles[1].position===0&&adjacent_circles[0].position===subsetData.length-1) ) {
subsetData.push(this_circle);
}
else
{
const append_after = adjacent_circles.sort((a,b)=>a.position-b.position)[0].position;
subsetData.splice(append_after+1, 0, this_circle);
}
//metaball=metaballSegments(subsetData);
}
}
}
return metaball;
}