计算平面上闭合多边形的面积

时间:2013-12-19 02:05:06

标签: c# 3d rotation geometry

我正在尝试计算位于平面上的多边形区域(一个形成非交叉闭合形状的集合共面点),我知道一种可以计算area of an irregular (or any) polygon in two dimensions的方法 - 但不是三个。我的解决方案是旋转平面,使其在z方向上的法线为0(因此我可以将其视为2D),然后运行2D区域函数。

问题是我不知道如何实际确定旋转轴和在其Z轴上展平平面的数量。我通过最简单的三维旋转方法进行旋转:Rotation Matrices。因此,考虑到我正在尝试使用旋转矩阵进行旋转,我如何计算旋转平面的角度,使其朝向与另一个矢量相同的方向?我实际上并不知道很多微积分或欧几里德几何,所以无论哪种解决方案都需要我自学,两者中最少的是理想的解决方案。还有更好的方法吗?

这是我在下面的尝试,它甚至没有接近让平面在Z轴上平坦。这是我的“Surface”类的实例方法,它是我的“Plane”类的衍生物,并且有一个共面点数组(IntersectPoints),形成一个闭合的多边形。

 public virtual double GetArea()
    {
        Vector zUnit = new Vector(0, 0, 1); //vector perprendicualr to z
        Vector nUnit = _normal.AsUnitVector();
        Surface tempSurface = null;
        double result = 0;

        if (nUnit != zUnit && zUnit.Dot(nUnit) != 0) //0 = perprendicular to z
        {
            tempSurface = (Surface)Clone();
            double xAxisAngle = Vector.GetAxisAngle(nUnit, zUnit, Physics.Formulae.Axes.X);
            double yAxisAngle = Vector.GetAxisAngle(nUnit, zUnit, Physics.Formulae.Axes.Y);
            double rotationAngle = Vector.GetAxisAngle(nUnit, zUnit, Physics.Formulae.Axes.Z);
            tempSurface.Rotate(xAxisAngle, yAxisAngle, rotationAngle); //rotating plane so that it is flat on the Z axis
        }
        else
        {
            tempSurface = this;
        }

        for (int x = 0; x < tempSurface.IntersectPoints.Count; x++) //doing a cross sum of each point
        {
            Point curPoint = tempSurface.IntersectPoints[x];
            Point nextPoint;

            if (x == tempSurface.IntersectPoints.Count - 1)
            {
                nextPoint = tempSurface.IntersectPoints[0];
            }
            else
            {
                nextPoint = tempSurface.IntersectPoints[x + 1];
            }

            double cross1 = curPoint.X * nextPoint.Y;
            double cross2 = curPoint.Y * nextPoint.X;
            result += (cross1 - cross2); //add the cross sum of each set of points to the result
        }

        return Math.Abs(result / 2); //divide cross sum by 2 and take its absolute value to get the area.
    }

这是我的核心旋转和获取轴角度方法:

 private Vector Rotate(double degrees, int axis)
    {
        if (degrees <= 0) return this;
        if (axis < 0 || axis > 2) return this;

        degrees = degrees * (Math.PI / 180); //convert to radians
        double sin = Math.Sin(degrees);
        double cos = Math.Cos(degrees);
        double[][] matrix = new double[3][]; 

        //normalizing really small numbers to actually be zero
        if (Math.Abs(sin) < 0.00000001) 
        {
            sin = 0;
        }
        if (Math.Abs(cos) < 0.0000001)
        {
            cos = 0;
        }

        //getting our rotation matrix
        switch (axis)
        {
            case 0: //x axis
                matrix = new double[][] 
                { 
                    new double[] {1, 0, 0},
                    new double[] {0, cos, sin * -1},
                    new double[] {0, sin, cos}
                };
                break;
            case 1: //y axis
                matrix = new double[][] 
                { 
                    new double[] {cos, 0, sin},
                    new double[] {0, 1, 0},
                    new double[] {sin * -1, 0, cos}
                };
                break;
            case 2: //z axis
                matrix = new double[][] 
                { 
                    new double[] {cos, sin * -1, 0},
                    new double[] {sin, cos, 0},
                    new double[] {0, 0, 1}
                };
                break;
            default:
                return this;
        }

        return Physics.Formulae.Matrix.MatrixByVector(this, matrix);
    }

public static double GetAxisAngle(Point a, Point b, Axes axis, bool inDegrees = true)
    { //pretty sure this doesnt actually work
        double distance = GetDistance(a, b);
        double difference;

        switch (axis)
        {
            case Axes.X:
                difference = b.X - a.X;
                break;
            case Axes.Y:
                difference = b.Y - a.Y;
                break;
            case Axes.Z :
                difference = b.Z - a.Z;
                break;
            default:
                difference = 0;
                break;
        }

        double result = Math.Acos(difference / distance);

        if (inDegrees == true)
        {
            return result * 57.2957; //57.2957 degrees = 1 radian
        }
        else
        {
            return result;
        }
    }

2 个答案:

答案 0 :(得分:1)

执行此操作的有效方法是对每条边的顶点的叉积进行求和。如果你的顶点是共面的,这将产生平面的法线,其长度是闭合多边形面积的2倍。

请注意,此方法与您的问题中链接的2D方法非常相似,它实际上计算了3D交叉积的2D等价物,对所有边进行求和,然后除以2。

Vector normal = points[count-1].cross(points[0]);
for(int i=1; i<count; ++i) {
    normal += points[i-1].cross(points[i]);
}
double area = normal.length() * 0.5;

此方法的优点:

  • 如果你的顶点只是大约平面,它仍会给出正确的答案
  • 它不依赖于飞机的角度。
  • 事实上,你根本不需要处理这个角度。
  • 如果您希望知道飞机方向,那么您已经恢复正常了。

一个可能的困难:如果您的多边形非常小,并且距离原点很远,则可能会出现浮点精度问题。如果可能出现这种情况,您应首先翻译所有顶点,使其位于原点,如下所示:

Vector normal(0,0,0);
Vector origin = points[count-1]; 
for(int i=1; i<count-1; ++i) {
    normal += (points[i-1]-origin).cross(points[i]-origin);
}
double area = normal.length() * 0.5;

答案 1 :(得分:0)

您无需旋转平面(或所有点)。只需计算多边形投影到Z平面的面积(如果它不垂直于多边形平面),例如,使用GetArea函数,并将结果除以多边形的平面 - Z平面角度 - 它等于标量zUnit和nUnit的产品(我建议nUnit是多边形平面的法线向量)

TrueArea = GetArea() / zUnit.Dot(nUnit)