Skip to content

Instantly share code, notes, and snippets.

@runevision
Last active March 9, 2026 11:55
Show Gist options
  • Select an option

  • Save runevision/970445fa11280def3e8be241fd3dc720 to your computer and use it in GitHub Desktop.

Select an option

Save runevision/970445fa11280def3e8be241fd3dc720 to your computer and use it in GitHub Desktop.
Demo code for calculating the touch point between a "ballooning" circle and a rectangle
using UnityEngine;
[ExecuteAlways]
public class BalloonTouch : MonoBehaviour {
public Transform start;
public Transform other;
void Update() {
Vector2 startPoint = start.position;
Vector2 dir = start.up;
// Animate rect for demonstration
other.position = new Vector3(2f * Mathf.Sin(Time.time * 2.3f), 2.5f + Mathf.Cos(Time.time));
other.rotation = Quaternion.Euler(0f, 0f, Time.time * 90f);
// Calculate score for rect
float score = BalloonScoreRect(startPoint, dir, other, out Vector2 touch);
// Draw debug visualizations
float radius = 0.5f / score;
DrawCircle(startPoint + dir * radius, radius, 48, Color.cyan);
DrawCircle(touch, 0.05f, 12, Color.cyan);
DrawCircle(startPoint, 0.05f, 12, Color.green);
}
static float BalloonScorePoint(Vector2 start, Vector2 dir, Vector2 point) {
Vector2 vec = point - start;
return Vector2.Dot(dir, vec) / vec.sqrMagnitude;
}
static float BalloonScoreLineSegment(Vector2 start, Vector2 dir, Vector2 p1, Vector2 p2, out Vector2 touch) {
Vector2 lineVec = p2 - p1;
Vector2 normal = new Vector2(-lineVec.y, lineVec.x).normalized;
// This is what makes the ballon touch logic work.
// By using a direction which is halfway between the input direction
// and the (reverse) normal of the line, we get an intersection
// which is exactly where the ballon would first touch the line.
Vector2 intersectDir = (dir + normal);
touch = Intersect(start, start + intersectDir, p1, p2);
// Constrain touch point to be inside line segment.
if (Vector2.Dot(touch - p2, p1 - p2) < 0)
touch = p2;
if (Vector2.Dot(touch - p1, p2 - p1) < 0)
touch = p1;
return BalloonScorePoint(start, dir, touch);
}
static float BalloonScoreRect(Vector2 start, Vector2 dir, Transform rect, out Vector2 touch) {
void UseIfBetter(Vector2 p1, Vector2 p2, ref float score, ref Vector2 touch) {
float newScore = BalloonScoreLineSegment(start, dir, p1, p2, out Vector2 newTouch);
if (newScore > score) {
score = newScore;
touch = newTouch;
}
Debug.DrawLine(p1, p2);
}
Vector2 PA = rect.TransformPoint(new Vector2(1.0f, 1.0f));
Vector2 PB = rect.TransformPoint(new Vector2(-1.0f, 1.0f));
Vector2 PC = rect.TransformPoint(new Vector2(-1.0f, -1.0f));
Vector2 PD = rect.TransformPoint(new Vector2(1.0f, -1.0f));
float score = float.NegativeInfinity;
touch = Vector2.zero;
UseIfBetter(PA, PB, ref score, ref touch);
UseIfBetter(PB, PC, ref score, ref touch);
UseIfBetter(PC, PD, ref score, ref touch);
UseIfBetter(PD, PA, ref score, ref touch);
return score;
}
void DrawCircle(Vector2 center, float radius, int segments, Color color) {
Vector2 p1 = center + new Vector2(radius, 0f);
for (int i = 0; i < segments; i++) {
int j = i + 1;
float rad = j * 2f * Mathf.PI / segments;
Vector2 p2 = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad)) * radius + center;
Debug.DrawLine(p1, p2, color);
p1 = p2;
}
}
static Vector2 Intersect(Vector2 line1A, Vector2 line1B, Vector2 line2A, Vector2 line2B) {
// Line 1
float A1 = line1B.y - line1A.y;
float B1 = line1A.x - line1B.x;
float C1 = A1 * line1A.x + B1 * line1A.y;
// Line 2
float A2 = line2B.y - line2A.y;
float B2 = line2A.x - line2B.x;
float C2 = A2 * line2A.x + B2 * line2A.y;
float det = A1 * B2 - A2 * B1;
if (det == 0) {
// Parallel lines
return Vector2.zero;
}
else {
float x = (B2 * C1 - B1 * C2) / det;
float y = (A1 * C2 - A2 * C1) / det;
return new Vector2(x, y);
}
}
}
@runevision
Copy link
Author

This demonstration is in the context of discussing automatic UI control navigation behavior based on a balloon approach. See the full discussion here:
godotengine/godot#103895

And my reply where I show a video of the code above here:
godotengine/godot#103895 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment