Currently the code recives a rectangle and determines the Vector2
positions of its corners. It will then calculate the new positions of the corners with the Sin
and Cos
relative to the angle of the rectangle. Currently it works only with rectangles.
The question I have is how can the code be updated to that it works with any type of shape/object. I don't need a code, just explaining the algorithm is fine.
foreach (var hit in hit_objects)
{
Collider2D hitCollider = hit.collider;
Bounds bounds = hitCollider.bounds;
Vector2[] corners = new Vector2[4];
corners[0] = new Vector2(bounds.min.x, bounds.min.y);
corners[1] = new Vector2(bounds.max.x, bounds.min.y);
corners[2] = new Vector2(bounds.max.x, bounds.max.y);
corners[3] = new Vector2(bounds.min.x, bounds.max.y);
Vector2 center = bounds.center;
float angle = hit.collider.gameObject.transform.rotation.eulerAngles.z * Mathf.Deg2Rad;
for (int i = 0; i < corners.Length; i++)
{
Vector2 direction = corners[i] - center;
float rotatedX = direction.x * Mathf.Cos(angle) - direction.y * Mathf.Sin(angle);
float rotatedY = direction.x * Mathf.Sin(angle) + direction.y * Mathf.Cos(angle);
corners[i] = new Vector2(rotatedX, rotatedY) + center;
Debug.Log(corners[i] + " " + i);
Vector2 directionToCorner = (corners[i] - (Vector2)transform.position);
Debug.DrawRay(transform.position, directionToCorner, Color.green);
}
}
Overview
The goal is to find tangent lines from a starting object (let’s call it “A”) to an obstacle (target object “B”) in a Unity 2D environment. This is achieved by progressively narrowing down the tangent direction using a bisection method. This approach helps approximate tangent lines by testing and adjusting directions until they align with the edge of the target object.
The code description
In each frame, the script detects if something is in the way.
get_tangents
is called to initiate the tangent calculations for each obstacle detected.
It calculates a perpendicular vector to the line connecting “A” and the “B”'s closest point. This perpendicular vector helps define a starting point for the tangent direction approximation.
The function then performs a bisection
, aiming to get closer to the tangent lines with each iteration.
There are two main points, the position 1 (inside the object “B”), and the position 2 (outside the object “B”). This function uses midpoint of these two positions to test if vector going through “A” to this midpoint intersects the target object, if it does this midpoint becomes the position 1 otherwise it becomes the position 2.
After some iterations, the found tangent is quite accurate.
using System.Collections;
using System.Collections.Generic;
using Unity.Burst.CompilerServices;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
public class tangents : MonoBehaviour
{
public LayerMask obstacleLayerMaskPF;
public int max_number_of_iterations = 100;
private Transform Player;
private List<RaycastHit2D> hit_objects = new List<RaycastHit2D>();
// Start is called before the first frame update
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player").GetComponent<Transform>();
}
// Update is called once per frame
void Update()
{
Vector2 rayStart = transform.position;
Vector2 player_direction = (Player.position - transform.position);
Debug.DrawRay(rayStart, player_direction, Color.red);
// Perform a raycast and get all the hits
RaycastHit2D[] hits = Physics2D.RaycastAll(rayStart, player_direction, player_direction.magnitude, obstacleLayerMaskPF);
hit_objects.Clear();
hit_objects.AddRange(hits);
get_tangents(hit_objects, this.transform.position, 0, player_direction);
}
void get_tangents(List<RaycastHit2D> hit_objects, Vector2 starting_position, int iteration, Vector2 player_direction)
{
int i = 0;
int j = 0;
int k = 0;
Vector2 center;
Vector2 perpendicular_vector;
Vector2[] start_middle_end = new Vector2[3];
Vector2 closest_point_to_target;
Vector2 vector_to_target;
Vector2 perpendicular_vector_to_target;
foreach (var hit in hit_objects)
{
closest_point_to_target = hit.collider.ClosestPoint(transform.position);
vector_to_target = closest_point_to_target - (Vector2)transform.position;
perpendicular_vector_to_target = new Vector2(vector_to_target.y, -vector_to_target.x);
if (i == iteration)
{
center = hit.collider.transform.position;
for (k = 0; k < 2; k++)
{
perpendicular_vector = new Vector2(player_direction.y, -player_direction.x) * (k - 1 + (k * 2));
start_middle_end[0] = center;
start_middle_end[2] = (Vector2)transform.position + perpendicular_vector_to_target.normalized * hit.collider.bounds.extents.magnitude * 2 * (k - 1 + (k * 2));
for (j = 0; j < max_number_of_iterations; j++)
{
bisection(hit.collider.gameObject, perpendicular_vector, center, start_middle_end);
}
Debug.DrawRay(transform.position, (start_middle_end[2] - (Vector2)transform.position) * 5, Color.red);
}
}
i++;
}
}
void bisection(GameObject obstacle, Vector2 perpendicular_vector, Vector2 center, Vector2[] start_middle_end)
{
bool hit_the_obstacle = false;
start_middle_end[1] = (start_middle_end[0] + start_middle_end[2]) / 2;
RaycastHit2D[] hits = Physics2D.RaycastAll(transform.position, start_middle_end[1] - (Vector2)transform.position, (start_middle_end[1] - (Vector2)transform.position).magnitude * 2, obstacleLayerMaskPF);
foreach (var hit in hits)
{
if (hit.collider != null)
{
if (hit.collider.gameObject == obstacle)
{
hit_the_obstacle = true;
}
}
}
if (hit_the_obstacle)
{
start_middle_end[0] = start_middle_end[1];
}
else start_middle_end[2] = start_middle_end[1];
}
}