计算机图形学 实验二 三维模型读取与控制

目录

一、实验内容

二、具体内容

(在实验2.3的基础上进行修改)

1、OFF格式三维模型文件的读取

2、三维模型的旋转动画

3、键盘鼠标的交互

4、模型的修改

三、代码


一、实验内容

  1. 读取实验提供的off格式三维模型,并对其赋色。利用鼠标和键盘的交互,控制动画效果,模型的颜色自己可以自行设置,好看就行。

二、具体内容

(在实验2.3的基础上进行修改)

1、OFF格式三维模型文件的读取

参考上机实验2.2的内容,完成对OFF格式三维模型文件的读取与显示,可改变物体的显示颜色,尽量特别,但不要太难看。

1)修改init()方法,读取OFF格式三维模型文件cow.off。

2)修改颜色:

在readoff()方法中:将坐标值([-1,1])映射到颜色值([0,1])

方法1:坐标值加1,除2(结果和实验给的类似)

方法2:坐标值取绝对值

(应该还不算难看)

2、三维模型的旋转动画

参考实验2.1中动画的生成方式,并结合实验2.3中对模型进行旋转变换的过程,生成旋转动画。

默认绕X轴旋转,每1000毫秒转一下rotateDelta角度。

定义相关变量:

在mian函数中定义相关操作:

3、键盘鼠标的交互

参考实验2.1中鼠标与键盘的交互,通过键盘设定选择绕x、y、z轴进行旋转,通过鼠标左右键控制动画的开始与暂停。

1)通过键盘设定选择绕x、y、z轴进行旋转:

在key_callback函数内增加增加如下代码,通过按"X"、"Y"、"Z"控制。

2)通过鼠标左右键控制动画的开始与暂停:

增加mouse_button_callback函数:

然后在main函数中绑定。

3)修改提示语:

4、模型的修改

参考以下代码,通过键盘设定可以在cow.off和cube.off之间切换。

清除顶点数组缓存:

glBindVertexArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

二维向量:

std::vector<std::vector<?>>

其他STL相关代码可以查阅C++ STL 教程 | 菜鸟教程 (runoob.com)

1)添加变量表示绘制的是牛还是方块:

2)修改init()函数:通过判断currentModel选择读取的文件:

3)在key_callback函数中添加如下代码:

通过按下"N"绘制牛,按下"M"绘制方块。

4)并修改相关提示语。

效果如下:

三、代码

1、main.cpp

#include "Angel.h"
#include "TriMesh.h"
#include <vector>
#include <string>
//#include "main.h"
using namespace std;

const int X_AXIS = 0;
const int Y_AXIS = 1;
const int Z_AXIS = 2;

const int TRANSFORM_SCALE = 0;
const int TRANSFORM_ROTATE = 1;
const int TRANSFORM_TRANSLATE = 2;

const double DELTA_DELTA = 0.3;		// Delta的变化率
const double DEFAULT_DELTA = 0.5;	// 默认的Delta值

double scaleDelta = DEFAULT_DELTA;
double rotateDelta = DEFAULT_DELTA;
double translateDelta = DEFAULT_DELTA;

glm::vec3 scaleTheta(1.0, 1.0, 1.0);		// 缩放控制变量
glm::vec3 rotateTheta(0.0, 0.0, 0.0);    // 旋转控制变量
glm::vec3 translateTheta(0.0, 0.0, 0.0);	// 平移控制变量

int currentTransform = TRANSFORM_ROTATE;	// 设置当前变换
int mainWindow;

//------------------------------------------------------------------------
bool isplaying = true;	// 动画状态
int rotationAxis = X_AXIS;	// 当前旋转轴,默认为 X 轴
int rotationTime = 1000;		// 每1000帧旋转一次

string currentModel ="cow";


struct openGLObject
{
	// 顶点数组对象
	GLuint vao;
	// 顶点缓存对象
	GLuint vbo;

	// 着色器程序
	GLuint program;
	// 着色器文件
	std::string vshader;
	std::string fshader;
	// 着色器变量
	GLuint pLocation;
	GLuint cLocation;
	GLuint matrixLocation;
	GLuint darkLocation;
};

