數學篇 求兩條直線的交點,說明過程


CAD調用說明

cad上面調用不用這么復雜,可以見 cad.net 投影三維圖元到某個平面上+求圖元交點

某些情況數學方法處理更佳,例如你要打倒桌子!

簡述

首先要說明,看懂本篇您並不需要高中文化水平...
為了求兩條線的交點,首先要知道什么能求,而目前來說,我只知道高中數學的直線方程,那么我就要引入直線方程的概念...

然后為什么直線方程能求交點呢?因為同時滿足兩條聯立的直線方程,它的共同解也就只能是交點...(這里沒看懂沒關系,跟着代碼走的時候你就知道了.)
而直線方程實際上是描述一條兩端無限延長的線,cad術語就是參照線,構成的兩點只是過兩點,而不是端點.
是參照線的話,這就有一個非常好的條件: 除非平行,否則必然有交點

那么在編程上,我拿到的數據一般是兩個點..(x1,y1)(x2,y2)

通過這一條線坐標計算出斜率,這個斜率實際上就是直角三角形(高/底)

img

//斜率=高/底
var a = (y2 - y1) / (x2 - x1); //需考慮分母不能為0
var b = (y4 - y3) / (x4 - x3); //需考慮分母不能為0

這樣就有個問題了,會存在分母可能為0的情況,需要先判斷一下:

//因為求斜率需要用除法,分母可能為0,所以求斜率之前,
//需要判斷兩條線是否x軸平行或者y軸平行
if (Eq(x2, x1) && Eq(x4, x3))
    throw new Exception("與y軸平行,兩直線垂直,斜率不存在,無交點");
if (Eq(y2, y1) && Eq(y4, y3))
    throw new Exception("與x軸平行,兩直線水平,斜率為零,無交點");

//求斜率,分母為0並不報錯,而是賦值成 Infinity
double a = (y2 - y1) / (x2 - x1); //需考慮分母不能為0 即x2=x1 l1垂直於x軸
double b = (y4 - y3) / (x4 - x3); //需考慮分母不能為0 即x4=x3 l2垂直於x軸
if (Eq(a, b))
    throw new Exception("斜率一致,兩直線斜着平行,無交點");

有了斜率和有兩個點,就可以求直線方程,可以利用"點斜式"來求.
如果你想知道其他的方式,可以看直線方程的五種形式可看樂樂課堂,
用我的話來說,其他形式最后都會成為點斜式,因為它足夠簡單.

直線方程點斜式

y-y1=k(x-x1)

這里k是斜率(因兩條直線:我的斜率是a和b),可以理解公式的x=x2,y=y2,為了使得x2,y2是個可變的點,所以用x,y代替(我的是_x,_y),成為未知數....這里x1,y1就是套入的點.

如下圖,形象理解一下y-y1是豎,x-x1是橫,豎=斜率*橫

變換成:第一種 y=k(x-x1)+y1 這種比較重要! 因為眾所周知的黎曼可積都是豎着切

變換成:第二種 x=(y-y1-k*x1)/k

然后由於未知數有兩個還沒法求,但是現在知道了一個條件: {知道x就可以推出y,知道y就可以推出x}

垂線情況

通過垂線得到_x,求出_y

我之前的代碼否決了兩條線都平行或者都垂直

還有一種情況未否決,這就是其中一條是垂直,它導致了一條線的分母是0,在c#中使用了分母為0的並不報錯,而是double的值成為一個Infinity(正無窮)

如果使用了這個斜率就會報錯,所以我需要避免使用這個它..

又由於這個斜率為0肯定是一條垂線,它的x1==x2是確定的,代表了兩條直線的交點的_x肯定是這個x1.

通過條件{知道x就可以推出y,知道y就可以推出x}套入公式即可求_y

double _x, _y = 0;//未知數初始化

//L1或L2兩直線可能其中一個有Y軸平行(垂直X軸)的
if (Eq(x2, x1)) //L1垂直於x軸  則x=x1=x2,(x2 - x1)是0==斜率a的分母,a=Infinity正無窮
{
    _x = x1;
    _y = b * x1 - b * x3 + y3;//公式變換第一種
    return new double[] { _x, _y };
}
else if (Eq(x4, x3)) //L2垂直於x軸 則x=x3=x4,(x4 - x3)是0==斜率b的分母,b=Infinity正無窮
{
    _x = x3;
    _y = a * _x - a * x1 + y1;//公式變換第一種
    return new double[] { _x, _y };
}

