UNPKG

paper

Version:

The Swiss Army Knife of Vector Graphics Scripting

133 lines (116 loc) 4.88 kB
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Meta Balls</title> <link rel="stylesheet" href="../css/style.css"> <script type="text/javascript" src="../../dist/paper-full.js"></script> <script type="text/paperscript" canvas="canvas"> // Ported from original Metaball script by SATO Hiroyuki // http://park12.wakwak.com/~shp/lc/et/en_aics_script.html project.currentStyle = { fillColor: 'black' }; var ballPositions = [[255, 129], [610, 73], [486, 363], [117, 459], [484, 726], [843, 306], [789, 615], [1049, 82], [1292, 428], [1117, 733], [1352, 86], [92, 798]]; var handle_len_rate = 2.4; var circlePaths = []; var radius = 50; for (var i = 0, l = ballPositions.length; i < l; i++) { var circlePath = new Path.Circle({ center: ballPositions[i], radius: 50 }); circlePaths.push(circlePath); } var largeCircle = new Path.Circle({ center: [676, 433], radius: 100 }); circlePaths.push(largeCircle); function onMouseMove(event) { largeCircle.position = event.point; generateConnections(circlePaths); } var connections = new Group(); function generateConnections(paths) { // Remove the last connection paths: connections.removeChildren(); for (var i = 0, l = paths.length; i < l; i++) { for (var j = i - 1; j >= 0; j--) { var path = metaball(paths[i], paths[j], 0.5, handle_len_rate, 300); if (path) { connections.appendTop(path); path.removeOnMove(); } } } } generateConnections(circlePaths); // --------------------------------------------- function metaball(ball1, ball2, v, handle_len_rate, maxDistance) { var center1 = ball1.position; var center2 = ball2.position; var radius1 = ball1.bounds.width / 2; var radius2 = ball2.bounds.width / 2; var pi2 = Math.PI / 2; var d = center1.getDistance(center2); var u1, u2; if (radius1 == 0 || radius2 == 0) return; if (d > maxDistance || d <= Math.abs(radius1 - radius2)) { return; } else if (d < radius1 + radius2) { // case circles are overlapping u1 = Math.acos((radius1 * radius1 + d * d - radius2 * radius2) / (2 * radius1 * d)); u2 = Math.acos((radius2 * radius2 + d * d - radius1 * radius1) / (2 * radius2 * d)); } else { u1 = 0; u2 = 0; } var angle1 = (center2 - center1).getAngleInRadians(); var angle2 = Math.acos((radius1 - radius2) / d); var angle1a = angle1 + u1 + (angle2 - u1) * v; var angle1b = angle1 - u1 - (angle2 - u1) * v; var angle2a = angle1 + Math.PI - u2 - (Math.PI - u2 - angle2) * v; var angle2b = angle1 - Math.PI + u2 + (Math.PI - u2 - angle2) * v; var p1a = center1 + getVector(angle1a, radius1); var p1b = center1 + getVector(angle1b, radius1); var p2a = center2 + getVector(angle2a, radius2); var p2b = center2 + getVector(angle2b, radius2); // define handle length by the distance between // both ends of the curve to draw var totalRadius = (radius1 + radius2); var d2 = Math.min(v * handle_len_rate, (p1a - p2a).length / totalRadius); // case circles are overlapping: d2 *= Math.min(1, d * 2 / (radius1 + radius2)); radius1 *= d2; radius2 *= d2; var path = new Path({ segments: [p1a, p2a, p2b, p1b], style: ball1.style, closed: true }); var segments = path.segments; segments[0].handleOut = getVector(angle1a - pi2, radius1); segments[1].handleIn = getVector(angle2a + pi2, radius2); segments[2].handleOut = getVector(angle2b - pi2, radius2); segments[3].handleIn = getVector(angle1b + pi2, radius1); return path; } // ------------------------------------------------ function getVector(radians, length) { return new Point({ // Convert radians to degrees: angle: radians * 180 / Math.PI, length: length }); } </script> </head> <body> <canvas id="canvas" resize></canvas> </body> </html>