1. 程式人生 > >【C# / Algorithm】任意圓形和三角形相交部分面積的計算方法

【C# / Algorithm】任意圓形和三角形相交部分面積的計算方法

    採用運算子過載技術,設計一個名為"&"的運算子,使用該運算子可以計算出一個圓與一個三角形的公共部分的面積,並給出示例程式碼。已知資料:圓的圓心座標和半徑,三角形的三個頂點座標。

今天無意中在貼吧發現了這樣一道題,很顯然難點在後半題。

於是開始考慮圓形和三角形的相交面積計算的通用方法。

我的思路:

三角形和圓形相交的情況比較多,按交點數就有0到6個,每種情況還有很多不同的細分情況要考慮。
將圓形所在的正方形,無限細分成NxN的小矩形(畫素點?),然後一次判斷每個矩形是否即在圓形內又在三角形內,則該矩形為相交部分。
統計所有的矩形,則可以求出相交部分的近似面積。
如果分割的足夠細,則結果精度會比較高。


測試Demo截圖:


以下是演算法核心程式碼(不包含winform介面程式碼)

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace CrossArea
{
    /// <summary>
    /// 面積計算
    /// </summary>
    public static class Area
    {
        /// <summary>
        /// 計算交叉部分面積
        /// </summary>
        /// <param name="p">圓心座標</param>
        /// <param name="r">圓半徑</param>
        /// <param name="a">三角形A點座標</param>
        /// <param name="b">三角形B點座標</param>
        /// <param name="c">三角形C點座標</param>
        /// <param name="accuracy">計算精度</param>
        /// <returns></returns>
        public static double CrossArea(PointF p, float r, PointF a, PointF b, PointF c, float accuracy)
        {
            // 計算縮放精度
            p = new PointF(p.X * accuracy, p.Y * accuracy);
            r = r * accuracy;
            a = new PointF(a.X * accuracy, a.Y * accuracy);
            b = new PointF(b.X * accuracy, b.Y * accuracy);
            c = new PointF(c.X * accuracy, c.Y * accuracy);
            // 掃描起點
            PointF p_start = new PointF(p.X - r, p.Y - r);
            // 掃描終點
            PointF p_end = new PointF(p.X + r, p.Y + r);
            // 掃描統計
            double count = 0;
            // 掃描圓形所在的正方形內所有的點
            for (int i = (int)p_start.X; i <= (int)p_end.X; i++)
            {
                for (int j = (int)p_start.Y; j <= (int)p_end.Y; j++)
                {
                    Point pt = new Point(i,j);
                    // 如果掃描當前點在圓形和三角形內
                    if (IsInCircle(p, r, pt) && IsInTriangle(a, b, c, pt))
                    {
                        count++;
                    }
                }
            }
            // 計算結果
            double answer = count / (accuracy * accuracy);
            return answer;
        }

        /// <summary>
        /// 計算點到點的距離
        /// </summary>
        /// <param name="point1">點1</param>
        /// <param name="point2">點2</param>
        /// <returns>距離</returns>
        public static double LengthFromPointToPoint(PointF point1, PointF point2)
        {
            return Math.Sqrt(Math.Pow(point1.X - point2.X, 2) + Math.Pow(point1.Y - point2.Y, 2));
        }          

        /// <summary>
        /// 點T是否在圓P內
        /// </summary>
        /// <param name="p">圓心座標</param>
        /// <param name="r">圓形半徑</param>
        /// <param name="t">點T</param>
        /// <returns></returns>
        public static bool IsInCircle(PointF p, float r, PointF t)
        {
            float length = (float)LengthFromPointToPoint(p, t);
            if (length <= r)
                return true;
            else
                return false;
        }

        /// <summary>
        /// 點T是否在三角形內
        /// </summary>
        /// <param name="a">三角形A點座標</param>
        /// <param name="b">三角形B點座標</param>
        /// <param name="c">三角形C點座標</param>
        /// <param name="t">點T</param>
        /// <returns></returns>
        public static bool IsInTriangle(PointF a, PointF b, PointF c, PointF t)
        {
            bool temp1 = IsInIntercept(a, b, c, t);
            bool temp2 = IsInIntercept(a, c, b, t);
            bool temp3 = IsInIntercept(b, c, a, t);
            if (temp1 && temp2 && temp3) return true;
            else return false;
        }

        /// <summary>
        /// 點T是否線上AB和C的截距範圍內
        /// </summary>
        /// <param name="a">線端點A</param>
        /// <param name="b">線端點B</param>
        /// <param name="c">三角形的另一個點C</param>
        /// <param name="t">點T</param>
        /// <returns></returns>
        public static bool IsInIntercept(PointF a, PointF b, PointF c, PointF t)
        {
            // AB延長線在Y軸上的截點,AB過C和T平行線在座標軸上的截點
            float p1, p2, pt;
            // 斜率不存在時
            if (a.X == b.X)
            {
                p1 = a.X;
                p2 = c.X;
                pt = t.X;
            }
            else
            {
                // 斜率為0時
                if (a.Y == b.Y)
                {
                    p1 = a.Y;
                    p2 = c.Y;
                    pt = t.Y;
                }
                // 斜率不為0時
                else
                {
                    // 斜率
                    float k = (a.Y - b.Y) / (a.X - b.X);    
                    float bb = a.Y - k * a.X;
                    // Y軸的截距
                    p1 = bb;
                    p2 = c.Y - k * c.X;
                    pt = t.Y - k * t.X;
                }
            }
            if ((pt <= p2 && pt >= p1) || (pt <= p1 && pt >= p2)) return true;
            else return false;
        }
    }
}