问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

贝塞尔曲线应用(贝塞尔插值)

创作时间:
作者:
@小白创作中心

贝塞尔曲线应用(贝塞尔插值)

引用
1
来源
1.
https://www.pianshen.com/article/3344550444/

贝塞尔插值是一种将多个顶点连接成平滑曲线的技术。通过使用贝塞尔曲线,可以确保生成的曲线既平滑又能够通过所有指定的顶点。本文将详细介绍贝塞尔插值的基本原理、实现步骤以及实际应用中的代码示例。

贝塞尔插值的基本原理

贝塞尔插值的核心思想是使用贝塞尔曲线来连接给定的顶点。具体来说,对于每对相邻的顶点,我们可以构造一条贝塞尔曲线,使得这条曲线通过这两个顶点。为了确保相邻的贝塞尔曲线能够平滑地连接在一起,我们需要计算合适的控制点。

实现步骤

1. 计算中点

首先,我们需要计算多边形每条边的中点。设多边形的顶点序列为 (P_0, P_1, \ldots, P_{n-1}),则第 (i) 条边的中点 (A_i) 可以表示为:

2. 连接中点

接下来,我们连接相邻边的中点,得到一系列线段 (C_i)。然后,我们需要计算这些线段与顶点之间的关系,以便确定控制点的位置。

3. 计算控制点

控制点的位置可以通过将线段 (C_i) 沿着特定路径进行平移来确定。具体来说,我们需要计算每条线段上从 (B_i) 到对应顶点的路径,并沿着这条路径移动控制点。

代码实现

下面是具体的代码实现,包括计算控制点和绘制贝塞尔曲线的函数:

// Assume we need to calculate the control
// points between (x1,y1) and (x2,y2).
// Then x0,y0 - the previous vertex,
// x3,y3 - the next one. 
double xc1 = (x0 + x1) / 2.0;
double yc1 = (y0 + y1) / 2.0;
double xc2 = (x1 + x2) / 2.0;
double yc2 = (y1 + y2) / 2.0;
double xc3 = (x2 + x3) / 2.0;
double yc3 = (y2 + y3) / 2.0; 
double len1 = sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
double len2 = sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
double len3 = sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2)); 
double k1 = len1 / (len1 + len2);
double k2 = len2 / (len2 + len3); 
double xm1 = xc1 + (xc2 - xc1) * k1;
double ym1 = yc1 + (yc2 - yc1) * k1; 
double xm2 = xc2 + (xc3 - xc2) * k2;
double ym2 = yc2 + (yc3 - yc2) * k2; 
// Resulting control points. Here smooth_value is mentioned
// above coefficient K whose value should be in range [0...1].
ctrl1_x = xm1 + (xc2 - xm1) * smooth_value + x1 - xm1;
ctrl1_y = ym1 + (yc2 - ym1) * smooth_value + y1 - ym1; 
ctrl2_x = xm2 + (xc2 - xm2) * smooth_value + x2 - xm2;
ctrl2_y = ym2 + (yc2 - ym2) * smooth_value + y2 - ym2; 

使用三次贝塞尔近似的代码

下面是使用三次贝塞尔曲线进行插值的代码实现:

// Number of intermediate points between two source ones,
// Actually, this value should be calculated in some way,
// Obviously, depending on the real length of the curve.
// But I don't know any elegant and fast solution for this
// problem.
#define NUM_STEPS 20 
void curve4(Polygon* p,
 double x1, double y1, //Anchor1
 double x2, double y2, //Control1
 double x3, double y3, //Control2
 double x4, double y4) //Anchor2
{
 double dx1 = x2 - x1;
 double dy1 = y2 - y1;
 double dx2 = x3 - x2;
 double dy2 = y3 - y2;
 double dx3 = x4 - x3;
 double dy3 = y4 - y3; 
 double subdiv_step = 1.0 / (NUM_STEPS + 1);
 double subdiv_step2 = subdiv_step*subdiv_step;
 double subdiv_step3 = subdiv_step*subdiv_step*subdiv_step; 
 double pre1 = 3.0 * subdiv_step;
 double pre2 = 3.0 * subdiv_step2;
 double pre4 = 6.0 * subdiv_step2;
 double pre5 = 6.0 * subdiv_step3; 
 double tmp1x = x1 - x2 * 2.0 + x3;
 double tmp1y = y1 - y2 * 2.0 + y3; 
 double tmp2x = (x2 - x3)*3.0 - x1 + x4;
 double tmp2y = (y2 - y3)*3.0 - y1 + y4; 
 double fx = x1;
 double fy = y1; 
 double dfx = (x2 - x1)*pre1 + tmp1x*pre2 + tmp2x*subdiv_step3;
 double dfy = (y2 - y1)*pre1 + tmp1y*pre2 + tmp2y*subdiv_step3; 
 double ddfx = tmp1x*pre4 + tmp2x*pre5;
 double ddfy = tmp1y*pre4 + tmp2y*pre5; 
 double dddfx = tmp2x*pre5;
 double dddfy = tmp2y*pre5; 
 int step = NUM_STEPS; 
 // Suppose, we have some abstract object Polygon which
 // has method AddVertex(x, y), similar to LineTo in
 // many graphical APIs.
 // Note, that the loop has only operation add!
 while(step--)
 {
 fx += dfx;
 fy += dfy;
 dfx += ddfx;
 dfy += ddfy;
 ddfx += dddfx;
 ddfy += dddfy;
 p->AddVertex(fx, fy);
 }
 p->AddVertex(x4, y4); // Last step must go exactly to x4, y4
}

