Phương pháp xác định điểm gần nhất trên đoạn thẳng AB so với điểm P dựa trên nguyên lý hình chiếu vector. Thuật toán phân tích ba trường hợp chính thông qua giá trị chiếu của vector AP lên đoạn AB:
- Nếu giá trị chiếu < 0: Điểm A là vị trí gần nhất
- Nếu giá trị chiếu > độ dài AB: Điểm B là vị trí gần nhất
- Trường hợp còn lại: Điểm chân đường vuông góc từ P xuống AB là điểm cần tìm
Thuật toán xử lý đồng thời cả trường hợp điểm P nằm trên đường thẳng chứa AB và trường hợp không cùng phương. Việc kiểm tra vị trí tương đối được thực hiện thông qua phép chiếu vô hướng mà không cần xét riêng trường hợp thẳng hàng.
// Xác định tọa độ điểm gần nhất trên đoạn thẳng
// positionIndicator: -1=A, 1=B, 0=điểm giữa đoạn
public static Vector2 TimDiemGanNhat(Vector2 start, Vector2 end, Vector2 target, out int positionIndicator)
{
positionIndicator = 0;
Vector2 segmentVector = end - start;
float segmentLengthSq = segmentVector.sqrMagnitude;
if (segmentLengthSq < Mathf.Epsilon)
return start;
Vector2 targetVector = target - start;
float projectionScalar = Vector2.Dot(targetVector, segmentVector) / segmentLengthSq;
if (projectionScalar <= 0)
{
positionIndicator = -1;
return start;
}
if (projectionScalar >= 1)
{
positionIndicator = 1;
return end;
}
return start + segmentVector * projectionScalar;
}
Triển khai tính khoảng cách thực tế bằng cách kết hợp với hàm trên:
// Tính khoảng cách Euclid từ điểm đến đoạn thẳng
public static float TinhKhoangCach(Vector2 A, Vector2 B, Vector2 P, out int viTri)
{
Vector2 closestPoint = TimDiemGanNhat(A, B, P, out viTri);
return Vector2.Distance(P, closestPoint);
}
Phân loại vị trí tương đối của điểm so với đoạn thẳng:
- Giá trị chiếu <0 hoặc >1: Điểm nằm ngoài đoạn (phía A hoặc B)
- Giá trị chiếu =0 hoặc =1: Điểm trùng với đầu mút
- 0 < giá trị chiếu <1: Điểm nằm trong đoạn
// Phân loại vị trí: -1=ngoài đoạn, 0=trùng đầu mút, 1=trong đoạn
// isEndpointA: true nếu thuộc nhóm đầu mút A
public static int XacDinhViTri(Vector2 point, Vector2 A, Vector2 B, out bool isEndpointA)
{
isEndpointA = false;
Vector2 segment = B - A;
Vector2 pointOffset = point - A;
float segmentSq = segment.sqrMagnitude;
float projection = Vector2.Dot(pointOffset, segment) / segmentSq;
if (projection < 0)
{
isEndpointA = true;
return -1;
}
if (projection > 1) return -1;
if (pointOffset.sqrMagnitude < Mathf.Epsilon)
{
isEndpointA = true;
return 0;
}
if ((point - B).sqrMagnitude < Mathf.Epsilon)
return 0;
return 1;
}