Assignment 8¶
这一次作业实现的内容是
- 样条曲线(包括BSpline和Bezier曲线)
- 基于两者的旋转曲面
- 贝塞尔曲面
Tasks¶
(1)这次作业的主要内容在于实现基类 Spline
以及相应的派生类,相应的抽象结构如下
基类 Spline
中包含了
// FOR VISUALIZATION
virtual void Spline::Paint(ArgParser *args);
// FOR CONVERTING BETWEEN SPLINE TYPES
virtual void Spline::OutputBezier(FILE *file);
virtual void Spline::OutputBSpline(FILE *file);
// FOR CONTROL POINT PICKING
virtual int Spline::getNumVertices();
virtual Vec3f Spline::getVertex(int i);
// FOR EDITING OPERATIONS
virtual void Spline::moveControlPoint(int selectedPoint, float x, float y);
virtual void Spline::addControlPoint(int selectedPoint, float x, float y);
virtual void Spline::deleteControlPoint(int selectedPoint);
// FOR GENERATING TRIANGLES
virtual TriangleMesh* Spline::OutputTriangles(ArgParser *args);
(2)实现派生类 curve
的 paint
函数,在OpenGL中画出相应的内容,包括了控制点,控制多边形以及相应的样条曲线
(3)然后实现 BSpline 与 Bezier curve 的转化,给其中一条曲线的控制点,然后输出另一条曲线的控制点,使得两组控制点生成的曲线相同
(4) 扩展样条的控制点数量,我们一开始实现的是4个控制点的,然后我们实现控制点的增减。对于贝塞尔曲线,我只允许 \(3n+1\) 个控制点,如果不是相应数量的控制点,那么只会使用前面相应 \(3n+1\) 个点。而对于 BSpline 就没有相应的问题了
(5)接着实现 SurfaceOfRevolution
,这个类的意思是利用之前实现的两种曲线,然后绕 y 轴旋转得到对应的立体结构
(6)最后完成 BezierPatch
在这次作业中,只需要实现 16 个控制点的贝塞尔曲面
Implementation¶
Curve
¶
这里展示的是 curve
的头文件
# ifndef ASSIGNMENT8_CURVE_H
# define ASSIGNMENT8_CURVE_H
#include "spline.h"
#include "matrix.h"
class Curve : public Spline {
public:
Curve (int num_vertices): Spline(), numVertices(num_vertices) {
vertices = new Vec3f[numVertices];
}
void Paint(ArgParser *args) override;
int getNumVertices() override{
return numVertices;
}
Vec3f getVertex(int i) override{
return vertices[i];
}
int getNumCurves() {
return numCurves;
}
void moveControlPoint(int selectedPoint, float x, float y) override{
vertices[selectedPoint] = Vec3f(x, y, 0);
}
virtual Vec3f* tessellate(ArgParser *args)=0;
void set (int i, Vec3f v){
vertices[i] = v;
}
protected:
int numCurves; //单位曲线数量
int numVertices; //控制点的数量
Vec3f *vertices; //控制点的数组
Matrix mat; //Spline Basis
};
class BezierCurve : public Curve {
public:
void InitializeMatrix()
{
float Bezier[] = {
-1, 3, -3, 1,
3, -6, 3, 0,
-3, 3, 0, 0,
1, 0, 0, 0
};
float BSpilne[] = {
-1, 3, -3, 1,
3, -6, 0, 4,
-3, 3, 3, 1,
1, 0, 0, 0
};
Matrix matBezier(Bezier);
Matrix matBSpline(BSpilne);
mat = matBezier;
matBSpline*= (1.0/6);
matBSpline.Inverse(matBSpline, 0.0001);
matInver = mat*matBSpline;
}
BezierCurve (int num_vertices): Curve(num_vertices){
InitializeMatrix();
numCurves = (num_vertices-1)/3;
}
Vec3f* tessellate(ArgParser *args);
void addControlPoint(int selectedPoint, float x, float y) override{
numVertices++;
numCurves = (numVertices-1)/3;
Vec3f* newVertices = new Vec3f[numVertices];
for (int i = 0; i < selectedPoint; i++){
newVertices[i] = vertices[i];
}
newVertices[selectedPoint] = Vec3f(x, y, 0);
for (int i = selectedPoint + 1; i < numVertices; i++){
newVertices[i] = vertices[i - 1];
}
delete[] vertices;
vertices = newVertices;
}
void deleteControlPoint(int selectedPoint) override{
numVertices--;
numCurves = (numVertices-1)/3;
Vec3f* newVertices = new Vec3f[numVertices];
for (int i = 0; i < selectedPoint; i++){
newVertices[i] = vertices[i];
}
for (int i = selectedPoint; i < numVertices; i++){
newVertices[i] = vertices[i + 1];
}
delete[] vertices;
vertices = newVertices;
}
void OutputBezier(FILE *file);
void OutputBSpline(FILE *file);
private:
Matrix matInver; //转化矩阵
};
class BSplineCurve : public Curve {
public:
void InitializeMatrix()
{
float Bezier[] = {
-1, 3, -3, 1,
3, -6, 3, 0,
-3, 3, 0, 0,
1, 0, 0, 0
};
float BSpilne[] = {
-1, 3, -3, 1,
3, -6, 0, 4,
-3, 3, 3, 1,
1, 0, 0, 0
};
Matrix matBezier(Bezier);
Matrix matBSpline(BSpilne);
mat = matBSpline*(1.0/6);
matBezier.Inverse(matBezier, 0.0001);
//转化矩阵的计算
matInver = mat*matBezier;
}
BSplineCurve (int num_vertices): Curve(num_vertices){
InitializeMatrix();
numCurves = num_vertices-3;
}
Vec3f* tessellate(ArgParser *args);
//这里代码的部分跟前面有所重复,只是为了更新numCurves(两种曲线的更新方法不一样)
void addControlPoint(int selectedPoint, float x, float y) override{
numVertices++;
numCurves = numVertices-3;
Vec3f* newVertices = new Vec3f[numVertices];
for (int i = 0; i < selectedPoint; i++){
newVertices[i] = vertices[i];
}
newVertices[selectedPoint] = Vec3f(x, y, 0);
for (int i = selectedPoint + 1; i < numVertices; i++){
newVertices[i] = vertices[i - 1];
}
delete[] vertices;
vertices = newVertices;
}
void deleteControlPoint(int selectedPoint) override{
numVertices--;
numCurves = numVertices-3;
Vec3f* newVertices = new Vec3f[numVertices];
for (int i = 0; i < selectedPoint; i++){
newVertices[i] = vertices[i];
}
for (int i = selectedPoint; i < numVertices; i++){
newVertices[i] = vertices[i + 1];
}
delete[] vertices;
vertices = newVertices;
}
void OutputBezier(FILE *file) override;
void OutputBSpline(FILE *file) override;
private:
Matrix matInver; //转化矩阵
};
# endif
在 curve.C
中需要完成的主要部分是 tessellate
,作用是生成曲线的离散点(利用相应的矩阵 GBT 相乘)我感觉更好的方法是直接用定义,利用插值来生成点。
Vec3f* BezierCurve::tessellate(ArgParser *args)
{
Vec3f* points = new Vec3f[numCurves*args->curve_tessellation];
float step = 1.0 / (args->curve_tessellation-1);
for (int i = 0; i < numVertices-3; i+=3 )
{
float vertex[]={
vertices[i].x(), vertices[i+1].x(), vertices[i+2].x(), vertices[i+3].x(),
vertices[i].y(), vertices[i+1].y(), vertices[i+2].y(), vertices[i+3].y(),
vertices[i].z(), vertices[i+1].z(), vertices[i+2].z(), vertices[i+3].z(),
1, 1, 1, 1
};
Matrix matVertex(vertex);
for (int j = 0; j < args->curve_tessellation; j++)
{
float t = j * step;
Vec4f T(t*t*t, t*t, t, 1);
Matrix m = matVertex * mat;
m.Transform(T);
points[(i/3) * args->curve_tessellation+j] = Vec3f(T[0], T[1], T[2]);
}
}
return points;
}
Vec3f* BSplineCurve::tessellate(ArgParser *args)
{
Vec3f* points = new Vec3f[numCurves*args->curve_tessellation];
float step = 1.0 / (args->curve_tessellation-1);
for (int i = 0; i < numVertices-3; i++ )
{
float vertex[]={
vertices[i].x(), vertices[i+1].x(), vertices[i+2].x(), vertices[i+3].x(),
vertices[i].y(), vertices[i+1].y(), vertices[i+2].y(), vertices[i+3].y(),
vertices[i].z(), vertices[i+1].z(), vertices[i+2].z(), vertices[i+3].z(),
1, 1, 1, 1
};
Matrix matVertex(vertex);
for (int j = 0; j < args->curve_tessellation; j++)
{
float t = j * step;
Vec4f T(t*t*t, t*t, t, 1);
Matrix m = matVertex * mat;
m.Transform(T);
points[i * args->curve_tessellation+j] = Vec3f(T[0], T[1], T[2]);
}
}
return points;
}
这里两者的代码依然重复的部分很多,主要的区别点就在于单位曲线数量的不同
然后还需要实现下面两个函数,用于实现两种曲线的转化(只需要转化4个控制点的部分)
这里只展示Bezier曲线到BSpline 的代码,反过来的部分差不多
void BezierCurve::OutputBSpline(FILE *file)
{
fprintf(file, "bspline\n");
float vertex[]={
vertices[0].x(), vertices[1].x(), vertices[2].x(), vertices[3].x(),
vertices[0].y(), vertices[1].y(), vertices[2].y(), vertices[3].y(),
vertices[0].z(), vertices[1].z(), vertices[2].z(), vertices[3].z(),
1, 1, 1, 1
};
Matrix matVertex(vertex);
Matrix result = matVertex * matInver;
fprintf(file, "num_vertices %d\n", numVertices);
for (int i = 0; i < 4; i++)
{
Vec3f v = Vec3f(result.Get(i, 0), result.Get(i, 1), result.Get(i, 2));
fprintf(file, "%f %f %f\n", v.x(), v.y(), v.z());
}
}
surfaceOfRevolution
¶
利用题目提供的 TriangleMesh
与 TriangleNet
类来生成相应的三角形网格,输出 .obj
文件
这个派生类的成员变量是一条曲线,通过这条曲线来生成相应的立体结构。其中 TriangleMesh
与 TriangleNet
的内容关注一下点的数量(有时候会+1,这导致了不少的bug),生成立体结构需要的是一张由点构成的网格,然后依次输入相应的点坐标
TriangleMesh* SurfaceOfRevolution::OutputTriangles(ArgParser *args) {
float angle = 2 * M_PI / args->revolution_tessellation;
Matrix m = Matrix::MakeYRotation(angle);
Vec3f* points = curve->tessellate(args);
int num_points = curve->getNumCurves()*args->curve_tessellation;
auto *mesh = new TriangleNet( args->revolution_tessellation,num_points-1);
for (auto i = 0; i <= args->revolution_tessellation; i++) {
for (int j = 0; j < num_points; j++) {
mesh->SetVertex(i, j, points[j]);
m.Transform(points[j]);
}
}
return mesh;
}
BezierPatch
¶
作业只需要实现16个控制点的贝塞尔曲面,具体的实现思路是先用横向的四个点生成相应的贝塞尔曲线,然后生成的四条贝塞尔曲线拥有相同参数的四个点构成新的控制点生成纵向的贝塞尔曲线,然后遍历就可得到对应的贝塞尔曲面了。
TriangleMesh* BezierPatch::OutputTriangles(ArgParser *args) {
auto *mesh = new TriangleNet(args->patch_tessellation-1, args->patch_tessellation-1);
BezierCurve** curves = new BezierCurve*[4];
for (int i = 0; i < 4; i++) {
curves[i] = new BezierCurve(4);
for (int j = 0; j < 4; j++){
curves[i]->set(j, vertices[i * 4 + j]);
}
}
for (int i = 0; i < args->patch_tessellation; i++) {
BezierCurve* tmpCurve = new BezierCurve(4);
tmpCurve->set(0, curves[0]->tessellate(args)[i]);
tmpCurve->set(1, curves[1]->tessellate(args)[i]);
tmpCurve->set(2, curves[2]->tessellate(args)[i]);
tmpCurve->set(3, curves[3]->tessellate(args)[i]);
for (int j = 0; j < args->patch_tessellation; j++) {
mesh->SetVertex(i, j, tmpCurve->tessellate(args)[j]);
}
}
return mesh;
}
相应的 paint
函数是类似的
void BezierPatch::Paint(ArgParser *args) {
glLineWidth(1);
glBegin(GL_LINE_STRIP);
glColor3f(0, 0, 1);
for (int i = 0; i < 16; i++) {
Vec3f p = vertices[i];
glVertex2f(p.x(), p.y());
}
glEnd();
glPointSize(5);
glBegin(GL_POINTS);
glColor3f(1, 0, 0);
for (int i = 0; i < 16; i++) {
Vec3f p = vertices[i];
glVertex2f(p.x(), p.y());
}
glEnd();
}
Summary¶
这次作业难度适中,主要是实现了样条曲线与曲面,大概写了一个下午,然后debug了两三个小时,赶在了寒假前完成了相应内容。主要遇到的bug有
- 生成三角形网格的时候,没有设置好网格的多少,有+1-1的细节
- 一些数组的大小的调整还是不太方便,使用 vector 容器会方便很多,这次出现了大量想要得到数组大小的操作,我的实现比较麻烦,有很多重复的代码
只剩下最后一个粒子系统的 Assignment,争取在开学后的一周做完。