【问题标题】:How to check if point belongs to ConvexShape?如何检查点是否属于 ConvexShape?
【发布时间】:2016-07-22 21:21:52
【问题描述】:

我为我的平台游戏编写了地图编辑器。我正在使用 SFML。地图由多边形组成 - ConvexShapes。我需要通过单击它们来添加 ConvexShapes 的选择。我知道我可以使用cv.getLocalBounds() 来获取矩形并接下来检查,但我需要更精确的解决方案。如何检查点击的点是否属于任何形状?

【问题讨论】:

  • 你可以考虑使用外部库:Boost.Geometry,它有一些例程来检查一个点是否属于一个多边形。或者你可以自己实现这个功能,看一些例子:alienryderflex.com/polygon
  • @jnbrq-CanberkSönmez 作为答案完全合法,但需要总结教程链接。

标签: c++ sfml


【解决方案1】:

基于问题 cmets link,这就是我得到的。

使用那里描述的算法,我们可以确定给定点是否在一个形状内,计算每边有多少交叉点

如果它们的数量每边都是奇数,则该点属于形状。那么简单:

bool contains(sf::ConvexShape shape, sf::Vector2f point){
    std::vector<sf::Vector2f> intersectPoints = getIntersectionPoints(shape, point);
    int nodesAtLeft = 0;
    int nodesAtRight = 0;
    for (sf::Vector2f po : intersectPoints){
        if (po.x < point.x){
            nodesAtLeft++;
        }
        else if(po.x > point.x){
            nodesAtRight++;
        }
    }
    return ((nodesAtLeft % 2) == 1) && ((nodesAtRight % 2) == 1);
}

那么,我们如何获得这些交点?我们应该检查形状的每一侧,其中 交叉点 与由我们给定的点确定的水平线。请注意,交叉点可能远离形状,因为交叉点的计算考虑整条线,不是线段

所以,一旦我们得到了那个交叉点,我们应该检查它是否属于这个段。

std::vector<sf::Vector2f> getIntersectionPoints(sf::ConvexShape shape, sf::Vector2f point){
    std::vector<sf::Vector2f> intersectPoints;
    sf::Vector2f p;
    bool crossingLine;  // This will be used to avoid duplicated points on special cases

    if (shape.getPointCount() < 3){
        return intersectPoints;
    }

    sf::FloatRect bounds = shape.getLocalBounds();

    // To determine horizontal line, we use two points, one at leftmost side of the shape (in fact, its bound) and the other at rightmost side
    Line pointLine, shapeLine;
    pointLine.p1 = sf::Vector2f(bounds.left, point.y);
    pointLine.p2 = sf::Vector2f(bounds.left + bounds.width, point.y);

    unsigned int nPoints = shape.getPointCount();

    for (int i = 0; i < nPoints; ++i){
        try{
            shapeLine.p1 = shape.getPoint(i % nPoints);         // Last one will be nPoints-1
            shapeLine.p2 = shape.getPoint((i + 1) % nPoints);   // So this one must be 0 in order to check last side (returning to origin)
            crossingLine = (shapeLine.p1.y >= point.y && shapeLine.p2.y <= point.y) || (shapeLine.p2.y >= point.y && shapeLine.p1.y <= point.y);
            p = intersection(shapeLine, pointLine);
            if (crossingLine && shapeLine.contains(p))
                intersectPoints.push_back(p);
        }
        catch (std::runtime_error e){

        }
    }

    return intersectPoints;
}

我认为检查点 C 是否属于段(由点 AB 定义)的最简单方法是通过执行距离检查,例如:

distance(A,C) + distance(C,B) == distance(A,B)

但是由于这个可能会过于严格,我已经对其进行了调整以考虑一点误差范围:

abs((distance(A, C) + distance(C, B)) - distance(A, B)) &lt; margin

在我忘记之前,Line 是这样定义的

struct Line{
    sf::Vector2f p1;
    sf::Vector2f p2;

    bool contains(sf::Vector2f point) const{
        float margin = 0.1; 
        return std::abs((distance(p1, point) + distance(point, p2)) - distance(p1, p2)) < margin;
    }
};

有了这个,现在唯一剩下的就是计算两条给定线之间的交点。真诚的,这个我就不解释了(主要是我刚刚从wikipedia复制过来的)

sf::Vector2f intersection(Line lineA, Line lineB){
    int x1 = lineA.p1.x;
    int y1 = lineA.p1.y;
    int x2 = lineA.p2.x;
    int y2 = lineA.p2.y;

    int x3 = lineB.p1.x;
    int y3 = lineB.p1.y;
    int x4 = lineB.p2.x;
    int y4 = lineB.p2.y;

    try{
        double retX = ((x1*y2 - y1*x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3*x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4));
        double retY = ((x1*y2 - y1*x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3*x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4));
        return sf::Vector2f(retX, retY);
    }
    catch (std::exception){
        throw new std::exception("");
    }
}

如果两条线平行或同一条线,两个分母都为零,会抛出DivideByZero异常,没有交集就不是问题。

我还做了一个 sn-p 来测试这个:

int main()
{
    sf::RenderWindow v(sf::VideoMode(600,400), "SFML");
    sf::ConvexShape shape;
    std::vector<sf::Vector2i> points;
    std::vector<sf::CircleShape> intPoints;

    shape.setPointCount(0);
    shape.setOutlineColor(sf::Color::Blue);
    shape.setFillColor(sf::Color::Black);
    shape.setOutlineThickness(1);

    while (v.isOpen()){
        sf::Event event;
        while (v.pollEvent(event)){
            if (event.type == sf::Event::Closed)
                v.close();
            else if (event.type == sf::Event::MouseButtonPressed){
                if (event.mouseButton.button == sf::Mouse::Button::Left){
                    // Add a point to the shape
                    intPoints.clear();
                    sf::Vector2i p = sf::Mouse::getPosition(v);
                    points.push_back(p);
                    shape.setPointCount(points.size());
                    for (int i = 0; i < points.size(); ++i){
                        shape.setPoint(i, sf::Vector2f(points[i]));
                    }
                }
                else if (event.mouseButton.button == sf::Mouse::Button::Right){
                    // Delete shape
                    points.clear();
                    intPoints.clear();
                    shape.setPointCount(0);
                }
                else if (event.mouseButton.button == sf::Mouse::Button::Middle){
                    // Set testing point
                    intPoints.clear();
                    sf::Vector2i p = sf::Mouse::getPosition(v);
                    if (contains(shape, sf::Vector2f(p))){
                        std::cout << "Point inside shape" << std::endl;
                    }
                    else{
                        std::cout << "Point outside shape" << std::endl;
                    }
                    auto v = getIntersectionPoints(shape, sf::Vector2f(p));
                    for (sf::Vector2f po : v){
                        sf::CircleShape c(2);
                        c.setFillColor(sf::Color::Green);
                        c.setOrigin(1, 1);
                        c.setPosition(po);
                        intPoints.push_back(c);
                    }
                    // testing point added too, to be visualized
                    sf::CircleShape c(2);
                    c.setFillColor(sf::Color::Red);
                    c.setOrigin(1, 1);
                    c.setPosition(sf::Vector2f(p));
                    intPoints.push_back(c);

                }
            }
        }
        v.clear();
        v.draw(shape);
        for (sf::CircleShape c : intPoints){
            v.draw(c);
        }
        v.display();
    }

    return 0;
}

一些捕获:

可能是一篇很长的帖子,但我已经尽力说清楚了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    • 2018-10-25
    • 2017-01-08
    相关资源
    最近更新 更多