[计算机动画] 路径曲线与运动物体控制(Cardinal样条曲线)
来源:互联网 发布:windows组件向导 编辑:程序博客网 时间:2024/05/29 04:26
在设计矢量图案的时候,我们常常需要用到曲线来表达物体造型,单纯用鼠标轨迹绘制显然是不足的。于是我们希望能够实现这样的方法:通过设计师手工选择控制点,再通过插值得到过控制点(或在附近)的一条平滑曲线。在这样的需求下,样条曲线诞生了。简而言之,样条曲线是由多个多项式按比例系数组成的多项式函数,而比例系数是由控制点决定的。
开发环境
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
写成矩阵形式,如下:
当然,我们需要注意到这仅仅是两个点之间的曲线。而对于多个点生成连续曲线的话,我们需要保证第一段曲线的末端和第二段曲线的初端,同时,斜率也要相等。可以看出,指明所有切线是比较麻烦的一件事,毕竟它不是像控制点一样那么直观的东西。
Cardinal曲线
在这个基础上,我们考虑这样一个问题:能不能绕开切线,换句话说,让程序自动生成切线。
为了得到一个点的切线,我们需要利用到这个点前后两个点来辅助构造:
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)
构建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_Hclass 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_OBJECTprivate: 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_OBJECTprivate: 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段,ufloat 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[n].x; s->y = knots[n].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//ufloat 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
- [计算机动画] 路径曲线与运动物体控制(Cardinal样条曲线)
- 【计算机动画】实验 路径曲线与运动物体控制 设计
- 【计算机动画】实验 路径曲线与运动物体控制 报告
- 计算机图形学 学习笔记(十一):曲线曲面(三):B样条 曲线与曲面
- cardinal曲线工具类
- 【UE4小白奋斗记录】BluePrint+样条曲线的路径动画(可循环)
- 采用Cardinal法构造插枝分段三次样条曲线 : 原理篇
- 采用Cardinal法构造插枝分段三次样条曲线 : 代码篇
- 采用Cardinal法构造插枝分段三次样条曲线 : 实战篇
- B-样条曲线:移动控制点
- 样条曲线反求控制点
- B样条曲线的控制
- C++编写任意次clampedB样条曲线(曲线分别与第一个控制点和最后一个控制点的第一边和最后一边相切)
- 动画与贝塞尔曲线
- iTween曲线动画(沿着轨迹运动)
- 三次样条曲线
- 样条曲线
- B样条曲线
- 分享一些做课题调查的方法
- AVL树( C++)
- [Android] ListView的优化
- MVC框架的封装(八)日志类
- OpenCV手写数字字符识别(基于k近邻算法)
- [计算机动画] 路径曲线与运动物体控制(Cardinal样条曲线)
- ubuntu16.04虚拟机免密码登录ssh
- 欢迎使用CSDN-markdown编辑器
- java初级三目运算运用
- Java中的JDBC驱动类型
- Connection 中的List和Set
- Codeforce 723E - One-Way Reform(欧拉回路*,构造)
- HDU 5918 kmp
- codeVS 3143 二叉树的序遍历