将绕组数算法扩展到弧

计算科学 计算几何 几何学 造型
2021-12-07 18:35:53

我有一个问题,我已经尝试解决了几天。我想知道我是否能够从社区获得一些帮助。

为了检测一个点是否在多边形中,我使用了绕组数方法。该算法基于我在此站点上找到的内容:http: //geomalgorithms.com/a03-_inclusion.html简而言之,该算法将返回一个结果,该结果将指示该点位于线段的左侧还是右侧。对于直线的逆时针方向,正值表示该点位于直线的左侧,负值表示该点位于直线的右侧。符号切换为顺时针方向。如果“lefts”的数量大于“rights”的数量,则该点位于多边形内部。仅考虑端点在点之间的线以加快算法速度。

现在我的多边形(我松散地使用这个术语)由弧和线段组成。这些多边形很简单,不会自行循环。多边形可以是凸的或凹的。作为旁注,我的程序中的弧表示为圆弧。

绕组数算法非常适用于线段。但是当我们谈到弧线时,事情可能会稍微复杂一些。我目前的实现是考虑弧线下方的任何点都在弧线内。(注:我的算法实现源码在文末给出)

这种实现大部分都有效,但从下图中,红点将被视为误报。缠绕数算法将指示红点在多边形内部,而它显然不在多边形内部。

假阳性 从算法中,第 1 行表示该点位于左侧,第 4 行表示该点位于右侧。但是弧段 2 将指示该点也在左侧,因此该点位于多边形内部。

现在,使用弧的实现有另一个错误,算法将返回假阴性。下面是几何图形,其中中心的点位于多边形的外部,而侧面的点位于多边形内部。

假阴性示例

该多边形仅由弧组成。

几天来,我一直在考虑不同的实现,但到目前为止,还没有任何结果。关于弧的另一件事是它们都处于逆时针方向。圆弧是凹的还是凸的取决于选择哪个节点作为第一个节点。

我正在考虑回到光线交叉算法,但我想看看我是否可以先让它工作。如果我有两种不同的直线和圆弧算法,我很好(事实上,我认为我会的)。理想情况下,我想使用缠绕数算法,但我愿意接受其他解决方案。

代码:

如果您想查看更完整的解决方案,我将发布代码源文件的链接。我还发布了与说服相关的片段。如果您对代码有任何疑问,请随时提出。谢谢

多边形测试点:https ://github.com/philm001/Omni-FEM/blob/master/Include/common/ClosedPath.h