聯立方程

現在剩下一種情況,就是兩條都是斜的.這個時候需要聯立方程.再重復提及一下: 因為直線方程描述是一條參照線,兩端無限延長,除非平行,否則必有交點.

又因為交點是兩條線的共同解,所以點斜式:line1和line2相減必然是0.

{第一條線的直線方程} - {第二條線的直線方程} = 0; //橋接你的思路: 因為交點.Y-交點.Y=0,交點.X-交點.X=0啊!

這樣做的目的,就是算式剩下未知數是_x(其實反過來用X也可以)

套到公式就是這樣:

[y=a(_x-x1)+y1] - [y=b(_x-x3)+y3] =0;
[a*(_x-x1)+y1] - [b*(_x-x3)+y3] =0;
[a*_x-a*x1+y1] - [b*_x-b*x3+y3] =0;
a*_x-a*x1+y1 - b*_x+b*x3-y3 =0;            去括號,括號前是-號,故此+-互變
a*_x-b*_x-a*x1+y1+b*x3-y3=0;               未知數的放一塊
(a - b)* _x = 0 + a*x1 - y1 - b*x3 + y3;   移項,+-互變
_x = (a * x1 - y1 - b * x3 + y3) / (a - b);

//上面程序代碼已經算了_x了,直接套入點斜式方程,通過條件{知道x就可以推出y,知道y就可以推出x}
_y = a * _x - a * x1 + y1;

就這樣,大功告成....

直線方程一般式

在點斜式上面使用了邏輯避讓開垂直和水平的情況,那么一般式里面本身就容納這個邏輯.
可以看看數學家們是怎么利用各種直線方程歸納出一般式的,就明白了為什么"容納"了,這個視頻有.

代碼

// https://blog.csdn.net/yangtrees/article/details/7965983
/// <summary>
/// 求交點,直線方程一般式,平行無解
/// </summary>
/// <returns>交點</returns>
public static PointV GetCrossPoint(PointV p1, PointV p2, PointV p3, PointV p4)
{
    /* 直線方程一般式: Ax+By+C=0,推導:
     * 當x1!=x2,則斜率[(y2-y1)/(x2-x1)],所以有點斜式方程: y-y1=k(x-x1)
     * y-y1     = [(y2-y1)/(x2-x1)]*(x-x1)                   ;高=斜率*底
     * y        = [(y2-y1)/(x2-x1)]*(x-x1)+y1                ;加法交換律,y=斜率*底+y1.
     * (x2-x1)y = {[(y2-y1)/(x2-x1)]*(x-x1)+y1}*(x2-x1)      ;兩邊同乘(x2-x1)
     *          = (y2-y1)/(x2-x1)*(x-x1)*(x2-x1) + y1(x2-x1) ;拆括號
     *          = (y2-y1)*(x-x1) + y1(x2-x1)                 ;約去兩個相同(x2-x1)項
     *          = (y2-y1)x    -(y2-y1)x1  + y1(x2-x1)        ;這一步開始根據一般式的格式,將系數划分出來
     *          |            |-(x1y2-x1y1)+ x2y1-x1y1        ;簡單運算
     *          |            |x2y1-x1y1-(x1y2-x1y1)          ;簡單運算
     *          |            |x2y1-x1y1-x1y2+x1y1            ;簡單運算
     * (x2-x1)y | (y2-y1)x   |x2y1-x1y2                      ;簡單運算
     * -B       | A          |C                              ;格式
     * 保留系數-|------------|-------------------------------
     * x1-x2    |y2-y1       |x2y1-x1y2                      ;寫入到代碼中
     * B        | A          |C                              ;直線方程一般式
     */
    var a1 = p2.Y - p1.Y;
    var b1 = p1.X - p2.X;
    var c1 = p2.X * p1.Y - p1.X * p2.Y;

    var a2 = p4.Y - p3.Y;
    var b2 = p3.X - p4.X;
    var c2 = p4.X * p3.Y - p3.X * p4.Y;

    /*  求交點就是聯立方程:
     *  (A1x+B1y+C1)-(A2x+B2y+C2)=0,二者實際上就是聯立方程組的叉積應用
     *  叉乘:依次用手指蓋住每列,交叉相乘再相減,注意主副順序
     *  x   y   z
     *  a1  b1  c1
     *  a2  b2  c2
     */
    var x = b1 * c2 - b2 * c1;//主-副(左上到右下是主,左下到右上是副)
    var y = a2 * c1 - a1 * c2;//副-主
    var z = a1 * b2 - a2 * b1;//主-副,為0表示兩直線重合
    var cp = new PointV();
    if (Math.Abs(z) > 1e-8)
    {
        cp.X = x / z;
        cp.Y = y / z;
        //cp.Z = z / z;
    }
    //唉唉唉!!!這樣Z不都是1了?直線方程是平面坐標系,因此Z抹去,
    //那么明明叉乘是可以滿足XYZ的推導,恰恰這個時候告訴你Z抹去了,
    //那是不是代表說,直線方程 上面少了參數?然后就可以描述成 空間直線方程?
    //所以就延伸出 空間直線方程一般式: Ax+By+Cz+D=0.
    return cp;
}