openGLObject cube_object;

TriMesh* cube = new TriMesh();

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}

void bindObjectAndData(TriMesh* mesh, openGLObject& object, const std::string& vshader, const std::string& fshader) {

	// 创建顶点数组对象
	glGenVertexArrays(1, &object.vao);  	// 分配1个顶点数组对象
	glBindVertexArray(object.vao);  	// 绑定顶点数组对象

	// 创建并初始化顶点缓存对象
	glGenBuffers(1, &object.vbo);
	glBindBuffer(GL_ARRAY_BUFFER, object.vbo);
	glBufferData(GL_ARRAY_BUFFER,
		mesh->getPoints().size() * sizeof(glm::vec3) + mesh->getColors().size() * sizeof(glm::vec3),
		NULL,
		GL_STATIC_DRAW);

	// @TODO: Task3-修改完TriMesh.cpp的代码成后再打开下面注释,否则程序会报错
	glBufferSubData(GL_ARRAY_BUFFER, 0, mesh->getPoints().size() * sizeof(glm::vec3), &mesh->getPoints()[0]);
	glBufferSubData(GL_ARRAY_BUFFER, mesh->getPoints().size() * sizeof(glm::vec3), mesh->getColors().size() * sizeof(glm::vec3), &mesh->getColors()[0]);

	object.vshader = vshader;
	object.fshader = fshader;
	object.program = InitShader(object.vshader.c_str(), object.fshader.c_str());

	// 从顶点着色器中初始化顶点的位置
	object.pLocation = glGetAttribLocation(object.program, "vPosition");
	glEnableVertexAttribArray(object.pLocation);
	glVertexAttribPointer(object.pLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	// 从顶点着色器中初始化顶点的颜色
	object.cLocation = glGetAttribLocation(object.program, "vColor");
	glEnableVertexAttribArray(object.cLocation);
	glVertexAttribPointer(object.cLocation, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(mesh->getPoints().size() * sizeof(glm::vec3)));

	// 获得矩阵存储位置
	object.matrixLocation = glGetUniformLocation(object.program, "matrix");

}

void init()
{
	std::string vshader, fshader;
	// 读取着色器文件路径
	vshader = "shaders/vshader.glsl";
	fshader = "shaders/fshader.glsl";

	//cube->generateCube();
	//cube->readOff("./Models/cow.off");
	if (currentModel == "cow") {
		cube->readOff("./Models/cow.off");
	}
	else {
		cube->generateCube();
	}
	bindObjectAndData(cube, cube_object, vshader, fshader);

	// 设置背景色为黑色
	glClearColor(0.0, 0.0, 0.0, 1.0);
}

// 渲染函数
void display()
{
	// 清空颜色缓冲和深度缓冲
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glUseProgram(cube_object.program);

	glBindVertexArray(cube_object.vao);

	// 初始化变换矩阵  glm::mat4表示 4x4 矩阵
	glm::mat4 m(1.0, 0.0, 0.0, 0.0,
		0.0, 1.0, 0.0, 0.0,
		0.0, 0.0, 1.0, 0.0,
		0.0, 0.0, 0.0, 1.0);

	// @TODO: Task4-在此处修改函数,计算最终的变换矩阵
	// 调用函数传入三种变化的变化量,累加得到变化矩阵
	// 注意三种变化累加的顺序
	// 构建旋转矩阵
	glm::mat4 rotationMatrix = glm::rotate(glm::mat4(1.0), rotateTheta.x, glm::vec3(1.0, 0.0, 0.0))
		* glm::rotate(glm::mat4(1.0), rotateTheta.y, glm::vec3(0.0, 1.0, 0.0))
		* glm::rotate(glm::mat4(1.0), rotateTheta.z, glm::vec3(0.0, 0.0, 1.0));

	// 构建缩放矩阵
	glm::mat4 scaleMatrix = glm::scale(glm::mat4(1.0), glm::vec3(scaleTheta.x, scaleTheta.y, scaleTheta.z));

	// 构建平移矩阵
	glm::mat4 translateMatrix = glm::translate(glm::mat4(1.0), glm::vec3(translateTheta.x, translateTheta.y, translateTheta.z));

	// 按照平移、旋转、缩放的顺序相乘得到最终的变换矩阵
	m = translateMatrix * rotationMatrix * scaleMatrix;

	// 传递变换矩阵到着色器
	glUniformMatrix4fv(cube_object.matrixLocation, 1, GL_FALSE, glm::value_ptr(m));

	// 绘制立方体中的各个三角形
	glDrawArrays(GL_TRIANGLES, 0, cube->getPoints().size());
}

// 通过Delta值更新Theta
// axis 表示坐标轴,sign 表示增加或减少
// currentTransform 表示当前变换类型
void updateTheta(int axis, int sign) {
	switch (currentTransform) {
		// 根据变换类型,增加或减少某种变换的变化量
	case TRANSFORM_SCALE:
		//增加或减少缩放的 Theta 值
		scaleTheta[axis] += sign * scaleDelta;
		break;
	case TRANSFORM_ROTATE:
		//增加或减少旋转的 Theta 值
		rotateTheta[axis] += sign * rotateDelta;
		break;
	case TRANSFORM_TRANSLATE:
		//增加或减少平移的 Theta 值
		translateTheta[axis] += sign * translateDelta;
		break;
	}
}

// 复原Theta和Delta
void resetTheta()
{
	scaleTheta = glm::vec3(1.0, 1.0, 1.0);	//scaleTheta 表示缩放变换的角度
	rotateTheta = glm::vec3(0.0, 0.0, 0.0);
	translateTheta = glm::vec3(0.0, 0.0, 0.0);
	scaleDelta = DEFAULT_DELTA;				//缩放变换的单位变化量
	rotateDelta = DEFAULT_DELTA;
	translateDelta = DEFAULT_DELTA;
}

// 更新变化Delta值
void updateDelta(int sign)
{
	switch (currentTransform) {
		// 根据变化类型增加或减少每一次变化的单位变化量
	case TRANSFORM_SCALE:
		scaleDelta += sign * DELTA_DELTA;
		break;
	case TRANSFORM_ROTATE:
		rotateDelta += sign * DELTA_DELTA;
		break;
	case TRANSFORM_TRANSLATE:
		translateDelta += sign * DELTA_DELTA;
		break;
	}
}

void cleanData();

// 处理键盘输入的回调函数
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
	switch (key)
	{
		// 退出。
	case GLFW_KEY_ESCAPE:
		if (action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE);
		break;
		// 1:缩放模式
	case GLFW_KEY_1:
		if (action == GLFW_PRESS) currentTransform = TRANSFORM_SCALE;
		break;
		// 2: 旋转模式
	case GLFW_KEY_2:
		if (action == GLFW_PRESS) currentTransform = TRANSFORM_ROTATE;
		break;
		// 3: 移动模式
	case GLFW_KEY_3:
		if (action == GLFW_PRESS) currentTransform = TRANSFORM_TRANSLATE;
		break;
		// 4: 绘制线。
	case GLFW_KEY_4:
		if (action == GLFW_PRESS) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		break;
		// 5: 绘制面。
	case GLFW_KEY_5:
		if (action == GLFW_PRESS) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		break;
		// Q: 增加 x。
	case GLFW_KEY_Q:
		if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(X_AXIS, 1);
		break;
		// A: 减少 x。
	case GLFW_KEY_A:
		if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(X_AXIS, -1);
		break;
		// W: 增加 y。
	case GLFW_KEY_W:
		if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Y_AXIS, 1);
		break;
		// S: 减少 y。
	case GLFW_KEY_S:
		if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Y_AXIS, -1);
		break;
		// E: 增加 z。
	case GLFW_KEY_E:
		if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Z_AXIS, 1);
		break;
		// D: 减少 z。
	case GLFW_KEY_D:
		if (action == GLFW_PRESS || action == GLFW_REPEAT) updateTheta(Z_AXIS, -1);
		break;
		// R: 增加变化量。
	case GLFW_KEY_R:
		if (action == GLFW_PRESS) updateDelta(1);
		break;
		// F: 减少变化量。
	case GLFW_KEY_F:
		if (action == GLFW_PRESS) updateDelta(-1);
		break;
		// T: 所有值重置。
	case GLFW_KEY_T:
		if (action == GLFW_PRESS) resetTheta();
		break;
		//-------------------------------------------------------------------
		// 选择绕 X 轴旋转
	case GLFW_KEY_X:
		if (action == GLFW_PRESS) rotationAxis = X_AXIS;
		break;
		// 选择绕 Y 轴旋转
	case GLFW_KEY_Y:
		if (action == GLFW_PRESS) rotationAxis = Y_AXIS;
		break;
		// 选择绕 Z 轴旋转
	case GLFW_KEY_Z:
		if (action == GLFW_PRESS) rotationAxis = Z_AXIS;
		break;
		
    // N: 加载cow.off模型
    case GLFW_KEY_N:
        if (action == GLFW_PRESS) {
			glBindVertexArray(0);//清除顶点数组缓存
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
			currentModel = "cow";
			init();
        }
        break;
    // M: 加载cube模型
    case GLFW_KEY_M:
        if (action == GLFW_PRESS) {
			glBindVertexArray(0);//清除顶点数组缓存
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
			currentModel = "cube";
			init();
        }
        break;
	}

}

