图形/多段线内缩外扩思路

博主分享了在处理多段线内缩外扩时遇到的问题,即现有算法库如Clipper和boost::geometry::buffer会改变点位顺序,不适用于需要保持原始点位信息的场景。为解决这一问题,提出了使用直线段平移的方法,保留顶点顺序,适用于带有圆弧的多段线。虽然方法较为简单,但能有效满足需求。博客还提及了几何理论和直线处理的相关知识,并链接了一个可能的解决方案实现。
摘要由CSDN通过智能技术生成

图形/多段线内缩外扩思路

前言

我在网上找了很多关于多边形内缩外扩的资料,也测试了一些算法库,如Clipperboost::geometry::buffer,其实都可以得到不错的结果。

Clipper

#include "clipper.hpp"   
... 
using namespace ClipperLib; 
  
int main() 
{ 
  Path subj; 
  Paths solution; 
  subj <<  
    IntPoint(348,257) << IntPoint(364,148) << IntPoint(362,148) <<  
    IntPoint(326,241) << IntPoint(295,219) << IntPoint(258,88) <<  
    IntPoint(440,129) << IntPoint(370,196) << IntPoint(372,275); 
  ClipperOffset co; 
  co.AddPath(subj, jtRound, etClosedPolygon); 
  co.Execute(solution, -7.0); 
    
  //draw solution ... 
  DrawPolygons(solution, 0x4000FF00, 0xFF009900); 
}
  • 效果


    offset1

boost::geometry::buffer

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/geometries.hpp>


int main()
{
    typedef double coordinate_type;
    typedef boost::geometry::model::d2::point_xy<coordinate_type> point;
    typedef boost::geometry::model::polygon<point> polygon;

    // Declare strategies
    const double buffer_distance = 1.0;
    const int points_per_circle = 36;
    boost::geometry::strategy::buffer::distance_symmetric<coordinate_type> distance_strategy(buffer_distance);
    boost::geometry::strategy::buffer::join_round join_strategy(points_per_circle);
    boost::geometry::strategy::buffer::end_round end_strategy(points_per_circle);
    boost::geometry::strategy::buffer::point_circle circle_strategy(points_per_circle);
    boost::geometry::strategy::buffer::side_straight side_strategy;

    // Declare output
    boost::geometry::model::multi_polygon<polygon> result;

    // Declare/fill a linestring
    boost::geometry::model::linestring<point> ls;
    boost::geometry::read_wkt("LINESTRING(0 0,4 5,7 4,10 6)", ls);

    // Create the buffer of a linestring
    boost::geometry::buffer(ls, result,
                distance_strategy, side_strategy,
                join_strategy, end_strategy, circle_strategy);


    // Declare/fill a multi point
    boost::geometry::model::multi_point<point> mp;
    boost::geometry::read_wkt("MULTIPOINT((3 3),(4 4),(6 2))", mp);

    // Create the buffer of a multi point
    boost::geometry::buffer(mp, result,
                distance_strategy, side_strategy,
                join_strategy, end_strategy, circle_strategy);


    // Declare/fill a multi_polygon
    boost::geometry::model::multi_polygon<polygon> mpol;
    boost::geometry::read_wkt("MULTIPOLYGON(((0 1,2 5,5 3,0 1)),((1 1,5 2,5 0,1 1)))", mpol);

    // Create the buffer of a multi polygon
    boost::geometry::buffer(mpol, result,
                distance_strategy, side_strategy,
                join_strategy, end_strategy, circle_strategy);


    return 0;
}
  • 效果


    buffer_linestring
    buffer_multi_point
    buffer_multi_polygon

遇到的问题

我现在得到的图形是经过反向解析文件再生成的,每个点位都是包含了一些信息,原始的点位个数和顺序是不可以轻易被改变的,而且图形也不一定是封闭的,其组成是可能带圆弧的多段线,后面再生成的文件还得和原始图形的点位顺序对应上才行。而以上两种处理库,是会改变点传入点位的个数和顺序的,这样的结果并不能正确生成理想的文件,就是这个问题让我很是头疼。

后来尝试找些几何理论的资料来解决,而网上的不少资料都是通过向量的方式来求夹角的,在出现内缩到一定程度,会有自交的情况,需要自己跳过垂直平行重复的点位,显然这也不是我想要的结果。

抛砖引玉

