[计算机动画] 路径曲线与运动物体控制(Cardinal样条曲线)
2016-10-05 00:58
791 查看
在设计矢量图案的时候,我们常常需要用到曲线来表达物体造型,单纯用鼠标轨迹绘制显然是不足的。于是我们希望能够实现这样的方法:通过设计师手工选择控制点,再通过插值得到过控制点(或在附近)的一条平滑曲线。在这样的需求下,样条曲线诞生了。简而言之,样条曲线是由多个多项式按比例系数组成的多项式函数,而比例系数是由控制点决定的。
开发环境
Qt 5.7Hermite曲线
Hermite曲线从一个点到另一个点利用Hermite插值产生一个三次多项式。如果想要在两点之间得到Hermite曲线,我们还需要给出两个点处曲线的切线斜率。
Hermite曲线是由四个基函数组成的:
h1(u) = 2u^3 – 3u^2 + 1
h2(u)= -2u^3 + 3u^2
h3(u)= u^3 – 2u^2 + u
h4(u)= u^3 - u^2
写成矩阵形式,如下:
当然,我们需要注意到这仅仅是两个点之间的曲线。而对于多个点生成连续曲线的话,我们需要保证第一段曲线的末端和第二段曲线的初端,同时,斜率也要相等。可以看出,指明所有切线是比较麻烦的一件事,毕竟它不是像控制点一样那么直观的东西。
在这个基础上,我们考虑这样一个问题:能不能绕开切线,换句话说,让程序自动生成切线。
为了得到一个点的切线,我们需要利用到这个点前后两个点来辅助构造:
P’i=τ*( Pi+1
- Pi-1 ) (*)
上式中,τ最好在(0,1)之间取值,它的值越大,曲线就越弯曲,反之则越接近直线。
在这样一个想法下,我们把(*)式带入原矩阵,得到了Cardinal曲线的矩阵形式:
在τ = 1/2 时,曲线被称为CatMull曲线。
这样一来,一个曲线实际上是由四个点控制的,那么我们马上就能想到——最边界的点是没有邻居点的,所以为了算法正确运行,我们需要加入虚拟点。简单来说,我们可以直接把首部点复制作为首部虚拟点,尾部复制作为尾部虚拟点。
我们需要明确一点,我们最终得到的实际上是一个分段函数,每两个控制点之间为一段,一共有n + 2 个控制点(含虚拟点),和 n - 1条曲线。我们得到的曲线是参数方程,u是参数,而不是普通的函数表达式。
换言之,我们可以这样认识Cardinal曲线:
X(u,i) = Ax(i) * u^3 + Bx(i) * u^2 + Cx(i) * u + Dx(i)
Y(u,i) = Ay(i) * u^3 + By(i) * u^2 + Cy(i) * u + Dy(i)
其中,u是参数,它的取值在[0,1],i是段数,它的取值在[0,n-2],A,B,C,D是不同段数的系数,这是一个参数方程,也是三次多项式。
构建Cardinal曲线
为了生成Cardinal曲线,我们只需给出控制点,弯曲程度,插值点个数。1)得到控制点作为输入信息
2)在两端加入虚拟点
3)计算得到基矩阵M(见之前给出的矩阵)
4)计算出每段曲线的A,B,C,D系数(矩阵M中每一行与控制点列向量相乘对应一个系数)
5)根据插值点个数计算出每段曲线中,插值参数u的值,并计算出对应的X,Y值
6)把所有的插值点用直线连接起来
控制小车的速度
为了控制小车的速度,也就是要让小车在特定时间移动到特定的距离。为了计算距离,我们需要在给定弧长的情况下,得到对应的u值。
我们可以根据参数方程,得到曲线的弧长公式:
其中:
这里的ax,bx,cx,ay,by,cy和Cardinal曲线的系数是对应的。(忽略az,bz,cz,因为我们并没有考虑三维空间)
我们用数值分析的方法解决这个问题,也就是二分法:
简言之,就是先从u在[0,1]中开始,计算u=1/2处弧长,如果实际弧长大于1/2处,则在[1/2,1]中继续计算,否则在[0,1/2]中继续计算。按此递归下去,直到实际结果与预期的误差小于某一精度。
看起来很简单。但是需要考虑两个问题:
1)我们的曲线方程是分段曲线,所以我们首先要计算出每段曲线的长度,然后判断我们预期长度属于哪个片段,然后截取预期长度在该片段中的长度,再进入递归计算。
2)我们还需要实现根据参数u,计算对应弧长的方法。
后者本质上就是求一个积分(在前面已经给出了)我们可以采用simpson方法,将其展开成n个区间(偶数),然后求和。
spline.cpp // 样条曲线 paintWindow.cpp //绘制窗口 window.cpp //主窗口 main.cpp //入口 |
代码
(明天会加入注释)spline.h
#ifndef SPLINE_H #define SPLINE_H class point { public: float x; float y; void setPoint(float _x, float _y); }; class spline { private: float *a[2],*b[2],*c[2],*d[2];//每段Spline曲线的参数 float *A, *B, *C, *D, *E; float m[16];//矩阵M point* knots; point* Spline; int grain; int n; int count; float tension; void CubicSpline(); void initLength(); void GetCardinalMatrix(); float f(int j,float x); float Matrix(int i,int j,float u); void init(int i,int j,float a0, float b0, float c0, float d0); float simpson(int j,float x,float y); public: spline(point* p, int _n, int _grain, float _tension); ~spline(); void print(); float getX(int i); float getY(int i); float getXFromU(int i,float u); float getYFromU(int i,float u); float getLen(int i,float u); float getU(int i,float s,float u1,float u2); int size(); }; #endif // SPLINE_H
paintWindow.h
#ifndef PAINTWINDOW_H #define PAINTWINDOW_H #include"spline.h" #include<QWidget> #include<vector> class QTimer; class paintWindow : public QWidget { Q_OBJECT private: spline* s; std::vector<point>vec; int size; QTimer* timer; class car_t{ public: QPixmap* p[3]; float speed; float acce; float getLen(int t); }car; float totalLen; float radio; float x1,y1,x2,y2; bool isFirst; float* length; int index; int time; void setPoint(float x,float y); int getSec(float s); float getRatio(); float getRatio(int i,int j); public: paintWindow(QWidget *parent = 0); ~paintWindow(); void setSpline(int grain, float tension); void setCar(float speed,float acce); void clear(); float getTotalLen(); void startMove(); protected: void paintEvent(QPaintEvent *); void mousePressEvent(QMouseEvent *); private slots: void changeState(); }; #endif // WINDOW_H
window.h
#ifndef WINDOW_H #define WINDOW_H #include "paintWindow.h" class QPushButton; class QLabel; class QHBoxLayout; class QVBoxLayout; class QLineEdit; class window : public QWidget { Q_OBJECT private: QWidget* menuWindow; QHBoxLayout* hlayout[5]; QVBoxLayout* vlayout; paintWindow* w; QLineEdit* grainLine; QLineEdit* tensionLine; QLineEdit* speedLine; QLineEdit* acceLine; QLabel* lenLabel; QLabel* grainLabel; QLabel* tensionLabel; QLabel* speedLabel; QLabel* acceLabel; QPushButton* genButton; QPushButton* startButton; QPushButton* clearButton; void layout(); public: window(QWidget* parent = 0); private slots: void updatePaintWindow(); void clear(); void start(); }; #endif // WINDOW_H
spline.cpp
#include "spline.h" #include<math.h> void point::setPoint(float _x, float _y) { x = _x; y = _y; } void spline::initLength() { A = new float[n-1]; B = new float[n-1]; C = new float[n-1]; D = new float[n-1]; E = new float[n-1]; for(int i=0;i<n-1;i++){ A[i] = 9*(a[0][i]*a[0][i]+a[1][i]*a[1][i]); B[i] = 12*(a[0][i]*b[0][i]+a[1][i]*b[1][i]); C[i] = 6*(a[0][i]*c[0][i]+a[1][i]*c[1][i]) + 4*(b[0][i]*b[0][i]+b[1][i]*b[1][i]); D[i] = 4*(b[0][i]*c[0][i]+b[1][i]*c[1][i]); E[i] = c[0][i]*c[0][i]+c[1][i]*c[1][i]; } } float spline::f(int i,float x) { return sqrt(((((A[i]*x+B[i])*x)+C[i])*x+D[i])*x+E[i]); } float spline::simpson(int j,float x,float y) { const int n = 10; const float h = (y - x)/n; float ans = 0.0f; for(int i=1;i<=n-1;i++){ if(i%2){ ans += 4*f(j,x+1.0f*i/n*(y-x)); } else ans += 2*f(j,x+1.0f*i/n*(y-x)); } ans += f(j,x) + f(j,y); ans *= h/3; return ans; } //第i段,u float spline::getLen(int i,float u) { return simpson(i,0,u); } float spline::getU(int i,float s,float u1,float u2) { float ms = getLen(i,(u1+u2)/2); if(ms-s>-1.0f && ms-s<1.0f){ return (u1+u2)/2; } else if(ms > s)return getU(i,s,u1,(u1+u2)/2); else if(ms < s)return getU(i,s,(u1+u2)/2,u2); } spline::spline(point* p, int _n, int _grain, float _tension) { n = _n; grain = _grain; tension = _tension; knots = new point[n + 2]; for (int i = 1; i<=n; i++) { knots[i].x = p[i-1].x; knots[i].y = p[i-1].y; } knots[0].x = p[0].x; knots[0].y = p[0].y; knots[n + 1].x = p[n - 1].x; knots[n + 1].y = p[n - 1].y; Spline = new point[(n-1)* grain + 1]; a[0] = new float[n-1]; b[0] = new float[n-1]; c[0] = new float[n-1]; d[0] = new float[n-1]; a[1] = new float[n-1]; b[1] = new float[n-1]; c[1] = new float[n-1]; d[1] = new float[n-1]; count = 0; CubicSpline(); initLength(); } int spline::size() { return (n-1)*grain + 1; } float spline::getX(int i) { return Spline[i].x; } float spline::getY(int i) { return Spline[i].y; } void spline::CubicSpline() { point *s, *k0, *kml, *k1, *k2; int i, j; float* u = new float[grain]; GetCardinalMatrix(); for (i = 0; i<grain; i++) { u[i] = ((float)i) / grain;//u [0,1] } s = Spline; kml = knots; k0 = kml + 1; k1 = k0 + 1; k2 = k1 + 1; for (i = 0; i<n-1; i++) { init(0,i,kml->x,k0->x,k1->x,k2->x); init(1,i,kml->y,k0->y,k1->y,k2->y); for (j = 0; j<grain; j++) { s->x = Matrix(0, i, u[j]); s->y = Matrix(1, i, u[j]); s++; } k0++, kml++, k1++, k2++; } s->x = knots .x; s->y = knots .y; delete u; } void spline::print() { for (int i = 0; i < grain*(n-1)+1; i++) { if (i%grain == 0)printf("\n"); printf("%f %f\n", Spline[i].x, Spline[i].y); } } void spline::GetCardinalMatrix() { float a1 = tension; m[0] = -a1, m[1] = 2 - a1, m[2] = a1 - 2, m[3] = a1; m[4] = 2 * a1, m[5] = a1 - 3, m[6] = 3 - 2 * a1, m[7] = -a1; m[8] = -a1, m[9] = 0, m[10] = a1, m[11] = 0; m[12] = 0, m[13] = 1, m[14] = 0, m[15] = 0; } void spline::init(int i,int j,float a0, float b0, float c0, float d0) { a[i][j] = m[0] * a0 + m[1] * b0 + m[2] * c0 + m[3] * d0; b[i][j] = m[4] * a0 + m[5] * b0 + m[6] * c0 + m[7] * d0; c[i][j] = m[8] * a0 + m[9] * b0 + m[10] * c0 + m[11] * d0; d[i][j] = m[12] * a0 + m[13] * b0 + m[14] * c0 + m[15] * d0; } //i为0:X,i为1:Y //u float spline::Matrix(int i, int j,float u) { return(d[i][j] + u*(c[i][j] + u*(b[i][j] + u*a[i][j]))); } float spline::getXFromU(int i,float u) { return Matrix(0,i,u); } float spline::getYFromU(int i,float u) { return Matrix(1,i,u); } spline::~spline() { delete[] knots; delete[] Spline; }
paintWindow.cpp
#include"paintWindow.h" #include<QMouseEvent> #include<QPainter> #include<QPixmap> #include<paintWindow.h> #include<cmath> #include<QTimer> #include<qDebug> //可以用累加法 float paintWindow::car_t::getLen(int t) { return speed*t + acce*t*t/2; } paintWindow::paintWindow(QWidget *parent): QWidget(parent) { s = NULL; size = 0; isFirst = true; index = 0; time = 0; x1 = y1 = x2 = y2 = -1; car.p[0] = new QPixmap; car.p[1] = new QPixmap; car.p[2] = new QPixmap; car.p[0]->load("1.png"); car.p[1]->load("2.png"); car.p[2]->load("3.png"); timer = new QTimer(); connect(timer,SIGNAL(timeout()),this,SLOT(changeState())); } paintWindow::~paintWindow() { //delete[] p; } int paintWindow::getSec(float s) { float len = length[0]; if(s<len)return 0; for(int i=1;i<vec.size()-1;i++){ if(s>len && s<len+length[i]){ return i; } len += length[i]; } } void paintWindow::changeState() { float len = car.getLen(time); index = (index+1)%3; if(len>totalLen||len<0){ time = 0; x1 = y1 = x2 = y2 = -1; timer->stop(); return; } time ++; int sec = getSec(len); for(int i=0;i<sec;i++){ len -= length[i]; } float u = s->getU(sec,len,0,1); if(isFirst){ x1 = s->getXFromU(sec,u); y1 = s->getYFromU(sec,u); isFirst = false; } else{ x2 = s->getXFromU(sec,u); y2 = s->getYFromU(sec,u); isFirst = true; } update(); } void paintWindow::startMove() { index = 0; time = 0; timer->start(100);//fps:10 } float paintWindow::getTotalLen() { totalLen = 0.0f; for(int i=0;i<vec.size()-1;i++){ length[i] = s->getLen(i,1.0f); totalLen += length[i]; } return totalLen; } void paintWindow::setCar(float speed,float acce) { car.acce = acce; car.speed = speed; } void paintWindow::setSpline(int grain, float tension) { length = new float[vec.size()-1]; if(!timer->isActive())index = 0; if(!s)delete s; if(vec.size()==0)return; point* p = new point[vec.size()]; for(int i=0;i<vec.size();i++){ p[i].x = vec[i].x; p[i].y = vec[i].y; } s = new spline(p,vec.size(),grain,tension); size = s->size(); } void paintWindow::clear() { size = 0; vec.clear(); delete s; time = 0; index = 0; timer->stop(); x1 = y1 = x2 = y2 = -1; update(); } float paintWindow::getRatio(int i,int j) { const float pi = 3.14159; double tan = (s->getY(j)-s->getY(i))/(s->getX(j)-s->getX(i)); double theta = atan(tan); return theta/(2*pi)*360; } float paintWindow::getRatio() { const float pi = 3.14159; if((x2-x1)<0.01f&&(y2-y1)<0.01f){ return radio; } if(x2-x1==0)return 0; float tan = (y2-y1)/(x2-x1); float theta = atan(tan); return radio = theta/(2*pi)*360; } void paintWindow::paintEvent(QPaintEvent *) { QPainter paint(this); if(size>0){ float ratio; if(x1==-1||x2==-1||y1==-1||y2==-1){ ratio = (s->getY(1)-s->getY(0))/(s->getX(1)-s->getX(0)); } else ratio = getRatio(); if(isFirst)paint.translate(x1,y1); else paint.translate(x2,y2); paint.rotate(ratio); paint.drawPixmap(-90,-90,180,90,*car.p[index%3]); paint.rotate(-ratio); if(isFirst)paint.translate(-x1,-y1); else paint.translate(-x2,-y2); } paint.setBrush(QBrush(Qt::black,Qt::SolidPattern));//设置画刷形式 for(int i=0;i<size-1;i++){ paint.drawLine(s->getX(i),s->getY(i), s->getX(i+1),s->getY(i+1)); } for(int i=0;i<vec.size();i++){ paint.drawEllipse(vec[i].x,vec[i].y,5,5); } } void paintWindow::mousePressEvent(QMouseEvent *e) { float x = e->pos().x(); float y = e->pos().y(); point p; p.setPoint(x,y); vec.push_back(p); update(); }
window.cpp
#include"window.h" #include<QPushButton> #include<QLabel> #include<QHBoxLayout> #include<QVBoxLayout> #include<QLineEdit> window::window(QWidget* parent):QWidget(parent) { layout(); } void window::layout() { w = new paintWindow(); menuWindow = new QWidget(); grainLabel = new QLabel("grain"); tensionLabel = new QLabel("tension"); speedLabel = new QLabel("speed"); acceLabel = new QLabel("accelerate"); lenLabel = new QLabel("total length:"); genButton = new QPushButton("生成轨迹"); startButton = new QPushButton("开始运动"); clearButton = new QPushButton("清空"); startButton->setDisabled(true); grainLine = new QLineEdit(); tensionLine = new QLineEdit(); speedLine = new QLineEdit(); acceLine = new QLineEdit(); connect(genButton,SIGNAL(clicked()),this,SLOT(updatePaintWindow())); connect(clearButton,SIGNAL(clicked()),this,SLOT(clear())); connect(startButton,SIGNAL(clicked()),this,SLOT(start())); grainLine->setText("20"); tensionLine->setText("0.5"); speedLine->setText("10"); acceLine->setText("0"); for(int i=0;i<5;i++){ hlayout[i] = new QHBoxLayout; } vlayout = new QVBoxLayout; hlayout[0]->addWidget(grainLabel); hlayout[0]->addWidget(grainLine); hlayout[1]->addWidget(tensionLabel); hlayout[1]->addWidget(tensionLine); hlayout[3]->addWidget(speedLabel); hlayout[3]->addWidget(speedLine); hlayout[4]->addWidget(acceLabel); hlayout[4]->addWidget(acceLine); vlayout->addLayout(hlayout[0]); vlayout->addLayout(hlayout[1]); vlayout->addLayout(hlayout[3]); vlayout->addLayout(hlayout[4]); vlayout->addWidget(lenLabel); vlayout->addWidget(genButton); vlayout->addWidget(startButton); vlayout->addWidget(clearButton); menuWindow->setFixedWidth(200); menuWindow->setLayout(vlayout); resize(900,900); hlayout[2]->addWidget(menuWindow); hlayout[2]->addWidget(w); setLayout(hlayout[2]); } void window::updatePaintWindow() { startButton->setDisabled(false); w->setSpline(grainLine->text().toInt(),tensionLine->text().toFloat()); lenLabel->setText("total length:\n" + QString("%1").arg(w->getTotalLen())); w->update(); } void window::clear() { w->clear(); startButton->setDisabled(true); } void window::start() { w->setCar(speedLine->text().toFloat(),acceLine->text().toFloat()); w->startMove(); }
main.cpp
#include "window.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); window w; w.show(); return a.exec(); }
qwq不要用我的图呜哇
1.png
2.png
3.png
相关文章推荐
- 【计算机动画】实验 路径曲线与运动物体控制 设计
- 【计算机动画】实验 路径曲线与运动物体控制 报告
- 【计算机动画】路径控制(二)
- 【计算机动画】路径控制
- 物体运动--通过改变transform--键盘控制
- js动画---多物体运动
- 令物体沿着指定路径运动[as3版]
- 模拟真实物体运动的js动画库插件-Anima
- (六)计算机视觉的知识、CamShitf算法、运动分析和物体跟踪Video
- 起伏地形环境多机器人编队运动控制与路径规划研究_2016年中小结
- iOS 让物体进行曲线运动
- 贝塞尔曲线:如何让一个正在按照贝塞尔曲线运动的精灵减速或加速(动画结束回调)
- 起伏地形环境多机器人编队运动控制与路径规划研究_2016年中小结
- 通过刚体组件控制物体的运动
- AR/VR learning (3)--物体的运动与动画(iTween插件的使用)
- js动画(4)——多物体运动
- 在Vega Prime中设定物体的运动路径
- javascript动画、运动算法详细解释与分析 (三、Tween 运动算法:二次方曲线算法)
- Unity游戏开发的数学与物理 2 ( 通过键盘控制物体的运动 )
- Unity 控制摄像机跟随运动物体