تقاطع قطعتين مستقيمتين

من موسوعة حسوب

تتحقق هذه الخوارزمية من تقاطع قطعتين مستقيمتين (لتكونا (p1, q1) و (p2, q2))، وتعتمد في ذلك على مفهوم التوجيه Orientation.

هناك ثلاثة طرق لتوجيه ثلاث نقاط مرتبة على مستوى معين:

  1. عكس عقارب الساعة
  2. مع عقارب الساعة
  3. على خط واحد colinear

يعرض الشكل التالي طرق التوجيه الثلاثة الواردة في أعلاه للنقاط (a, b, c):

تكون القطعتان المستقيمتان (p1, q1) و (p2, q2) متقاطعتين إذا -وفقط إذا- تحقق أحد الشرطين التاليين:

  1. الحالة العامة:
  • إذا امتلكت النقاط (p1, q1, p2) و (p1, q1, q2) توجيهاً مختلفًا.
  • وإذا امتلكت النقاط (p2, q2, p1) و (p2, q2, q1) توجيهًا مختلفًا.
  1. حالة خاصة:
  • إذا كانت النقاط (p1, q1, p2) و (p1, q1, q2) و (p2, q2, p1) و (p2, q2, q1) على استقامة واحدة.
  • إذا تقاطع الإسقاط على المحور السيني للقطعتين المستقيمتين (p1, q1) و (p2, q2).
  • إذا تقاطع الإسقاط على المحور الصادي للقطعتين المستقيمتين (p1, q1) و (p2, q2).

تنفيذ الخوارزمية

تعرض الأمثلة التالية طريقة تنفيذ الخوارزمية في عدد من لغات البرمجة:

  • C++‎:
#include <iostream> 
using namespace std; 

struct Point 
{ 
	int x; 
	int y; 
}; 

// تتحقق الدالة من أن النقطة المعطاة الأولى تقع على القطعة المستقيمة الناتجة من النقطتين الأخرتين
bool onSegment(Point p, Point q, Point r) 
{ 
	if (q.x <= max(p.x, r.x) && q.x >= min(p.x, r.x) && 
		q.y <= max(p.y, r.y) && q.y >= min(p.y, r.y)) 
	return true; 

	return false; 
} 

// تعيد الدالة القيم التالية بعد تحديد التوجيه الخاص بالنقاط الثلاثة المعطاة
// 0 ---> النقاط الثلاثة على استقامة واحدة
// 1 ---> التوجيه مع عقارب الساعة
// 2 ---> التوجيه عكس عقارب الساعة
int orientation(Point p, Point q, Point r) 
{ 
	int val = (q.y - p.y) * (r.x - q.x) - 
			(q.x - p.x) * (r.y - q.y); 

	if (val == 0) return 0; // على استقامة واحدة

	return (val > 0)? 1: 2; // مع أو عكس عقارب الساعة
} 

// الدالة الرئيسية التي تعيد قيمة صحيحة إذا كانت القطعتان المستقيمتان متقاطعتين

bool doIntersect(Point p1, Point q1, Point p2, Point q2) 
{ 
	// تحديد التوجيهات المطلوبة للحالتين العامة والخاصة
	int o1 = orientation(p1, q1, p2); 
	int o2 = orientation(p1, q1, q2); 
	int o3 = orientation(p2, q2, p1); 
	int o4 = orientation(p2, q2, q1); 

	// الحالة العامة
	if (o1 != o2 && o3 != o4) 
		return true; 

	// الحالات الخاصة
	
	// النقاط p1 و q1 و p2 على استقامة واحدة والنقطة p2 تقع على القطعة المستقيمة p1q1
	if (o1 == 0 && onSegment(p1, p2, q1)) return true; 

	// النقاط p1 و q1 و p2 على استقامة واحدة والنقطة q2 تقع على القطعة المستقيمة p1q1
	if (o2 == 0 && onSegment(p1, q2, q1)) return true; 

	// النقاط p2 و q2 و q1 على استقامة واحدة والنقطة p1 تقع على القطعة المستقيمة p2q2
	if (o3 == 0 && onSegment(p2, p1, q2)) return true; 

	// النقاط p2 و q2 و q1 على استقامة واحدة والنقطة q1 تقع على القطعة المستقيمة p2q2
	if (o4 == 0 && onSegment(p2, q1, q2)) return true; 

	return false; // لا تنتمي النقاط إلى أيٍّ من الحالات السابقة
} 

