求两条直线的交点,最常见的写法是列出两条直线的方程,联立求解。
但这种办法的弊端很大:
1 )算法是坐标系相关的,要考虑直线是水平还是垂直,写出很多判断条件,增加了程序的不稳定性
2)即使两直线都是斜的,只要接近水平或者垂直,也会带来过大的误差,导致计算失败
出现这种问题的核心是联立方程无法做到坐标系无关,总要把求交点的几何问题绑定到坐标系。
从本质上讲,两条直线的交点是几何图形的内在性质,不依赖于坐标系。用矢量变换的方式来求交点,可以避免求解直线方程,也不用判断直线是否水平垂直,能真正做到坐标系无关。类似的算法还可以推广到求直线和圆的交点、圆和圆的交点、直线和椭圆的交点等等。
以下是用delphi编写的用矢量变换的方法求两条直线交点的程序。
该程序不仅能求出交点坐标,还能判断直线是否通过交点,因为有时候交点在延长线上。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
CalInterPoint(x1, y1, x2, y2, x3, y3, x4, y4: Double;
var xj, yj: Double): integer;
calPro(vx1, vy1, vx2, vy2: Double): Double;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
TForm1.CalInterPoint(x1, y1, x2, y2, x3, y3, x4, y4: Double;
var xj, yj: Double): integer;
{-----------------------------------------------------------------------------
函数名: CalInterPoint
作者 : 陈新中
时间 : 2002-8-28
参数 : (x1,y1,x2,y2)是一条直线
(x3,y3,x4,y4)是另一条直线
返回值: >=0 相交 包含1表示交点在12上包含2表示交点在34上
四种组合
0 两条直线都不通过交点
1 直线12通过 ,直线34不通过
2 直线34通过 ,直线12不通过
3 直线12和34都通过
-1 平行
功能 : 该函数计算直线的交点
-----------------------------------------------------------------------------}
var
px12, py12, //直线12的方向矢量
px34, py34, //直线34的方向矢量
t12, t34, //12 和34的长度
nx12, ny12, //直线12的单位矢
nx34, ny34, //直线34的单位矢
px13, py13, //13连线矢量
ty, //13连线矢量在直线12上的投影
tysx, tysy, //13连线矢量在直线12上的投影矢量
czsx, czsy, //3到12的垂线矢量
cossit, //垂线矢跟直线34方向矢的夹角余弦
sux, suy, //3到交点的连线矢量
czL, //垂线长度
gene: //变换因子
Double;
begin
px12 := x2 - x1; //12连线矢量
py12 := y2 - y1;
px34 := x4 - x3; //34连线矢量
py34 := y4 - y3;
//如果矢积为0,则说明平行,置平行标志,返回
if px12 * py34 - px34 * py12 = 0 then
begin
Result := -1;
exit;
end;
if (x3 - x1) * (y3 - y2) + (x3 - x2) * (y3 - y1) = 0 then
begin //如果3正好落在直线12上,交点为 (x3,x4)
xj := x3;
yj := y3;
end
else
begin
t12 := sqrt(px12 * px12 + py12 * py12); //12长度
t34 := sqrt(px34 * px34 + py34 * py34); //34长度
nx12 := px12 / t12; //12单位矢
ny12 := py12 / t12;
nx34 := px34 / t34; //34单位矢
ny34 := py34 / t34;
px13 := x3 - x1; //13连线矢
py13 := y3 - y1;
ty := px13 * nx12 + py13 * ny12; //投影
tysx := ty * nx12; //投影矢量
tysy := ty * ny12;
czsx := tysx - px13; //垂线矢
czsy := tysy - py13;
czL := sqrt(czsx * czsx + czsy * czsy); //垂线长度
gene := czL / calPro(x4 - x3, y4 - y3, czsx, czsy); //变换因子
xj := x3 + px34 * gene;
yj := y3 + py34 * gene;
end;
Result := 0;
if (x1 - xj) * (x2 - xj) + (y1 - yj) * (y2 - yj) < 0 then
Result := Result + 1; //判断是否在直线12上
if (x3 - xj) * (x4 - xj) + (y3 - yj) * (y4 - yj) < 0 then
Result := Result + 2; //判断是否在直线34上
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
var
x1, y1, x2, y2, x3, y3, x4, y4: double;
xo, yo: double;
ret: integer;
info: string;
begin
x1 := 150;
y1 := 150;
x2 := 200;
y2 := 200;
x3 := 200;
y3 := 100;
x4 := 0;
y4 := 150;
Form1.Canvas.MoveTo(round(x1), round(y1));
Form1.Canvas.LineTo(round(x2), round(y2));
Form1.Canvas.MoveTo(round(x3), round(y3));
Form1.Canvas.LineTo(round(x4), round(y4));
ret := CalInterPoint(x1, y1, x2, y2, x3, y3, x4, y4, xo, yo);
Form1.Canvas.Ellipse(round(xo - 10), round(yo - 10), round(xo + 10), round(yo + 10));
info := '';
if (ret and 1) > 0 then
begin
info := info + '交点在直线12上 ';
end
else
begin
info := info + '交点不在直线12上 ';
end;
if (ret and 2) > 0 then
begin
info := info + '交点在直线34上 ';
end
else
begin
info := info + '交点不在直线34上 ';
end;
ShowMessage(info);
end;
TForm1.calPro(vx1, vy1, vx2, vy2: Double): Double;
// 求矢量的投影 矢量v1在v2上的投影 (vx1, vy1)是矢量v1 (vx2, vy2)是矢量v2
begin
result := (vx1 * vx2 + vy1 * vy2) / sqrt(vx2 * vx2 + vy2 * vy2);
end;
end.
但这种办法的弊端很大:
1 )算法是坐标系相关的,要考虑直线是水平还是垂直,写出很多判断条件,增加了程序的不稳定性
2)即使两直线都是斜的,只要接近水平或者垂直,也会带来过大的误差,导致计算失败
出现这种问题的核心是联立方程无法做到坐标系无关,总要把求交点的几何问题绑定到坐标系。
从本质上讲,两条直线的交点是几何图形的内在性质,不依赖于坐标系。用矢量变换的方式来求交点,可以避免求解直线方程,也不用判断直线是否水平垂直,能真正做到坐标系无关。类似的算法还可以推广到求直线和圆的交点、圆和圆的交点、直线和椭圆的交点等等。
以下是用delphi编写的用矢量变换的方法求两条直线交点的程序。
该程序不仅能求出交点坐标,还能判断直线是否通过交点,因为有时候交点在延长线上。
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons;
type
TForm1 = class(TForm)
BitBtn1: TBitBtn;
procedure BitBtn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
CalInterPoint(x1, y1, x2, y2, x3, y3, x4, y4: Double;
var xj, yj: Double): integer;
calPro(vx1, vy1, vx2, vy2: Double): Double;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
TForm1.CalInterPoint(x1, y1, x2, y2, x3, y3, x4, y4: Double;
var xj, yj: Double): integer;
{-----------------------------------------------------------------------------
函数名: CalInterPoint
作者 : 陈新中
时间 : 2002-8-28
参数 : (x1,y1,x2,y2)是一条直线
(x3,y3,x4,y4)是另一条直线
返回值: >=0 相交 包含1表示交点在12上包含2表示交点在34上
四种组合
0 两条直线都不通过交点
1 直线12通过 ,直线34不通过
2 直线34通过 ,直线12不通过
3 直线12和34都通过
-1 平行
功能 : 该函数计算直线的交点
-----------------------------------------------------------------------------}
var
px12, py12, //直线12的方向矢量
px34, py34, //直线34的方向矢量
t12, t34, //12 和34的长度
nx12, ny12, //直线12的单位矢
nx34, ny34, //直线34的单位矢
px13, py13, //13连线矢量
ty, //13连线矢量在直线12上的投影
tysx, tysy, //13连线矢量在直线12上的投影矢量
czsx, czsy, //3到12的垂线矢量
cossit, //垂线矢跟直线34方向矢的夹角余弦
sux, suy, //3到交点的连线矢量
czL, //垂线长度
gene: //变换因子
Double;
begin
px12 := x2 - x1; //12连线矢量
py12 := y2 - y1;
px34 := x4 - x3; //34连线矢量
py34 := y4 - y3;
//如果矢积为0,则说明平行,置平行标志,返回
if px12 * py34 - px34 * py12 = 0 then
begin
Result := -1;
exit;
end;
if (x3 - x1) * (y3 - y2) + (x3 - x2) * (y3 - y1) = 0 then
begin //如果3正好落在直线12上,交点为 (x3,x4)
xj := x3;
yj := y3;
end
else
begin
t12 := sqrt(px12 * px12 + py12 * py12); //12长度
t34 := sqrt(px34 * px34 + py34 * py34); //34长度
nx12 := px12 / t12; //12单位矢
ny12 := py12 / t12;
nx34 := px34 / t34; //34单位矢
ny34 := py34 / t34;
px13 := x3 - x1; //13连线矢
py13 := y3 - y1;
ty := px13 * nx12 + py13 * ny12; //投影
tysx := ty * nx12; //投影矢量
tysy := ty * ny12;
czsx := tysx - px13; //垂线矢
czsy := tysy - py13;
czL := sqrt(czsx * czsx + czsy * czsy); //垂线长度
gene := czL / calPro(x4 - x3, y4 - y3, czsx, czsy); //变换因子
xj := x3 + px34 * gene;
yj := y3 + py34 * gene;
end;
Result := 0;
if (x1 - xj) * (x2 - xj) + (y1 - yj) * (y2 - yj) < 0 then
Result := Result + 1; //判断是否在直线12上
if (x3 - xj) * (x4 - xj) + (y3 - yj) * (y4 - yj) < 0 then
Result := Result + 2; //判断是否在直线34上
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
var
x1, y1, x2, y2, x3, y3, x4, y4: double;
xo, yo: double;
ret: integer;
info: string;
begin
x1 := 150;
y1 := 150;
x2 := 200;
y2 := 200;
x3 := 200;
y3 := 100;
x4 := 0;
y4 := 150;
Form1.Canvas.MoveTo(round(x1), round(y1));
Form1.Canvas.LineTo(round(x2), round(y2));
Form1.Canvas.MoveTo(round(x3), round(y3));
Form1.Canvas.LineTo(round(x4), round(y4));
ret := CalInterPoint(x1, y1, x2, y2, x3, y3, x4, y4, xo, yo);
Form1.Canvas.Ellipse(round(xo - 10), round(yo - 10), round(xo + 10), round(yo + 10));
info := '';
if (ret and 1) > 0 then
begin
info := info + '交点在直线12上 ';
end
else
begin
info := info + '交点不在直线12上 ';
end;
if (ret and 2) > 0 then
begin
info := info + '交点在直线34上 ';
end
else
begin
info := info + '交点不在直线34上 ';
end;
ShowMessage(info);
end;
TForm1.calPro(vx1, vy1, vx2, vy2: Double): Double;
// 求矢量的投影 矢量v1在v2上的投影 (vx1, vy1)是矢量v1 (vx2, vy2)是矢量v2
begin
result := (vx1 * vx2 + vy1 * vy2) / sqrt(vx2 * vx2 + vy2 * vy2);
end;
end.