//-------------------------------------------------------------------------------
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
	if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
		isplaying = true; // 左键按下,开始动画
	}
	if (button == GLFW_MOUSE_BUTTON_RIGHT && action == GLFW_PRESS) {
		isplaying = false; // 右键按下,暂停动画
	}
}


// 输出帮助信息
void printHelp() {
	printf("%s\n\n", "3D Transfomations");
	printf("Keyboard options:\n");
	printf("n: Draw cow\n");
	printf("m: Draw block\n");
	printf("left mouse button: start playing\n");
	printf("right mouse button: Pause playback\n");
	printf("x: Rotate around the X-axis\n");
	printf("y: Rotate around the Y-axis\n");
	printf("z: Rotate around the Z-axis\n");
	printf("The following are the operations previously used:\n");
	printf("1: Transform Scale\n");
	printf("2: Transform Rotate\n");
	printf("3: Transform Translate\n");
	printf("q: Increase x\n");
	printf("a: Decrease x\n");
	printf("w: Increase y\n");
	printf("s: Decrease y\n");
	printf("e: Increase z\n");
	printf("d: Decrease z\n");
	printf("r: Increase delta of currently selected transform\n");
	printf("f: Decrease delta of currently selected transform\n");
	printf("t: Reset all transformations and deltas\n");
}

