贝塞尔曲线应用(贝塞尔插值)
创作时间:
作者:
@小白创作中心
贝塞尔曲线应用(贝塞尔插值)
引用
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 的狮子。
下面是放大图
热门推荐
斯大林格勒战役,战略要地,命运转折
如何选择最适合你的剑桥英语教材
35句豁达洒脱诗词:人生要安于所遇,并自得其乐,才是最大的成功
峨眉山特色中餐盘点:必尝的8道美食,让你食指大动!
深覆合的定型年龄:一个动态变化的过程
过敏性皮肤干燥脱皮怎么办?
计算机考研哪个方向好?内行人推荐的6大热门方向,考生必看
深度解析,大盘股,定义、特点与投资策略
什么是IT?IT行业是做什么的?
电动车骑行指南:如何确保每次出行都安全无虞
《凡人歌》映照现代社会中婚姻的真实面貌和复杂性
银行贷款利率调整的参考什么因素
饮食控制,科学减肥
运动与血糖管理:适合糖尿病患者的运动方案
黑龙江中医药大学怎么样好不好,附网民真实评价
活性维生素药物在肾内科该如何选用?
新冠试剂检测盒使用方法详解
技能人才:企业发展的不可或缺之力
反季旅行真的香,冬天可去的9个热门目的地,人少景美体验好
如何写一个好的产品创意宣传视频脚本?去哪里找灵感?
非机动车走机动车道出事故怎么划分责任
新冠试剂检测盒使用方法详解
法官说法丨原车主VS买受人——返还原物请求权和善意取得之间的“火花”
司马昭为何敢公然除掉曹髦呢?
时评:铁路旅游专列绽芳华,文旅融合绘新卷
过渡句在交流与写作中的重要性与应用技巧探讨
2.0L与1.4T:高速试驾揭示性能差异
有效利用课堂笔记备考的技巧
上汽通用旗下多款车型现燃油泵模块故障,雪佛兰获召回,别克凯迪拉克遭质疑
为什么大便形状会改变?7种常见类型与对应改善方法