編程特有

如果你把這個提示報錯代碼去掉,會出現有意思的東西.
img

測試代碼

本篇代碼參考自

先下載一個PointV類

控制台測試

using JoinBox.BasalMath;
using System.Runtime.InteropServices;
using static JoinBox.BasalMath.Geometrist;

namespace 求交點
{
    public class test
    {
        public static void Print(string str)
        {
            System.Console.WriteLine(str);
        }

        public static void Main(string[] args)
        {
            PointV pt;

            //水平平行
            pt = IntersectWith(PointV.Origin, new PointV(10, 0), new PointV(0, 5), new PointV(10, 5));
            Print(pt.ToString());

            //垂直平行
            pt = IntersectWith(PointV.Origin, new PointV(0, 10), new PointV(5, 0), new PointV(5, 10));
            Print(pt.ToString());

            //一斜一水平
            pt = IntersectWith(PointV.Origin, new PointV(10, 10), new PointV(0, 5), new PointV(10, 5));
            Print(pt.ToString());

            //一斜一垂直
            pt = IntersectWith(PointV.Origin, new PointV(10, 10), new PointV(5, 0), new PointV(5, 10));
            Print(pt.ToString());

            //L1線垂直
            pt = IntersectWith(new PointV(5, 0), new PointV(5, 10), PointV.Origin, new PointV(10, 10));
            Print(pt.ToString());

            //L2線垂直
            pt = IntersectWith(PointV.Origin, new PointV(10, 10), new PointV(5, 0), new PointV(5, 10));
            Print(pt.ToString());

            //兩條都是斜的 交點
            pt = IntersectWith(PointV.Origin, new PointV(10, 10), new PointV(10, 0), new PointV(0, 10));
            Print(pt.ToString());

            //兩條都是斜着 平行
            pt = IntersectWith(PointV.Origin,
                new PointV(1, 1),
                new PointV(0.70710678118655, -0.70710678118655),
                new PointV(1.70710678118655, 0.29289321881345));

            Print(pt.ToString());
        }
    }
}

封裝

using System;

namespace JoinBox.BasalMath
{
    public partial class Geometrist
    {
        /// <summary>
        /// 直線方程求交點
        /// </summary>
        public static PointV IntersectWith(PointV p1, PointV p2, PointV p3, PointV p4)
        {
            var obj = IntersectWith(p1.X, p1.Y, p2.X, p2.Y, p3.X, p3.Y, p4.X, p4.Y);
            return new PointV(obj);
        }

        static bool Eq(double a, double b, double tolerance = 1e-6)
        {
            return Math.Abs(a - b) < tolerance;
        }

