抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

课程名称:计算机动画
实验项目名称:路径曲线与运动物体控制
实验日期:2020 年 9 月 27 日

一、 实验目的和要求

  1. 设计并实现一个路径曲线,通过不同参数控制曲线状态,并实现对物体沿生成路线运动的控制。
  2. 通过上述实验内容,了解动画动态控制的基本原理何方法,提高动画编程能力。

二、 实验内容和原理

  1. 选用 Cardinal 曲线表示运动路径,掌握它的表示和算法,了解不同控制参数对曲线形状和状态的影响。
  2. 编写代码实现 Cardinal 曲线算法,对照 cardinal 样条曲线的数学表示和程序之间的对应关系。
  3. 给定若干关键控制点的位置(这些控制点可以大致描述某个运动路径的形状),用上述程序计算出控制点之间的插值点,显示出样条曲线。
  4. 改变曲线弯曲程度的参数$τ∈[0,1]$大小和控制插值点数目的参数 grain ,观察曲线形状的变化。
  5. 在路径曲线上放置一小汽车,使其在沿生成的 cardinal 曲线运动,汽车速度和加速度可以调节。

三、 实验平台

Qt 5.14.2 @ Windows

四、 实验步骤

1. 首先,对照Cardinal 样条曲线的数学表达和程序中计算代码的对应关系。

Cardinal 样条曲线矩阵表示:
$$$ P(u) = U^T M B $$$
其中,u 是幂次最高为 3 的插值变量,且u∈[0,1], M 是 Hermite 多项式矩阵,B 是曲线中,用户指定的关键点数据。其矩阵展开表示。
图片alt

其中,$P_i-1,P_i,P_i+1,P_i+2$,是用户指定的控制点控制点,参数τ 控制曲线的弯曲程度。
为了实现 Cardinal 样条曲线计算,创建 spline 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class spline
{
private:
double *ax,*bx,*cx,*dx;//P(u)系数
double *ay,*by,*cy,*dy;//P(u)系数
double *A,*B,*C,*D,*E;//计算弧长所用系数
double* matrix[4];//计算矩阵
double tension;//参数τ
int num;//关键点个数
int grain;//每两个关键点之间插值点的个数(含关键点)
bool create_flag=false;//是否已经为指针分配空间(判断是否需要delete)
vector<QPoint> all_points;//所有点

public:
spline();
//生成CubicSpline曲线
void set_Spline(vector<QPoint>& vec,int _grain,double _tension);
//计算生成的三次样条曲线上所有插值点
void CubicSpline(vector<QPoint>& vec);
double calc_Total_length(); //计算曲线总长度


void init_Matrix(); //初始化矩阵
void init_spline_Coefficient(vector<QPoint>& vec);//计算P(u)系数

QPoint calc_Interpolation(int i,double u);//计算内部插值点
point calc_double_Interpolation(int i,double u);//计算内部插值点(坐标为double类型)

vector<QPoint>& get_all_points(); //返回储存所有插值点的vector

void init_length_Coefficient(int _num);//初始化长度计算参数
double f(int i,double u); //f函数
double simpson(int i,double a,double b);//求样条曲线长度
double calc_U(double s,int i,double u1,double u2);//根据长度计算参数u的值
void clear();//清除数据

~spline(){}
};

其中 set_Spline 函数根据关键点数组 vec、插值点数目 _grain、和控制曲线弯曲程度的参数 _tension 生成样条曲线所需要的计算数据
(如矩阵数值,P(u)系数等)。具体算法见源码。

其中init_Matrix 函数计算矩阵中的数值,即为 P(u)公式中的矩阵M。init_spline_Coefficient 函数计算不同曲线段中的P(u)多项式系数,即为公式中的 M*B(分 x,y 两个方向计算)。具体算法见源码。

2. CubicSpline 函数.