实际应用示例

运用上述原理,某位网友给出了C++封装好的代码,如下:

为了把一串点连成光滑的曲线,运用贝塞尔曲线的光滑性来穿过这些点。

大致思路就是 先算出相邻原始点的中点,在把相邻中点连成的线段平移到对应的原始点,以平移后的中点作为控制点,相邻原始点为起始点画贝塞尔曲线,这样就保证了连接处的光滑。而贝塞尔曲线本身是光滑的,所以就把这些原始点用光滑曲线连起来了。

我封装了一个函数,留着以后用。

(c++版,其它语言只要把数组和可变数组稍微变一下就能用)

void createCurve(CvPoint *originPoint,int originCount,vector<CvPoint> &curvePoint){
 //控制点收缩系数 ,经调试0.6较好,CvPoint是opencv的,可自行定义结构体(x,y)
 float scale = 0.6;
 CvPoint midpoints[originCount];
 //生成中点
 for(int i = 0 ;i < originCount ; i++){
 int nexti = (i + 1) % originCount;
 midpoints[i].x = (originPoint[i].x + originPoint[nexti].x)/2.0;
 midpoints[i].y = (originPoint[i].y + originPoint[nexti].y)/2.0;
 }
 //平移中点
 CvPoint extrapoints[2 * originCount];
 for(int i = 0 ;i < originCount ; i++){
 int nexti = (i + 1) % originCount;
 int backi = (i + originCount - 1) % originCount;
 CvPoint midinmid;
 midinmid.x = (midpoints[i].x + midpoints[backi].x)/2.0;
 midinmid.y = (midpoints[i].y + midpoints[backi].y)/2.0;
 int offsetx = originPoint[i].x - midinmid.x;
 int offsety = originPoint[i].y - midinmid.y;
 int extraindex = 2 * i;
 extrapoints[extraindex].x = midpoints[backi].x + offsetx;
 extrapoints[extraindex].y = midpoints[backi].y + offsety;
 //朝 originPoint[i]方向收缩
 int addx = (extrapoints[extraindex].x - originPoint[i].x) * scale;
 int addy = (extrapoints[extraindex].y - originPoint[i].y) * scale;
 extrapoints[extraindex].x = originPoint[i].x + addx;
 extrapoints[extraindex].y = originPoint[i].y + addy;
 int extranexti = (extraindex + 1)%(2 * originCount);
 extrapoints[extranexti].x = midpoints[i].x + offsetx;
 extrapoints[extranexti].y = midpoints[i].y + offsety;
 //朝 originPoint[i]方向收缩
 addx = (extrapoints[extranexti].x - originPoint[i].x) * scale;
 addy = (extrapoints[extranexti].y - originPoint[i].y) * scale;
 extrapoints[extranexti].x = originPoint[i].x + addx;
 extrapoints[extranexti].y = originPoint[i].y + addy;
 }
 CvPoint controlPoint[4];
 //生成4控制点,产生贝塞尔曲线
 for(int i = 0 ;i < originCount ; i++){
 controlPoint[0] = originPoint[i];
 int extraindex = 2 * i;
 controlPoint[1] = extrapoints[extraindex + 1];
 int extranexti = (extraindex + 2) % (2 * originCount);
 controlPoint[2] = extrapoints[extranexti];
 int nexti = (i + 1) % originCount;
 controlPoint[3] = originPoint[nexti];
 float u = 1;
 while(u >= 0){
 int px = bezier3funcX(u,controlPoint);
 int py = bezier3funcY(u,controlPoint);
 //u的步长决定曲线的疏密
 u -= 0.005;
 CvPoint tempP = cvPoint(px,py);
 //存入曲线点
 curvePoint.push_back(tempP);
 }
 }
 }
 //三次贝塞尔曲线
 float bezier3funcX(float uu,CvPoint *controlP){
 float part0 = controlP[0].x * uu * uu * uu;
 float part1 = 3 * controlP[1].x * uu * uu * (1 - uu);
 float part2 = 3 * controlP[2].x * uu * (1 - uu) * (1 - uu);
 float part3 = controlP[3].x * (1 - uu) * (1 - uu) * (1 - uu);
 return part0 + part1 + part2 + part3;
 }
 float bezier3funcY(float uu,CvPoint *controlP){
 float part0 = controlP[0].y * uu * uu * uu;
 float part1 = 3 * controlP[1].y * uu * uu * (1 - uu);
 float part2 = 3 * controlP[2].y * uu * (1 - uu) * (1 - uu);
 float part3 = controlP[3].y * (1 - uu) * (1 - uu) * (1 - uu);
 return part0 + part1 + part2 + part3;
 }

实际效果展示

下面是用原始形状和系统K=1.0的贝塞尔插值两种方法来描画的 SVG 的狮子。

下面是放大图

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号