// 清理数据
void cleanData() {
	cube->cleanData();

	// 释放内存
	delete cube;
	cube = NULL;

	// 删除绑定的对象
	glDeleteVertexArrays(1, &cube_object.vao);

	glDeleteBuffers(1, &cube_object.vbo);
	glDeleteProgram(cube_object.program);
}

int main(int argc, char** argv)
{
	// 初始化GLFW库,必须是应用程序调用的第一个GLFW函数
	glfwInit();

	// 配置GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
	//设置字符格式
    #pragma execution_character_set("GBK");
	GLFWwindow* window = glfwCreateWindow(600, 600, "homework", NULL, NULL);

	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// 调用任何OpenGL的函数之前初始化GLAD
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}

	init();
	// 输出帮助信息
	printHelp();
	// 启用深度测试
	glEnable(GL_DEPTH_TEST);

	//------------------------------------------------------------------------
	glfwSetMouseButtonCallback(window, mouse_button_callback); // 设置鼠标回调

	int flag = clock();
	while (!glfwWindowShouldClose(window))
	{
		display();

		// 交换颜色缓冲 以及 检查有没有触发什么事件(比如键盘输入、鼠标移动等)
		glfwSwapBuffers(window);
		glfwPollEvents();
		
		//--------------------------------------------------------------------------------
		//处理动画
		int now = clock();
		if (isplaying) {
			// 每1000帧旋转一定的角度
			if ((now - flag) >= rotationTime) {
				if (rotationAxis == X_AXIS) {
					rotateTheta.x += rotateDelta; // 沿X轴旋转
				}
				else if (rotationAxis == Y_AXIS) {
					rotateTheta.y += rotateDelta; // 沿Y轴旋转
				}
				else if (rotationAxis == Z_AXIS) {
					rotateTheta.z += rotateDelta; // 沿Z轴旋转
				}

				flag = now;
			}
		}
	}
	cleanData();

	return 0;
}

