贝塞尔曲线应用(贝塞尔插值)
创作时间:
作者:
@小白创作中心
贝塞尔曲线应用(贝塞尔插值)
引用
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 的狮子。
下面是放大图
热门推荐
平行世界的桥梁,拉普拉斯变换的威力
十代i5笔记本CPU天梯图解析,性能与性价比全面对比分析
如何评估PE投资的回报
NCEP/DOE Reanalysis II气候数据集:构建、应用与最新研究进展
“新中式”旅游“出圈”,助力场景沉浸营造与传统文化焕新
如何装pve虚拟机
五大战区分布特点及影响因素分析
企业人力资源部如何确保合规性和遵循法律法规?
为什么光耦固态继电器(SSR)值得关注?
望海潮:柳永的诗意人生与词韵隽永
50年和70年的产权房有何不同?能买吗?
DVT:消除视觉变换器中的噪声伪影
2024年专业牙医的正畸指南:避免矫牙误区,打造好笑容
手上长倒刺,千万别手欠!有人因此进急诊!
银行的信用卡信用额度提升可以通过还款记录实现吗?
低压预装式变电站的并联运行技术
KLY214AS固态继电器的特点及应用领域概述
跟着网评购物:网络信息如何影响消费行为
银行房贷对二手房的房龄要求和判断方法
银行房贷对二手房的房龄要求和判断方法
主板2个M.2接口,哪个m2插槽是与CPU直连?总结不得不说PCIe知识
小笼包的蒸笼垫选择
健康生活丨重视饮水卫生 保障健康生活
了解人博卡病毒
写给小白的操作系统入门科普
316不锈钢可以做餐具吗?探索现代厨房新选择
英特尔13/14代CPU缩缸引发性能争议:解决方案与影响分析
女命八字忌印比是什么意思
夫妻公积金贷款扣款顺序及共同贷款注意事项
置业顾问的奇幻之旅:如何在虚构的故事中打动客户的心?