# Help with smoothly connecting two circles

**URL:** <https://discourse.processing.org/t/help-with-smoothly-connecting-two-circles/46549>\
**Category:** Coding Questions\
**Created:** [June 15, 2025, 4:36am UTC](https://discourse.processing.org/t/help-with-smoothly-connecting-two-circles/46549 "2025-06-15T04:36:47Z")\
**Posts on this page:** 1\
**Showing post:** 7

<div class="post-metadata">

**Author:** ![solub](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.processing.org/solub/32/333_2.png) [@solub](https://discourse.processing.org/u/solub)\
**Post date:** [June 16, 2025, 10:33pm UTC](https://discourse.processing.org/t/help-with-smoothly-connecting-two-circles/46549/7 "2025-06-16T22:33:51Z")

</div>

Hi @arkwl,

A visually similar approach would be to approximate the [closing](https://en.wikipedia.org/wiki/Closing_(morphology)) of the **pair** of circles by _inwardly offsetting its outward offset_. This eliminates the need to extract arc segments and connect them to the input circles.

Please refer to this [post](https://discourse.processing.org/t/help-creating-organic-looking-blobs/8777/11) for a quick overview and to part 3 of this other [post](https://discourse.processing.org/t/creating-patterns-how-can-i-create-a-geometric-shape-that-looks-like-a-rectangle-with-inward-curved-circular-arcs-replacing-the-corners/45111/7) (entitled “metaballs”) for further explanations.

The main challenge in your case is to dynamically adjust the offset value to ensure the closing of the two circles remains valid regardless of their distance.

If this value is too low, the closing cannot be formed (the two parts of the blob remain disconnected), and if it is too high, the closing will extend beyond the circles’ boundaries.

 ![ex2](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/5/0/506cfd134769b5aec871716cc4a865bcb833ffdc.png)

One way of calculating a minimum offset value is to approximate the radius of the arcs that are externally tangent to the two circles (bitangent arcs), with the additional constraint that these arcs _should not intersect each other_.

 ![ex](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/f/7/f7d4dec82920050b7eddb3a63370961875b68ae4.png)

The intuition is that these bitangent arcs naturally define the minimum curvature needed to smoothly connect the circles while maintaining their original shapes. By using this radius as our offset value, we ensure that the closing operation will always produce a valid, organic connection between the circles, regardless of their distance.

To compute a valid minimum radius for the bitangent arc circles, the procedure is as follows:

- find the minimum radius R that makes the arcs tangent to both circles
- use the [Heron’s formula](https://en.wikipedia.org/wiki/Heron%27s_formula) to locate the centers of the circular arcs
- if the arc circles are colliding, iteratively increase R by a small factor until they are properly separated
- compute an approximation of the _closing_ of the pair of circles based on that updated R value using `PGS_Morphology.dilationErosion()`

![gooey](https://canada1.discourse-cdn.com/flex036/uploads/processingfoundation1/original/3X/9/c/9c3adc7353df79f2d323dfd67fa8b52f937f1827.gif)

_Please note that I’ve added a mechanism that linearly increases the arc radius as the distance between the circles decreases. This creates a more organic, gooey feel to the connection - as if the circles were made of a sticky, elastic material that stretches more when they’re far apart._

> **Annotated Script (py5)**
>
> ```auto
> """
> Example script demonstrating how to create an organic connection between two circles
> using morphological operations (dilation and erosion). The offset value is calculated
> by finding two bitangent arcs between the circles. The arcs are guaranteed to be tangent
> to both circles and will not intersect each other. The script handles various cases
> including intersecting circles and provides a mechanism to control the separation
> between the arcs.
>            
> # Author: sol/ub ◐
> # Created: 2025-17-06
> # Python Version: 3.9
> # Context: Reply to @arkwl -> t.ly/5tipm (Processing Discourse Forum) 
>            
> """
> 
> from micycle.pgs import *
> from org.locationtech.jts.operation.buffer import BufferOp, BufferParameters
> import math
> 
> W, H = 1400, 800 # Dimensions of canvas
> 
> R1 = 40 # Radius of the first circle
> R2 = 160 # Radius of the second circle
> C2x, C2y = W//2, H//2 # Coordinates of the second circle
> 
> SW = 2 # Stroke weight
> BG = '#FFF' # Background color
> 
> # Define high-resolution buffer parameters (PGS related)
> NS = 32 # increase for smoother contour
> params = BufferParameters()
> params.setQuadrantSegments(NS)     
> 
> def setup():
> size(W, H, P2D)
> smooth(8)
>     
> global c2
>     
> # Set the second circle at the center of the canvas
> c2 = create_shape(ELLIPSE, C2x, C2y, R2*2, R2*2)
>     
> def draw():
> background(BG)
>     
> # Set the first ellipse at mouse position
> c1 = create_shape(ELLIPSE, mouse_x, mouse_y, R1*2, R1*2)
>             
> # Calculate a minimum tangent arc radius (= offset value for the dilatation/erosion process)
> R = find_min_radius(mouse_x, mouse_y, R1, C2x, C2y, R2)
>     
> # Convert the circles to JTS Geometry
> shapes = PGS_Conversion.flatten(*(c1, c2))
> geom = PGS_Conversion.fromPShape(shapes)
>     
> # Dilate & erode
> dilated = BufferOp.bufferOp(geom, R, params) # First buffer (dilation)
> closed = BufferOp.bufferOp(dilated, -R, params) # Second buffer (erosion)
>     
> # Convert the closing back to PShape
> closing = PGS_Conversion.toPShape(closed)
>     
> # Set colors and stroke weight
> closing.setStroke(color.Colors.BLACK)
> closing.setStrokeWeight(SW)
> closing.setFill(color.ColorUtils.composeColor(235, 238, 240))
> shape(closing)
>         
> 
> def find_min_radius(x1, y1, r1, x2, y2, r2):
>     
> # Calculate distance between circle centers
> dx = x2 - x1
> dy = y2 - y1
> d = math.sqrt(dx*dx + dy*dy)
> theta = math.atan2(dy, dx)
>     
> extra_separation = constrain(remap(d, R2-R1, W*.8, 600, 1), 1, 600) # personalized values to be modified according to your preferences
>     
> # Check if one circle is completely inside the other
> if d <= abs(r1 - r2):
> #raise ValueError("One circle is completely inside the other")
> return 0
>     
> # Check if circles intersect
> if d < r1 + r2:
> # Calculate intersection points
> # Using the formula from: https://mathworld.wolfram.com/Circle-CircleIntersection.html
> d2 = d * d
> r12 = r1 * r1
> r22 = r2 * r2
>         
> # Calculate the distance from circle 1's center to the intersection line
> a = (r12 - r22 + d2) / (2 * d)
>         
> # Calculate the distance from the intersection line to the intersection points
> h = math.sqrt(r12 - a * a)
>         
> # Calculate the intersection points
> x3 = x1 + a * dx / d
> y3 = y1 + a * dy / d
>         
> # Calculate the perpendicular vector
> perp_x = -dy / d
> perp_y = dx / d
>         
> # Calculate the two intersection points
> int1_x = x3 + h * perp_x
> int1_y = x3 + h * perp_y
> int2_x = x3 - h * perp_x
> int2_y = x3 - h * perp_y
>         
> # Calculate the radius of the tangent arcs
> # The radius should be large enough to pass through both intersection points
> # and be tangent to both circles
> R = max(r1, r2) + extra_separation
>         
> # Calculate the centers of the tangent arcs
> # They should be equidistant from both intersection points
> mid_x = (int1_x + int2_x) / 2
> mid_y = (int1_y + int2_y) / 2
>         
> # Calculate the direction vector between intersection points
> dir_x = int2_x - int1_x
> dir_y = int2_y - int1_y
> dir_len = math.sqrt(dir_x * dir_x + dir_y * dir_y)
> dir_x /= dir_len
> dir_y /= dir_len
>         
> # Calculate the perpendicular distance from midpoint to arc centers
> # Using the formula: R^2 = (d/2)^2 + h^2, where d is distance between intersection points
> h = math.sqrt(R * R - (dir_len/2) * (dir_len/2))
>         
> # Calculate the arc centers
> center1_x = mid_x - h * dir_y
> center1_y = mid_y + h * dir_x
> center2_x = mid_x + h * dir_y
> center2_y = mid_y - h * dir_x
>         
> return R
>     
> # If circles don't intersect, use the original method
> # Calculate initial R for tangency
> R = (d*d - (r1 + r2)*(r1 + r2)) / (4 * (r1 + r2))
>     
> # Calculate arc centers
> def calculate_centers(R):
> a, b, c = (r1 + R), (r2 + R), d
> s = (a + b + c) / 2
>         
> # Calculate area using Heron's formula
> try:
> area = math.sqrt(s * (s - a) * (s - b) * (s - c))
> except ValueError:
> # If we get here, the triangle inequality doesn't hold
> # This shouldn't happen with the checks above, but just in case
> raise ValueError("Invalid triangle configuration")
>             
> h = 2 * area / c
>         
> # If h is too large, we need to adjust R
> if h >= a:
> # Calculate maximum possible h for current R
> h = a * 0.99 # Use 99% of a to ensure we're within bounds
>         
> x = math.sqrt(a*a - h*h)
>         
> center1_x = x1 + x * math.cos(theta) - h * math.sin(theta)
> center1_y = y1 + x * math.sin(theta) + h * math.cos(theta)
> center2_x = x1 + x * math.cos(theta) + h * math.sin(theta)
> center2_y = y1 + x * math.sin(theta) - h * math.cos(theta)
>         
> return (center1_x, center1_y), (center2_x, center2_y)
>     
> # Calculate initial positions
> center1, center2 = calculate_centers(R)
>     
> # Check if arcs are too close
> arc_dx = center2[0] - center1[0]
> arc_dy = center2[1] - center1[1]
> arc_d = math.sqrt(arc_dx*arc_dx + arc_dy*arc_dy)
>     
> # If arcs are too close, increase R until they're properly separated
> target_separation = 2 * R + extra_separation
>     
> if arc_d < target_separation:
> max_attempts = 100 # Prevent infinite loops
> attempts = 0
> while arc_d < target_separation and attempts < max_attempts:
> R *= 1.1 # Increase R by 10%
> center1, center2 = calculate_centers(R)
>             
> # Calculate new distance between centers
> arc_dx = center2[0] - center1[0]
> arc_dy = center2[1] - center1[1]
> arc_d = math.sqrt(arc_dx*arc_dx + arc_dy*arc_dy)
> attempts += 1
>     
> return R
> 
> ```

---

_[View the full topic](https://discourse.processing.org/t/help-with-smoothly-connecting-two-circles/46549)._