2、TriMesh.cpp

#include "TriMesh.h"


// 一些基础颜色
const glm::vec3 basic_colors[8] = {
    glm::vec3(1.0, 1.0, 1.0),	// White
    glm::vec3(1.0, 1.0, 0.0),	// Yellow
    glm::vec3(0.0, 1.0, 0.0),	// Green
    glm::vec3(0.0, 1.0, 1.0),	// Cyan
    glm::vec3(1.0, 0.0, 1.0),	// Magenta
    glm::vec3(1.0, 0.0, 0.0),	// Red
    glm::vec3(0.0, 0.0, 0.0),	// Black
    glm::vec3(0.0, 0.0, 1.0)		// Blue
};

// 立方体的各个点
const glm::vec3 cube_vertices[8] = {
    glm::vec3(-0.5, -0.5, -0.5),
    glm::vec3(0.5, -0.5, -0.5),
    glm::vec3(-0.5,  0.5, -0.5),
    glm::vec3(0.5,  0.5, -0.5),
    glm::vec3(-0.5, -0.5,  0.5),
    glm::vec3(0.5, -0.5,  0.5),
    glm::vec3(-0.5,  0.5,  0.5),
    glm::vec3(0.5,  0.5,  0.5)
};

TriMesh::TriMesh()
{
}

TriMesh::~TriMesh()
{
}

std::vector<glm::vec3> TriMesh::getVertexPositions()
{
    return vertex_positions;
}

std::vector<glm::vec3> TriMesh::getVertexColors()
{
    return vertex_colors;
}

std::vector<vec3i> TriMesh::getFaces()
{
    return faces;
}


std::vector<glm::vec3> TriMesh::getPoints()
{
    return points;
}

std::vector<glm::vec3> TriMesh::getColors()
{
    return colors;
}