// اختبار الدوال السابقة 
int main() 
{ 
	struct Point p1 = {1, 1}, q1 = {10, 1}; 
	struct Point p2 = {1, 2}, q2 = {10, 2}; 

	doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n"; 

	p1 = {10, 0}, q1 = {0, 10}; 
	p2 = {0, 0}, q2 = {10, 10}; 
	doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n"; 

	p1 = {-5, -5}, q1 = {0, 0}; 
	p2 = {1, 1}, q2 = {10, 10}; 
	doIntersect(p1, q1, p2, q2)? cout << "Yes\n": cout << "No\n"; 

	return 0; 
}
  • بايثون:
  • جافا:
class GFG 
{ 

static class Point 
{ 
	int x; 
	int y; 

		public Point(int x, int y) 
		{ 
			this.x = x; 
			this.y = y; 
		} 
	
}; 

// تتحقق الدالة من أن النقطة المعطاة الأولى تقع على القطعة المستقيمة الناتجة من النقطتين الأخرتين
static boolean onSegment(Point p, Point q, Point r) 
{ 
	if (q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && 
		q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y)) 
	return true; 

	return false; 
} 

// تعيد الدالة القيم التالية بعد تحديد التوجيه الخاص بالنقاط الثلاثة المعطاة
// 0 ---> النقاط الثلاثة على استقامة واحدة
// 1 ---> التوجيه مع عقارب الساعة
// 2 ---> التوجيه عكس عقارب الساعة
static int orientation(Point p, Point q, Point r) 
{ 
	int val = (q.y - p.y) * (r.x - q.x) - 
			(q.x - p.x) * (r.y - q.y); 

	if (val == 0) return 0; // على استقامة واحدة 

	return (val > 0)? 1: 2; // مع أو عكس عقارب الساعة 
} 

// الدالة الرئيسية التي تعيد قيمة صحيحة إذا كانت القطعتان المستقيمتان متقاطعتين
static boolean doIntersect(Point p1, Point q1, Point p2, Point q2) 
{ 
	// تحديد التوجيهات المطلوبة للحالتين العامة والخاصة 
	int o1 = orientation(p1, q1, p2); 
	int o2 = orientation(p1, q1, q2); 
	int o3 = orientation(p2, q2, p1); 
	int o4 = orientation(p2, q2, q1); 

	// الحالة العامة 
	if (o1 != o2 && o3 != o4) 
		return true; 

	// الحالات الخاصة 
	// النقاط p1 و q1 و p2 على استقامة واحدة والنقطة p2 تقع على القطعة المستقيمة p1q1 
	if (o1 == 0 && onSegment(p1, p2, q1)) return true; 

	// النقاط p1 و q1 و p2 على استقامة واحدة والنقطة q2 تقع على القطعة المستقيمة p1q1 
	if (o2 == 0 && onSegment(p1, q2, q1)) return true; 

	// النقاط p2 و q2 و q1 على استقامة واحدة والنقطة p1 تقع على القطعة المستقيمة p2q2 
	if (o3 == 0 && onSegment(p2, p1, q2)) return true; 

	// النقاط p2 و q2 و q1 على استقامة واحدة والنقطة q1 تقع على القطعة المستقيمة p2q2 
	if (o4 == 0 && onSegment(p2, q1, q2)) return true; 

	return false; // لا تنتمي النقاط إلى أيٍّ من الحالات السابقة
} 

// اختبار الدوال السابقة
public static void main(String[] args) 
{ 
	Point p1 = new Point(1, 1); 
	Point q1 = new Point(10, 1); 
	Point p2 = new Point(1, 2); 
	Point q2 = new Point(10, 2); 

	if(doIntersect(p1, q1, p2, q2)) 
		System.out.println("Yes"); 
	else
		System.out.println("No"); 

	p1 = new Point(10, 1); q1 = new Point(0, 10); 
	p2 = new Point(0, 0); q2 = new Point(10, 10); 
	if(doIntersect(p1, q1, p2, q2)) 
			System.out.println("Yes"); 
	else
		System.out.println("No"); 

	p1 = new Point(-5, -5); q1 = new Point(0, 0); 
	p2 = new Point(1, 1); q2 = new Point(10, 10);; 
	if(doIntersect(p1, q1, p2, q2)) 
		System.out.println("Yes"); 
	else
		System.out.println("No"); 
} 
}

تعطي الشيفرات السابقة المخرجات التالية:

No
Yes
No

مصادر