        /// <summary>
        /// 直線方程求交點
        /// </summary>
        static double[] IntersectWith(
           double x1, double y1,
           double x2, double y2,
           double x3, double y3,
           double x4, double y4)
        {
            //因為求斜率需要用除法,分母可能為0,所以求斜率之前,
            //需要兩條線是否x軸平行或者y軸平行
            if (Eq(x2, x1) && Eq(x4, x3))
                throw new Exception("與y軸平行,兩直線垂直,斜率不存在,無交點");
            if (Eq(y2, y1) && Eq(y4, y3))
                throw new Exception("與x軸平行,兩直線水平,斜率為零,無交點");

            //求斜率,分母為0並不報錯,而是賦值成 Infinity
            double a = (y2 - y1) / (x2 - x1); //需考慮分母不能為0 即x2=x1 l1垂直於x軸
            double b = (y4 - y3) / (x4 - x3); //需考慮分母不能為0 即x4=x3 l2垂直於x軸
            if (Eq(a, b))
                throw new Exception("斜率一致,兩直線斜着平行,無交點");

            double _x, _y;

            //L1或L2兩直線可能其中一個有Y軸平行(垂直X軸)的
            if (Eq(x2, x1)) //L1垂直於x軸  則x=x1=x2,(x2 - x1)是0==a分母,a=Infinity正無窮
            {
                _x = x1;
                _y = b * x1 - b * x3 + y3;//公式變換第一種
                return new double[] { _x, _y };
            }
            else if (Eq(x4, x3)) //L2垂直於x軸 則x=x3=x4,(x4 - x3)是0==b分母,b=Infinity正無窮
            {
                _x = x3;
                _y = a * _x - a * x1 + y1;//公式變換第一種
                return new double[] { _x, _y };
            }

            //兩條直線都是非垂直狀態

            /* 知道了點和斜率,那么兩條點斜式方程聯立.
               因為直線方程是一條參照線,兩端無限延長,除非平行,否則必有交點.
               又因為交點是兩條線的共同解,所以點斜式:line1和line2的y相減是0,y=k(_x-x1)+y1
               所以未知數y就相減去掉,剩下x,來求y.
               反之,也可以相減去掉x,來求y.
               [y=a(_x-x1)+y1] - [y=b(_x-x3)+y3] =0;
               [a*(_x-x1)+y1] - [b*(_x-x3)+y3] =0;
               [a*_x-a*x1+y1] - [b*_x-b*x3+y3] =0;
               a*_x-a*x1+y1 - b*_x+b*x3-y3 =0; 去括號,+-互變
               (a - b)* _x = 0 + a*x1 - y1 - b*x3 + y3; //移項,+-互變
               _x = (a * x1 - y1 - b * x3 + y3) / (a - b);
            */
            _x = (a * x1 - y1 - b * x3 + y3) / (a - b);

            //但是上面程序代碼已經算了_x了,直接套入點斜式方程,偷懶...也可以通過公式計算
            /* y-y1=k*x-k*x1
               y-y1+k*x1=k*x
               (y-y1+k*x1)/k=x
               [(y-y1-a*x1)/a] - [(y-y3-b*x3)/b] =0; //這是按照公式的方法
            */

            _y = a * _x - a * x1 + y1;  // 點斜式方程 y-y1=k(x-x1)

            return new double[] { _x, _y };
        }
    }
}

CAD測試

namespace JoinBox
{
    public class CmdTest
    {
        [CommandMethod("CmdTest_IntersectWith")]
        public void CmdTest_IntersectWith()
        {
            var doc = Acap.DocumentManager.MdiActiveDocument;
            var ed = doc.Editor;
            var db = doc.Database;
            ed.WriteMessage(Environment.NewLine + "驚驚net測試區:");

            var pts = new List<Point3d>();
            var ppo = new PromptPointOptions("")
            {
                AllowArbitraryInput = true,//任意輸入
                AllowNone           = true //允許回車
            };
            for (int i = 0; i < 4; i++)
            {
                ppo.Message = $"{Environment.NewLine}測試點{i + 1}:";
                var ppr = ed.GetPoint(ppo);//用戶點選
                if (ppr.Status != PromptStatus.OK)
                    return;
                pts.Add(ppr.Value);
            }
            var pt1 = Geometrist.IntersectWith(pts[0], pts[1], pts[2], pts[3]);
            ed.WriteMessage("\n點斜式交點1是:" + pt1.ToString());
            var pt2 = Geometrist.GetCrossPoint(pts[0], pts[1], pts[2], pts[3]);
            ed.WriteMessage("\n一般式交點2是:" + pt2.ToString());
        }
    } 
}

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM