作业一Readme
1. 实现大致框架,实现下图继承关系的几个类并给对应类写好空过程
继承关系各类继承关系如下所示
Spline
class Spline
{
public:
Spline();
~Spline();
// 用于画图的FOR VISUALIZATION
virtual void Paint(ArgParser *args);
virtual void addControlPoint(int pt, float x, float y);
virtual void set(int i, Vec3f v);
// 用于实现样条类型转换的FOR CONVERTING BETWEEN SPLINE TYPES
virtual void OutputBezier(FILE *file) {};
virtual void OutputBSpline(FILE *file) {};
// 用于得到控制点的FOR CONTROL POINT PICKING
virtual int getNumVertices();
virtual Vec3f getVertex(int i);
// 用于编辑操作的FOR EDITING OPERATIONS
virtual void moveControlPoint(int selectedPoint, float x, float y);
virtual void ddControlPoint(int selectedPoint, float x, float y);
virtual void deleteControlPoint(int selectedPoint);
// 用于产生三角形的FOR GENERATING TRIANGLES
virtual TriangleMesh* OutputTriangles(ArgParser *args);
/////////
};
BezierCurve
class BezierCurve:public Curve
{
public:
BezierCurve();
BezierCurve(int array);
void Paint(ArgParser *args);//绘制函数
void GetCnk(GLint n, GLint* c);//获取参数1,3,3,1
void GetPointPr(GLint *c, GLfloat t, Point3D *Pt, int ControlN, Point3D *ControlP);//获取当前点的坐标
void OutputBSpline(FILE *file);
void OutputBezier(FILE *file);
void addControlPoint(int pt, float x, float y);
Point3D* caluate(Point3D * pt);//传入四个点,传出四个点
TriangleMesh* OutputTriangles(ArgParser *args);
~BezierCurve();
};
BSplineCurve
{
public:
BSplineCurve();
BSplineCurve(int array);
void Paint(ArgParser *args);
void GetPointPr(GLfloat t,Point3D& Pt, int ControlN, Point3D* ControlP);
void addControlPoint(int pt, float x, float y);
void OutputBSpline(FILE *file);
void OutputBezier(FILE *file);
TriangleMesh* OutputTriangles(ArgParser *args);
Point3D * caluate(Point3D * pt);
~BSplineCurve();
};
SurfaceOfRevolution
class SurfaceOfRevolution : public Surface
{
private:
Curve * myCurve;
public:
SurfaceOfRevolution();
SurfaceOfRevolution(Curve* curve);
~SurfaceOfRevolution();
virtual void OutputBezier(FILE *file);
virtual void OutputBSpline(FILE *file);
void moveControlPoint(int selectedPoint, float x, float y);
void addControlPoint(int pt, float x, float y);
void deleteControlPoint(int selectedPoint);
void Paint(ArgParser *args);//绘制函数
int getNumVertices();
Vec3f getVertex(int i);
TriangleMesh* OutputTriangles(ArgParser *args);
};
BzeierPatch
class BezierPatch :
public Surface
{
Point3D Point[16];
public:
BezierPatch();
virtual void set(int c, Vec3f v);
~BezierPatch();
void Paint(ArgParser *args);//绘制函数
void GetCnk(GLint n, GLint* c);//获取参数1,3,3,1
void GetPointPr(GLint *c, GLfloat t, Point3D *Pt, int ControlN, Point3D *ControlP);//获取当前点的坐标
void OutputBSpline(FILE *file);
void OutputBezier(FILE *file);
GLfloat** mxn(GLfloat ** m1, GLfloat ** m2, int m, int n, int p);
Point3D* caluate(Point3D * pt);//传入四个点,传出四个点
TriangleMesh* OutputTriangles(ArgParser *args);
};
1. 遇到的问题
vs2017版本不兼容问题
添加链接目录右键工程->属性->常规->附加包含目录
修改成刚刚的工程目录
然后将头文件与源文件拷贝到工程中,就完成了第一步
2. 实现Bezier 与 BSpline曲线的绘制
实现思路:由于首先让我们绘制的是四个控制点的Bezier与BSpline的曲线,但是这里看了一下接下来的题目,对于接下来的多Bezier与多BSpline控制点进行了兼容。
对于Bezier曲线来说曲线的生成首先指定其控制点,然后根据其控制点的坐标与Bezier矩阵进行矩阵相乘,得出一个曲线对应公式,然后根据传进来的参数值进行曲线分割。
Bezier 绘制
首先在set函数里面保存控制点信息
void Curve::set(int i, Vec3f v)
{
GLfloat x = 0.0, y = 0.0, z = 0.0;
v.Get(x,y,z);
Point3D PointADD;
PointADD.x = x;
PointADD.y = y;
PointADD.z = z;
Point.push_back(PointADD);
num++;
}
由于Curve是Bezier与BSpline的父类,所以对于两者实现相同的函数都写在Curve中。这里的Point3D是自定义的数据类型,与文件中的Vec3f保留数据相同,都是三个GLfloat数据点
实现Paint函数
void BezierCurve::Paint(ArgParser *args) {
Vec3f myVec[4];
for (int n = 0; n < (num-1)/3; n++) {
for (int four = 0; four < 4; four++) {
myVec[four].Set( Point[n * 3 + four].x, Point[n * 3 + four].y, Point[n * 3 + four].z);
}
glPointSize(5);
glLineWidth(5);
glBegin(GL_LINES);
glColor3f(0, 0, 1);
GLfloat x[4] = { 0,0,0,0 };
GLfloat y[4] = { 0,0,0,0 };
GLfloat z[4] = { 0,0,0,0 };
for (int i = 0; i < 4; i++) {
myVec[i].Get(x[i], y[i], z[i]);
}
for (int i = 0; i < 3; i++) {
glVertex3f(x[i], y[i], z[i]);
glVertex3f(x[i + 1], y[i + 1], z[i + 1]);
}
glEnd();
glColor3f(0.0, 1, 0.0);
GLint *c;
Point3D Pt(0, 0, 0);
GLint ControlN = 4;
c = new GLint[ControlN];
GetCnk(ControlN - 1, c);
Point3D* ControlP = new Point3D[4];
for (int i = 0; i < 4; i++) {
ControlP[i].x = x[i];
ControlP[i].y = y[i];
ControlP[i].z = z[i];
}
glBegin(GL_LINES);
for (int i = 0; i <args->curve_tessellation; i++) {
GetPointPr(c, (GLfloat)i / (GLfloat)args->curve_tessellation, &Pt, 4, ControlP);
glVertex3f(Pt.x, Pt.y, Pt.z);
GetPointPr(c, (GLfloat)(i + 1) / (GLfloat)args->curve_tessellation, &Pt, 4, ControlP);
glVertex3f(Pt.x, Pt.y, Pt.z);
}
glEnd();
glColor3f(1, 0, 0);
glBegin(GL_POINTS);
for (int i = 0; i < 4; i++) {
glVertex3f(x[i], y[i], z[i]);
}
glEnd();
delete[] ControlP;
}
}
核心就是绘制曲线部分,通过获取四个控制点,与Bezier控制点相乘,得到一个曲线公式,利用杨辉三角求出(1-t)与(t)的参数关系,这里由于是四个控制点所以是1331,最后通过指定由t段直线描绘曲线来绘制t次直线,从而得到曲线。
BSpline Paint
BSpline曲线与Bzier曲线生成几乎相同,要注意的只有一点,就是Bezier的多点绘制方式是1234\2345\3456这种一次向后推移一位的绘制方式,除此之外就没有什么区别了,利用BSpline矩阵与四个控制点生成对应的曲线,然后根据传进来的参数确定曲线上近似点的位置,最后绘制出曲线。
效果图
Bezier
Bezier
BSpline
BSpline
3.Bezier与BSpline的转换
根据公式G*Bezier T = G BSpline * T
假设T相等
要求G(BSpline),则公式变成
G x Bezeir x BSpline﹣¹
由于G已知,Bezier 已知,只要求出BSpline的逆矩阵就可得到对应的控制点。
这里求逆用的是伴随矩阵与行列式求逆
给出求伴随矩阵的代码。通过两层循环得到每一行的每一个先行数,剩下两层循环用于求出剩下3乘3矩阵对应的行列式的值
//求伴随矩阵
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
for (int x = 0; x < 4; x++) {
for (int y = 0; y < 4; y++) {
if (x == i || y == j) {
continue;
}
else {
small[x > i ? x - 1 : x][y > j ? y - 1 : y] = BSplineNumber[x][y];
}
}
}
GLfloat addNum = small[0][0] * small[1][1] * small[2][2] + small[0][1] * small[1][2] * small[2][0] +
small[0][2] * small[1][0] * small[2][1];
GLfloat jianNum = small[0][0] * small[1][2] * small[2][1] + small[0][1] * small[1][0] * small[2][2] + small[0][2] * small[1][1] * small[2][0];
GLfloat help = (addNum - jianNum);
A_[j][i] = pow(-1, j + i)*help;
}
}
最后通过矩阵相乘得到最后的点矩阵,给出矩阵相乘代码
GLfloat** Curve::mxn(GLfloat ** m1, GLfloat ** m2, int m, int n, int p)//矩阵相乘
{
GLfloat **result = new GLfloat*[m];
for (int i = 0; i < m; i++) {
result[i] = new GLfloat[p];
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < p; j++) {
result[i][j] = 0;
for (int z = 0; z < n; z++) {
result[i][j] += m1[i][z] * m2[z][j];
}
}
}
return result;
}
最后实现写入文件就完成了第三部分任务
写入文件就是根据老师的文件规格写入对应的控制点的数据就行。
这里给出Bezier转BSpline的写入
这里的caluatef返回对应的BSpline的四个控制点
void BezierCurve::OutputBSpline(FILE * file)
{
fprintf(file, "%s %s ", "bspline", "num_vertices");
fprintf(file, "%d ", 4);
Point3D* pt=caluate(&Point[0]);
for (int i = 0; i < 4; i++) {
fprintf(file, "%f %f %f ", (float)pt[i].x, (float)pt[i].y, (float)pt[i].z);
}
}
效果图
Bezier转BSpline BSpline转Bezier4. 实现多点曲线绘制
由于在之前实现了对多点的兼容,所以就不副贴代码,对于之前的Paint函数的相关部分做一展示
for (int n = 0; n < (num-1)/3; n++) {
for (int four = 0; four < 4; four++) {
myVec[four].Set( Point[n * 3 + four].x, Point[n * 3 + four].y, Point[n * 3 + four].z);
}
Bezier是1234、4567、789 10 进行控制点复制,所以初始点坐标n = (num-1)/3
每次根据这个控制点获取四个控制点,然后与初始只有四个点的绘制过程相同,最后绘制出对应的多段曲线
BSpline对应的控制点公式为n = num - 3;
效果图
image.pngimage.png
image.png
5. 实现控制点编辑功能
实现思路:通过点击选定对应控制点,利用向量存储控制点
添加点:在选定线上与点击位置求垂足,确定垂足坐标,插入到向量中形成这条线的两点中间,然后调用重绘函数,重新绘制
void BSplineCurve::addControlPoint(int pt, float x, float y)
{
Point3D newPoint(x, y, 0);
Point.insert(Point.begin() + pt, newPoint);
num++;
}
删除点:删除对应选重点即在控制点向量中删除对应点坐标,也就是调用erase删除指定下标的向量。
void Curve::deleteControlPoint(int selectedPoint)
{
if (num > 4) {
Point.erase(Point.begin() + selectedPoint);
num--;
}
}
移动点:鼠标点击选定,记录坐标覆盖对应控制点坐标位置,并在拖动中保持重绘
void Curve::moveControlPoint(int selectedPoint, float x, float y)
{
Point[selectedPoint].x = x;
Point[selectedPoint].y = y;
}
效果图(由于Bezier不可添加控制点,这里采用BSpline演示)
移动点添加点
6.实现旋转曲线的绘制
实现思路:在SurfaceOfRevolution中实现绘制函数,在Paint函数中调用对应的Curve子类绘制函数,同时实现各种包括添加点,移动点,删除点的函数,仅仅通过初始化的时候传进来的Curve指针就可以调用Curve对应的函数,SurfaceOfRevolution仅仅只是调用一下。
关键:实现OutputTriangles,初始化TriangleNet,第一个参数是所画的笔数,第二个参数是画几笔,框架会自动连接好每一笔形成一个几何体
利用Bezier或者BSpline对应的计算曲线函数求出当前曲线在空间中的对应坐标,然后通过指定的描述曲面个数来确定每次绕y轴旋转的角度,最后将每次求出来的点的坐标绕轴旋转对应次数,并且使用SetVertex存入即可
TriangleMesh * BezierPatch::OutputTriangles(ArgParser * args)
{
//获取点的个数与分割个数
int patch_tessellation = args->patch_tessellation;
int num_vertices = 16;
//获取面的分割个数
int _v_tess = patch_tessellation;
Vec3f** myVec3f = new Vec3f*[patch_tessellation];
for (int i = 0; i < patch_tessellation; i++) {
myVec3f[i] = new Vec3f[patch_tessellation];
}
Vec3f *helpVec = new Vec3f[4 * patch_tessellation];
TriangleNet *myMesh = new TriangleNet(patch_tessellation, _v_tess);
GLint *c;
Point3D* Pt = new Point3D;
GLint ControlN = 4;
c = new GLint[ControlN];
GetCnk(ControlN - 1, c);
Point3D* ControlP = new Point3D[4];
for (int i = 0; i < 4; i++) {
for (int n = 0; n < 4; n++) {
ControlP[n].x = Point[i*4 + n].x;
ControlP[n].y = Point[i*4 + n].y;
ControlP[n].z = Point[i*4 + n].z;
}
for (int j = 0; j < patch_tessellation; j++) {
GetPointPr(c, (GLfloat)j / (GLfloat)patch_tessellation, Pt, 4, ControlP);
helpVec[i*patch_tessellation+j].Set(Pt->x, Pt->y, Pt->z);
}
}
for (int i = 0; i < patch_tessellation; i++) {
for (int n = 0; n < 4; n++) {
helpVec[i + patch_tessellation * n].Get(ControlP[n].x, ControlP[n].y, ControlP[n].z);
}
for (int j = 0; j < patch_tessellation; j++) {
GetPointPr(c, (GLfloat)j / (GLfloat)patch_tessellation, Pt, 4, ControlP);
myVec3f[i][j].Set(Pt->x, Pt->y, Pt->z);
}
}
for (int i = 0; i < patch_tessellation*patch_tessellation; i++) {
myMesh->SetVertex(i / patch_tessellation, i%patch_tessellation, myVec3f[i / patch_tessellation][i%patch_tessellation]);
}
for (int i = 0; i < patch_tessellation; i++) {
delete[] myVec3f[i];
}
delete[] myVec3f;
return myMesh;
}
效果图
image.png image.png7. 实现自定义绘制
实现思路:由于在surfaceOfRevolution中实现了对控制点的各种点击事件,所以就可以对传进来的曲线进行自定义的移动,然后保存到obj文件中,最后可以绘制出自定义的图形
由于各种点击触发的响应事件已经在Curve中实现过了,所以SurfaceOfRevolution中仅仅只需要调用一下就可以了
void SurfaceOfRevolution::OutputBezier(FILE * file)
{
myCurve->OutputBezier(file);
}
void SurfaceOfRevolution::OutputBSpline(FILE * file)
{
myCurve->OutputBSpline(file);
}
void SurfaceOfRevolution::moveControlPoint(int selectedPoint, float x, float y)
{
myCurve->moveControlPoint(selectedPoint, x, y);
}
void SurfaceOfRevolution::addControlPoint(int pt, float x, float y)
{
myCurve->addControlPoint(pt, x, y);
}
void SurfaceOfRevolution::deleteControlPoint(int selectedPoint)
{
myCurve->deleteControlPoint(selectedPoint);
}
void SurfaceOfRevolution::Paint(ArgParser * args)
{
myCurve->Paint(args);
}
int SurfaceOfRevolution::getNumVertices()
{
return myCurve->getNumVertices();
}
Vec3f SurfaceOfRevolution::getVertex(int i)
{
return myCurve->getVertex(i);
}
TriangleMesh * SurfaceOfRevolution::OutputTriangles(ArgParser * args)
{
TriangleMesh *myTriangleMesh;
myTriangleMesh=myCurve->OutputTriangles(args);
return myTriangleMesh;
}
效果图
罐子罐子
8. 实现4乘4格网绘制曲面
实现思路:
- 声明16个大小的控制点数组,装入控制点
- 读入要求绘制的曲线层数,初始化对应的TriangleNet,第一个参数为一条曲线所需要的点的个数,第二个参数为曲线的条数
- 利用Bezier曲线公式求出对应点的坐标
- 通过TriangleNet的SetVertex函数将求出点的坐标保存
- 返回TriangleNet
具体实现函数
//获取点的个数与分割个数
int patch_tessellation = args->patch_tessellation;
int num_vertices = 16;
//获取面的分割个数
int _v_tess = patch_tessellation;
Vec3f** myVec3f = new Vec3f*[patch_tessellation];
for (int i = 0; i < patch_tessellation; i++) {
myVec3f[i] = new Vec3f[patch_tessellation];
}
Vec3f *helpVec = new Vec3f[4 * patch_tessellation];
TriangleNet *myMesh = new TriangleNet(patch_tessellation, _v_tess);
GLint *c;
Point3D* Pt = new Point3D;
GLint ControlN = 4;
c = new GLint[ControlN];
GetCnk(ControlN - 1, c);
Point3D* ControlP = new Point3D[4];
for (int i = 0; i < 4; i++) {
for (int n = 0; n < 4; n++) {
ControlP[n].x = Point[i*4 + n].x;
ControlP[n].y = Point[i*4 + n].y;
ControlP[n].z = Point[i*4 + n].z;
}
for (int j = 0; j < patch_tessellation; j++) {
GetPointPr(c, (GLfloat)j / (GLfloat)patch_tessellation, Pt, 4, ControlP);
helpVec[i*patch_tessellation+j].Set(Pt->x, Pt->y, Pt->z);
}
}
for (int i = 0; i < patch_tessellation; i++) {
for (int n = 0; n < 4; n++) {
helpVec[i + patch_tessellation * n].Get(ControlP[n].x, ControlP[n].y, ControlP[n].z);
}
for (int j = 0; j < patch_tessellation; j++) {
GetPointPr(c, (GLfloat)j / (GLfloat)patch_tessellation, Pt, 4, ControlP);
myVec3f[i][j].Set(Pt->x, Pt->y, Pt->z);
}
}
for (int i = 0; i < patch_tessellation*patch_tessellation; i++) {
myMesh->SetVertex(i / patch_tessellation, i%patch_tessellation, myVec3f[i / patch_tessellation][i%patch_tessellation]);
}
for (int i = 0; i < patch_tessellation; i++) {
delete[] myVec3f[i];
}
delete[] myVec3f;
return myMesh;
效果图
image.png image.png9. 绘制茶壶
茶壶函数已经写好,调用一下绘制保存就行了
image.png
image.png
至此,完成作业要求。