void TriMesh::cleanData() {
    vertex_positions.clear();
    vertex_colors.clear();

    faces.clear();

    points.clear();
    colors.clear();

    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

void TriMesh::storeFacesPoints() {

    // @TODO: Task-2修改此函数在points和colors容器中存储每个三角面片的各个点和颜色信息
    // 根据每个三角面片的顶点下标存储要传入GPU的数据
     // 清空points和colors容器  
    points.clear();
    colors.clear();

    // 遍历每个面  
    for (const auto& face : faces) {
        // 根据索引获取顶点的位置和颜色  
        unsigned int x = face.x;
        unsigned int y = face.y;
        unsigned int z = face.z;

        glm::vec3 pos1 = vertex_positions[x];
        glm::vec3 col1 = vertex_colors[x];
        glm::vec3 pos2 = vertex_positions[y];
        glm::vec3 col2 = vertex_colors[y];
        glm::vec3 pos3 = vertex_positions[z];
        glm::vec3 col3 = vertex_colors[z];

        // 将顶点位置和颜色添加到points和colors容器中  
        points.push_back(pos1);
        colors.push_back(col1);
        points.push_back(pos2);
        colors.push_back(col2);
        points.push_back(pos3);
        colors.push_back(col3);
    }

}

// 立方体生成12个三角形的顶点索引
void TriMesh::generateCube() {
    // 创建顶点前要先把那些vector清空
    cleanData();

    // @TODO: Task1-修改此函数,存储立方体的各个面信息
    // vertex_positions和vertex_colors先保存每个顶点的数据
    for (int i = 0; i < 8; ++i) {
        vertex_positions.push_back(cube_vertices[i]);
        // 这里简单使用基本颜色数组中的颜色,每个顶点按顺序分配颜色  
        vertex_colors.push_back(basic_colors[i % 8]);
    }
    // faces再记录每个面片上顶点的下标

    // 立方体12个面的顶点索引  
    // 每个面由两个三角形组成  
    faces.push_back(vec3i(1, 3, 7)); // 前面  
    faces.push_back(vec3i(1, 7, 5));
    faces.push_back(vec3i(0, 2, 6)); // 后面  
    faces.push_back(vec3i(0, 6, 4));
    faces.push_back(vec3i(2, 6, 7)); // 右面  
    faces.push_back(vec3i(2, 7, 3));
    faces.push_back(vec3i(0, 4, 5)); // 左面  
    faces.push_back(vec3i(0, 5, 1));
    faces.push_back(vec3i(4, 5, 7)); // 顶面  
    faces.push_back(vec3i(4, 7, 6));
    faces.push_back(vec3i(0, 1, 3)); // 底面  
    faces.push_back(vec3i(0, 3, 2));
    storeFacesPoints();
}



void TriMesh::readOff(const std::string& filename)
{
    // fin打开文件读取文件信息
    if (filename.empty())
    {
        return;
    }
    std::ifstream fin;
    fin.open(filename);
    if (!fin)
    {
        printf("File on error\n");
        return;
    }
    else
    {
        printf("File open success\n");
        cleanData();
        int nVertices, nFaces, nEdges;

        // 读取OFF字符串
        std::string str;
        fin >> str;
        // 读取文件中顶点数、面片数、边数
        fin >> nVertices >> nFaces >> nEdges;
        // 根据顶点数,循环读取每个顶点坐标
        for (int i = 0; i < nVertices; i++)
        {
            glm::vec3 tmp_node;
            fin >> tmp_node.x >> tmp_node.y >> tmp_node.z;
            vertex_positions.push_back(tmp_node);
            //vertex_colors.push_back(tmp_node);
            // 将坐标值([-1,1])映射到颜色值([0,1])
            /*
            //方法1:加1,除2(结果和实验给的一样)
                glm::vec3 color = (tmp_node + glm::vec3(1.0f)) * 0.5f;
             */
            //方法2:取坐标绝对值
            float a = tmp_node.x>0? tmp_node.x:-tmp_node.x;
            float b = tmp_node.y>0? tmp_node.y:-tmp_node.y;
            float g = tmp_node.z>0? tmp_node.z:-tmp_node.z;
            glm::vec3 color(a,b,g);

            vertex_colors.push_back(color);

        }
        // 根据面片数,循环读取每个面片信息,并用构建的vec3i结构体保存
        for (int i = 0; i < nFaces; i++)
        {
            int num, a, b, c;
            // num记录此面片由几个顶点构成,a、b、c为构成该面片顶点序号
            fin >> num >> a >> b >> c;
            faces.push_back(vec3i(a, b, c));
        }
    }
    fin.close();
    storeFacesPoints();
};
相关推荐
禁默1 天前
2024年图像处理、多媒体技术与机器学习
图像处理·人工智能·microsoft
Zmxcl-0071 天前
IIS解析漏洞
服务器·数据库·microsoft
蚁景网络安全2 天前
Cobalt Strike 4.8 用户指南-第十四节 Aggressor 脚本
windows·microsoft
不坑老师2 天前
不坑盒子2024.1218更新了,模板库上线、一键添加拼音、一键翻译……支持Word、Excel、PPT、WPS
microsoft·word·powerpoint·excel·wps
小奥超人3 天前
【ppt技巧】如何设置PPT带有密码的只读模式?
windows·经验分享·microsoft·powerpoint·办公技巧
七月的和弦3 天前
Win10将WindowsTerminal设置默认终端并添加到右键(无法使用微软商店)
windows·microsoft
阿达_优阅达4 天前
集成方案 | Docusign + 金蝶云,实现合同签署流程自动化!
运维·microsoft·自动化·企业数字化转型·docusign
嘟嘟实验室4 天前
TRELLIS,一键生成3D模型,图像转3D,微软开源
人工智能·python·microsoft·3d·开源·aigc
PowerBI学谦5 天前
Copilot for Microsoft 365 office手把手使用指南
人工智能·microsoft·copilot
慧都小妮子5 天前
.NET 9微软新平台 + FastReport .NET:如何提升报告生成效率
microsoft·.net·报表控件·fastreport·.net9