浅谈贝塞尔(Bezier)曲线的原理以及VC代码实现
Bezier曲线原理
诞生缘由:
就是有一位大神觉得画曲线工具太难用,打个比方微软的画图工具和其他专业的画图工具,他就越想越恼火,为什么我就不能修改画错的曲线呢? 为什么我要重画呢?随后就发明了这个震惊世界的算法,随后在CAD领域得到广泛的运用!
原理:
原理就是我画的曲线是像S身材的火辣妹子一样,既圆润又漂亮,而且还能微整。岂不美哉。
很简单,我们现在有两个点P0,P1比喻一个妹子的头和脚两个点,你画一条直线是不是发现居然平平无奇对吧(图1),但是那位大神就想着我能不能让她变得前凸后翘随意改变呢?(图2).端点1,2就是P0,P1,控制点1,2就叫做P2,P3。那位大神就通过这两个控制点来控制妹子某些不可描述的部位想变大就变大,想平就平,这基本就是他的原理了。
图1
图2
一阶贝塞尔曲线(如上图1):
给定点P0、P1,线性贝兹曲线只是一条两点之间的直线。这条线由下式给出:
运用到了物理知识的S=V*T,具体参照贝塞尔详细推导过程。
二阶贝塞尔曲线公式:
贝塞尔曲线公式
三阶贝塞尔公式(如图2):
通用公式:
公式推导:
各阶贝塞尔曲线详细推导过程
如果看不懂也没关系,就像我们知道1+1=2就行了,剩下的交给伟大的数学家,我们只需运用而不是重构公式。
以二阶贝塞尔曲线公式为例:
图3
图4
运用到了贝塞尔曲线的递推定理:
公式中P0,P2是固定点,P1是控制点。有这两个定理可以推算出塞尔曲线公式。下图是三阶的递推过程。
只要套用上面的递推方法也就得到了三阶贝塞尔曲线的公式。
解释一下(图2为例):
P0代表头部(始端点),P1代表女性的前凸点(控制点1),P2代表后翘点(控制点2),P3代表脚部(末端点)。始末端点都是鼠标画的点,而控制点是计算出来的点,比如头和脚我们都有吧,但是前凸点和后翘点是靠后天发育的,也可以去做手术得来的,就是说人为造成的,而控制点就是人为造出来的参考点。如果天生丽质的话也是不可否认的,毕竟大师还是很多的。控制点怎么造出来呢,看下面!!!
控制点的确定原理
第一种控制点生成原理的公式是:
ControlA[i]=points[i]+(points[i+1]-points[i-1])*ControlAScale;
ControlB[i]=points[i+1]+(points[i+2]-points[i])*ControlBScale;
这个公式的生成原理我也不是很清楚,我觉得是求基于points[i]的偏移量得到的控制点,我没在数学里找到这个公式,还劳烦数学大神给我解答一下,大家不理解也可以去转载文章里看看,我就不多言了。
void CscrollView::SourcePointsMid(CArray<CPoint> & SourcePoints, CArray<CPoint> &MidPoints)//求源点的中点
{
CPoint midp;
int size = SourcePoints.GetSize();
for (size_t i = 0; i < size; i++)
{
int next = (i + 1) % size;
midp.x = (SourcePoints[i].x + SourcePoints[next].x) / 2.0;
midp.y = (SourcePoints[i].y + SourcePoints[next].y) / 2.0;
MidPoints.Add(midp);
}
}
/*
获取控制点A,B点。
*/
void CscrollView::BezierControlAB(CArray<CPoint> &points, Control *ControlA,
Control *ControlB,double ControlAScale, double ControlBScale)
{
//控制点公式:ControlA[i]=points[i]+(points[i+1]-points[i-1])*ControlAScale;
// ControlB[i]=points[i+1]-(points[i+2]-points[i])*ControlBScale;
int size = points.GetSize();
if (m_ABType == 0)//m_ABType == 0是第一种控制点生成
{
ControlA->x[0] = ((double)points[0].x) + (((double)points[1].x) - ((double)points[size - 1].x))*ControlAScale;
ControlA->y[0] = ((double)points[0].y) + (((double)points[1].y) - ((double)points[size - 1].y))*ControlAScale;
ControlA->x[size - 1] = ((double)points[size - 1].x) + (((double)points[0].x) - ((double)points[size - 2].x))*ControlAScale;
ControlA->y[size - 1] = ((double)points[size - 1].y) + (((double)points[0].y) - ((double)points[size - 2].y))*ControlAScale;
ControlB->x[size - 1] = ((double)points[0].x) - (((double)points[1].x) - ((double)points[size - 1].x))*ControlBScale;
ControlB->y[size - 1] = ((double)points[0].y) - (((double)points[1].y) - ((double)points[size - 1].y))*ControlBScale;
ControlB->x[size - 2] = ((double)points[size - 1].x) - (((double)points[0].x) - ((double)points[size - 2].x))*ControlBScale;
ControlB->y[size - 2] = ((double)points[size - 1].y) - (((double)points[0].y) - ((double)points[size - 2].y))*ControlBScale;
for (size_t i = 1; i < size - 1; i++)
{
ControlA->x[i] = ((double)points[i].x) + (((double)points[i + 1].x) - ((double)points[i - 1].x))*ControlAScale;
ControlA->y[i] = ((double)points[i].y) + (((double)points[i + 1].y) - ((double)points[i - 1].y))*ControlAScale;
}
for (size_t i = 0; i < size - 2; i++)
{
ControlB->x[i] = ((double)points[i + 1].x) - (((double)points[i + 2].x) - ((double)points[i].x))*ControlBScale;
ControlB->y[i] = ((double)points[i + 1].y) - (((double)points[i + 2].y) - ((double)points[i].y))*ControlBScale;
}
}
else//m_ABType != 0第二种控制点生成方式
{
//生成原理
//http://blog.sina.com.cn/s/blog_c2f85f910101e5sk.html
CArray<CPoint> MidPoints;
SourcePointsMid(points, MidPoints);
for (size_t i = 0; i < size; i++)
{
int preindex = (i + size - 1) % size;
int nextindex = (i + 1) % size;
CPoint MidMidPoint;
MidMidPoint.x = (MidPoints[i].x + MidPoints[preindex].x) / 2;
MidMidPoint.y = (MidPoints[i].y + MidPoints[preindex].y) / 2;
int OffseX = points[i].x - MidMidPoint.x;
int OffseY = points[i].y - MidMidPoint.y;
ControlA->x[i] = MidPoints[preindex].x + OffseX;
ControlA->y[i] = MidPoints[preindex].y + OffseY;
double OffseScaleX = ((double)(ControlA->x[i] - points[i].x))*ControlAScale;
double OffseScaleY = ((double)(ControlA->y[i] - points[i].y))*ControlAScale;
ControlA->x[i] = (double)points[i].x + OffseScaleX;
ControlA->y[i] = (double)points[i].y + OffseScaleY;
ControlB->x[i] = MidPoints[i].x + OffseX;
ControlB->y[i] = MidPoints[i].y + OffseY;
OffseScaleX = ((double)(ControlB->x[i] - points[i].x))*ControlBScale;
OffseScaleY = ((double)(ControlB->y[i] - points[i].y))*ControlBScale;
ControlB->x[i] = (double)points[i].x + OffseScaleX;
ControlB->y[i] = (double)points[i].y + OffseScaleY;
}
}
}
/*
三阶贝塞尔公式直接套用即可。
points:鼠标点击生成的坐标点,也称原始点,锚点
BezierCurveP:要接收的曲线坐标点
ControlA:控制点A
ControlB:控制点B
ControlA,ControlB是BezierControlAB函数中的ControlA,ControlB
*/
void CscrollView::BezierCurve(CArray<CPoint> &points, CArray<CPoint> *BezierCurveP,
Control *ControlA, Control *ControlB)
{
CPoint pt;
BezierCurveP->RemoveAll();
int size = points.GetSize();
int index = 0;
for (size_t i = 0; i < points.GetSize() - 1; i++)
{
index = i;
if (m_ABType == 1)//如果是第二种控制点生成方式
{
index = i + 1;
if (index == size)
{
index = 0;
}
}
for (int j = 0; j < 100; j++)
{
double t = j / 100.0, x, y;
x = ((double)points[i].x)*(1.0 - t)*(1.0 - t)*(1.0 - t)
+ 3 * ControlB->x[i] *t*(1.0 - t)*(1.0 - t)
+ 3 * ControlA->x[index] *t*t*(1.0 - t)
+ ((double)points[i+1].x) * t*t*t;
y = ((double)points[i].y)*(1.0 - t)*(1.0 - t)*(1.0 - t)
+ 3 * ControlB->y[i]*t*(1.0 - t)*(1.0 - t)
+ 3 * ControlA->y[index]*t*t*(1.0 - t)
+ ((double)points[i+1].y) * t*t*t;
pt = CPoint(x, y);
BezierCurveP->Add(pt);
}
}
}
源代码
链接:百度云完整代码
提取码:r8ly
编译环境:VS2017
源码详情:
VC++代码,包含了窗口缩放技术,两种贝塞尔曲线生成原理。
以后会对曲线进行拉伸处理,以及基本的绘图操作。谢谢!!以后会出B样条曲线的代码以及原理,请各位多多指教。