根据_grain 值生成不同的u 值,并计算曲线上所有插值点的坐标,储存在名为 all_points 的 vector 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//计算曲线上所有插值点
void spline::CubicSpline(vector<QPoint>& vec)
{
//当没清除就再次点击生成曲线时,不清除之前的插值点,插入间隔点
QPoint temp(0,0);
all_points.push_back(temp);

//根据设置的插值点个数参数,计算对应的u值
int num=vec.size();
double* u = new double[grain];
for (int i = 0; i<grain; i++) {
u[i] = ((double)i) / grain; //u [0,1]
}

//根据u值和曲线参数计算插值点坐标
for (int i = 0; i<num-1; i++) {
QPoint p1=vec[i];
//加入关键点
all_points.push_back(p1);
for (int j = 1; j<grain; j++) {
QPoint temp=calc_Interpolation(i,u[j]);
all_points.push_back(temp);
}
}
//加入关键点
QPoint p1=vec[num-1];
all_points.push_back(p1);

delete []u;
}

其中,calc_Interpolation 函数根据曲线段序号i,和参数 u 的不同值,计算具体一个插值点的坐标。

3. 编写 paintWindow 类作为画板,继承自 QWidget 类。

在paintWindow 类中编写鼠标回调函数mousePressEvent,记录通过鼠标交互选定的关键点。以及绘制函数 paintEvent,在每次update()时调用。paintWindow 类定义具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class paintWindow : public QWidget
{
Q_OBJECT
private:
spline* sp;
int grain; //每个曲线区间有多少个插值点(包括两端关键点)
double tension; //参数,控制曲线弯曲程度

bool ifDrawInpoint=false; //是否显示插值点

int time;//时间
QTimer* timer; //计时器
QPixmap* car[5]; //储存小车位图信息

int car_index;//显示第几种小车
int pen_index=0;//使用第几种笔刷

double speed; //小车速度
double accelerate; //小车加速度

point now_point; //运动当前点
point next_point; //运动下一个点
double ratio; //旋转角度

vector<QPoint> points;//储存所有关键点

bool endflag=false; //小车是否运动到曲线末端

public:
paintWindow();

void paintEvent(QPaintEvent *); //绘制函数
void mousePressEvent(QMouseEvent *e); //鼠标回调函数

void create_Spline(int _grain, double _tension); //生成并显示cubicspline曲线
void start_Move(double _speed,double _accelarate); //小车开始运动
void stop_Move(); //小车暂停运动
void continue_Move(); //小车继续运动

int numbers(); //关键点个数
double total_length(); //曲线总长度
double now_length(); //小车当前走过的路线长度
int get_spline_index(double now_len); //获取小车当前在哪一段曲线
double get_Ratio(); //获取当前曲线斜率

void change_car(); //改变小车
void change_pen(QPainter& paint); //改变笔刷
void change_DrawInPoint(); //改变是否绘制插值点的控制变量
QPixmap* now_car(); //当前小车位图指针

void clear();
private slots:
void changeState(); //连接计时器,改变小车坐标,旋转角度等信息
};

4.

鼠标回调函数mousePressEvent,记录通过鼠标交互选定的关键点。
绘制函数 paintEvent,根据数据变化,绘制所有的关键点、曲线,已经选择是否绘制插值点。

5. 引入QTimer 类作为计时器.

每隔一段时间调用 changestate 函数,改变小车坐标及旋转角度等。其中now_point 是当前小车位置,next_point 是下一个小车位置。get_Ratio 函数计算当下曲线的斜率,以及小车旋转角度。

6. MainWindow 设计及按钮槽函数

MainWindow 窗口设计如下:
图片alt

MainWindow 类设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
paintWindow* p_w;

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private slots:
void on_create_clicked();//绘制曲线并显示
void on_clear_clicked();//清屏
void on_start_clicked();//开始运动按钮槽函数
void on_show_clicked();//展示插值点按钮槽函数
void on_stop_move_clicked();//停止运动按钮槽函数
void on_continue_move_clicked();//继续运动按钮槽函数


void update_numbers();//更新关键点数目
void on_change_clicked();

private:
Ui::MainWindow *ui;
};

五、 实验结果分析

选取控制点:
图片alt

绘制曲线:同时显示关键点个数和路线总长度。
图片alt

不同参数对曲线值的影响:

1、不同的 grain:grain=5(红色)和 grain=60(蓝色)
图片alt

2、不同的tension(τ):分别为:
0(绿色),0.25(灰色),0.5(红色),0.75(蓝色),1.0(黄色)
图片alt

显示插值点(白色为内部插值点):
图片alt

小车开始运动:
图片alt

更换小车:
图片alt

评论