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)
spline.cpp // 样条曲线 paintWindow.cpp //绘制窗口 window.cpp //主窗口 main.cpp //入口 |
#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
#ifndef PAINTWINDOW_H
#define PAINTWINDOW_H
#include"spline.h"
#include
#include
class QTimer;
class paintWindow : public QWidget
{
Q_OBJECT
private:
spline* s;
std::vectorvec;
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
#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
#include "spline.h"
#include
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-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; ix,k0->x,k1->x,k2->x);
init(1,i,kml->y,k0->y,k1->y,k2->y);
for (j = 0; jx = 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
//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;
}
#include"paintWindow.h"
#include
#include
#include
#include
#include
#include
#include
//可以用累加法
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(slen && stotalLen||len<0){
time = 0;
x1 = y1 = x2 = y2 = -1;
timer->stop();
return;
}
time ++;
int sec = getSec(len);
for(int i=0;igetU(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;igetLen(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;isize();
}
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;igetX(i),s->getY(i),
s->getX(i+1),s->getY(i+1));
}
for(int i=0;ipos().x();
float y = e->pos().y();
point p;
p.setPoint(x,y);
vec.push_back(p);
update();
}
#include"window.h"
#include
#include
#include
#include
#include
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();
}
#include "window.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
window w;
w.show();
return a.exec();
}