bool pointInContour(wxRealPoint point)
    {
        int windingNumber = 0;
        bool isFirstRun = true;
        bool reverseWindingResult = false;

        double additionTerms = 0;
        double subtractionTerms = 0;

        // First, the algorithm will need to ensure that all of the lines are oriented in either CCW or CW.
        // Each contour, the order of the lines is CCW or CW but the order of the nodes is different.
        // For example, the start node of the 1st line could be "connected" to the end node of the next line.
        // In which case, we need to swap the nodes of the 1st line.

        for(auto lineIterator = p_closedPath.begin(); lineIterator != p_closedPath.end(); lineIterator++)
        {
            auto nextLineIterator = lineIterator + 1;

            if(nextLineIterator == p_closedPath.end())
                nextLineIterator = p_closedPath.begin();

            if(isFirstRun)
            {
                if(*(*lineIterator)->getSecondNode() != *(*nextLineIterator)->getFirstNode())
                {
                    if(*(*lineIterator)->getFirstNode() == *(*nextLineIterator)->getFirstNode())
                        (*lineIterator)->swap();
                    else if(*(*lineIterator)->getFirstNode() == *(*nextLineIterator)->getSecondNode())
                    {
                        (*lineIterator)->swap();
                        (*nextLineIterator)->swap();
                    }
                    else if(*(*lineIterator)->getSecondNode() == *(*nextLineIterator)->getSecondNode())
                        (*nextLineIterator)->swap();
                }

                isFirstRun = false;
            }
            else
            {
                if(*(*lineIterator)->getSecondNode() == *(*nextLineIterator)->getSecondNode())
                    (*nextLineIterator)->swap();
            }

            /* 
             * This is the actual shoelace algorithm that is implemented. In short, we have two terms, addition terms
             * and subtraction terms. THe addition terms are the summation of the Xpoint of the first node multiplied by
             * the Ypoint of the second node for all edges (arcs are a special case)
             * 
             * The subtraction term is the summation of the Xpoint of the second node multiplied by the
             * Ypoint of the first node (arcs are a special case)
             */ 
            if((*lineIterator)->isArc())
            {
                /* This algorithm is not being used to accurately determine the arc within a closed contour
                 * with that said, we only need to approximately determine the area. For arcs, we shall draw
                 * a line from the first node to the mid point and then from the mid point to the second node.
                 * We will use these two lines as the calculation for the shoelace algorithm.
                 */ 
                additionTerms += ((*lineIterator)->getFirstNode()->getCenter().x) * ((*lineIterator)->getMidPoint().y);
                subtractionTerms += ((*lineIterator)->getMidPoint().x) * ((*lineIterator)->getFirstNode()->getCenter().y);

                additionTerms += ((*lineIterator)->getMidPoint().x) * ((*lineIterator)->getSecondNode()->getCenter().y);
                subtractionTerms += ((*lineIterator)->getSecondNode()->getCenter().x) * ((*lineIterator)->getMidPoint().y);
            }
            else
            {
                additionTerms += ((*lineIterator)->getFirstNode()->getCenter().x) * ((*lineIterator)->getSecondNode()->getCenter().y);
                subtractionTerms += ((*lineIterator)->getSecondNode()->getCenter().x) * ((*lineIterator)->getFirstNode()->getCenter().y);
            }
        }

        // Next, we need to run the shoe-lace algorithm to determine if the ordering is CCW or CW. If CW, then we will need to swap
        // the start node and end node of all of the edges in order to ensure the polygon edge is in CCW
        double shoelaceResult = additionTerms - subtractionTerms;

        if(shoelaceResult < 0)
            reverseWindingResult = true;

        // Now we can perform the winding number algorithm
        for(auto lineIterator = p_closedPath.begin(); lineIterator != p_closedPath.end(); lineIterator++)
        {

            if((*lineIterator)->isArc() && (*lineIterator)->getSwappedState())
                (*lineIterator)->swap();

            double isLeftResult = (*lineIterator)->isLeft(point);

            if(!(*lineIterator)->isArc())
            {
                if(reverseWindingResult)
                    isLeftResult *= -1;

                if((*lineIterator)->getFirstNode()->getCenterYCoordinate() <= point.y)
                {
                    if((*lineIterator)->getSecondNode()->getCenterYCoordinate() > point.y)
                    {
                        if(isLeftResult > 0)
                            windingNumber++;
                        else if(isLeftResult < 0)
                            windingNumber--;
                    }
                }
                else if((*lineIterator)->getSecondNode()->getCenterYCoordinate() <= point.y)
                {
                    if(isLeftResult < 0)
                        windingNumber--;
                    else if(isLeftResult > 0)
                        windingNumber++;
                }

            }
            else
            {
                if((*lineIterator)->getSwappedState())
                {
                    isLeftResult *= -1;
                    // For arcs, we need to preserve the orientation of the first and second
                    // node for drawing purposes. Lines do not matter as much for drawing but arcs,
                    // it matters
                    (*lineIterator)->swap();
                }

                if(isLeftResult > 0)
                    windingNumber++;
                else
                    windingNumber--;
            }
        }

        if(windingNumber > 0)
            return true;
        else
            return false;
}

IsLeft 函数植入(代码从第 896 行开始):https ://github.com/philm001/Omni-FEM/blob/master/Include/UI/geometryShapes.h

double isLeft(wxRealPoint point)
    {
        if(!p_isArc)
        {
            return ((_secondNode->getCenterXCoordinate() - _firstNode->getCenterXCoordinate()) * (point.y - _firstNode->getCenterYCoordinate()) 
                        - (point.x - _firstNode->getCenterXCoordinate()) * (_secondNode->getCenterYCoordinate() - _firstNode->getCenterYCoordinate())); 
        }
        else
        {
            double result = 0;

            if(isSameSign(crossProduct(point - _firstNode->getCenter(), _secondNode->getCenter() - _firstNode->getCenter()), 
                            crossProduct(this->getCenter() - _firstNode->getCenter(), _secondNode->getCenter() - _firstNode->getCenter())))
            {
                result = dotProduct(point - _firstNode->getCenter(), _secondNode->getCenter() - _firstNode->getCenter()) / 
                            dotProduct(_secondNode->getCenter() - _firstNode->getCenter(), _secondNode->getCenter() - _firstNode->getCenter());

                if(result >= 0 && result <= 1)
                    return 1.0;
                else
                    return -1.0;
            }
            else
            {
                result = dotProduct(point - this->getCenter(), point - this->getCenter());

                if(result <= pow(_radius, 2))
                    return 1.0;
                else
                    return -1.0;
            }


        }
    }
1个回答

用一个盒子包围你的每一条弧线,该盒子连接一个对角线上的两个端点,然后选择另外两个点将东西变成一个盒子。然后用包含所有直线段以及所有框的内侧两侧的多边形替换原来的带弧线的多边形 P。(或外面,或随机选择。)PP

然后,给定一个要测试的点,您可以首先确定该点是否在其中一个框内。如果不是,那么如果它在内部(外部)(易于测试),那么它也必须在内部(外部)另一方面,如果该点位于包围弧的一个框中,那么您将必须测试它是在弧的一侧还是另一侧——但这很容易,因为弧具有如此简单的结构体。PP