期间想到可以用直线段按照斜率平移的方法来处理,内缩的情况是相邻两线段的交点就是图形新的顶点,而拐角是圆弧的时候,判断圆弧两边的线段平移后有没有交点来决定要不要移除。当然这个办法显然比较笨,但是得到的图形顶点顺序和个数是可控的,也能让我如愿与原始的点位所带的信息对应上。

这里只是记录了思路,具体代码实现省略。附带直线的相关知识。

可以参考大神的实现方法

jbuckmccready/CavalierContours

最后

感谢各位大佬的无私奉献。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线段裁剪算法是计算机图形学中非常重要的算法之一,常用于将线段裁剪到指定的窗口范围内。下面是一份使用C++实现的边线段裁剪算法的代码示例: ```c++ #include <iostream> #include <graphics.h> //定义窗口边界 const int LEFT = 100; const int RIGHT = 500; const int TOP = 100; const int BOTTOM = 400; //定义线段端点的结构体 struct Point { int x; int y; }; //计算端点与边界的位置关系 enum Position { INSIDE, LEFT_OF, RIGHT_OF, BELOW, ABOVE }; //计算端点与边界的位置关系 Position getPosition(Point p, int edge) { if (p.x < edge) { return LEFT_OF; } else if (p.x > edge) { return RIGHT_OF; } else if (p.y < edge) { return BELOW; } else if (p.y > edge) { return ABOVE; } else { return INSIDE; } } //裁剪线段 void clipLine(Point p1, Point p2) { int x1 = p1.x; int y1 = p1.y; int x2 = p2.x; int y2 = p2.y; //计算端点与边界的位置关系 Position p1pos = INSIDE; Position p2pos = INSIDE; while (true) { p1pos = getPosition(p1, LEFT); p2pos = getPosition(p2, LEFT); //如果两个端点都在窗口内,则绘制该线段并退出循环 if ((p1pos == INSIDE) && (p2pos == INSIDE)) { line(x1, y1, x2, y2); break; } //如果两个端点都在窗口外,则直接退出循环 if ((p1pos != INSIDE) && (p2pos != INSIDE)) { break; } //计算线段与窗口边界的交点 int intersectX = 0; int intersectY = 0; if (p1pos != INSIDE) { if (p1pos == LEFT_OF) { intersectX = LEFT; intersectY = y1 + (y2 - y1) * (intersectX - x1) / (x2 - x1); } else if (p1pos == RIGHT_OF) { intersectX = RIGHT; intersectY = y1 + (y2 - y1) * (intersectX - x1) / (x2 - x1); } else if (p1pos == BELOW) { intersectY = BOTTOM; intersectX = x1 + (x2 - x1) * (intersectY - y1) / (y2 - y1); } else if (p1pos == ABOVE) { intersectY = TOP; intersectX = x1 + (x2 - x1) * (intersectY - y1) / (y2 - y1); } x1 = intersectX; y1 = intersectY; } else { if (p2pos == LEFT_OF) { intersectX = LEFT; intersectY = y1 + (y2 - y1) * (intersectX - x1) / (x2 - x1); } else if (p2pos == RIGHT_OF) { intersectX = RIGHT; intersectY = y1 + (y2 - y1) * (intersectX - x1) / (x2 - x1); } else if (p2pos == BELOW) { intersectY = BOTTOM; intersectX = x1 + (x2 - x1) * (intersectY - y1) / (y2 - y1); } else if (p2pos == ABOVE) { intersectY = TOP; intersectX = x1 + (x2 - x1) * (intersectY - y1) / (y2 - y1); } x2 = intersectX; y2 = intersectY; } } } int main() { //初始化图形界面 initwindow(600, 500, "Line Clipping"); //绘制窗口边界 rectangle(LEFT, TOP, RIGHT, BOTTOM); //定义测试线段 Point lineStart = {50, 200}; Point lineEnd = {450, 350}; //绘制测试线段 setcolor(YELLOW); line(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y); //裁剪测试线段并绘制裁剪后的线段 setcolor(GREEN); clipLine(lineStart, lineEnd); //等待用户关闭窗口 getchar(); //关闭图形界面 closegraph(); return 0; } ``` 这个程序使用了位置关系算法来计算线段与窗口边界的位置关系,并使用 Cohen-Sutherland 算法来裁剪线段。裁剪后的线段使用绿色表示,测试线段使用黄色表示,窗口边界使用黑色表示。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值