类球多面体生成——经纬划分法

规则多面体生成算法,算法本身并不复杂。开始想百度一份的,结果没百度到。贴出来,希望以后有用得到的同学可在直接拿去用。

算法过程

  1. 根据经纬线数目求出多面体表面所有点的坐标;
  2. 连接南北极附近的三角形面;
  3. 连接中间的四边形(或两个三角形);

算法实现

下面是该算法的C++实现.

Convex* SphereGenerator::generate(int longitudes, int latitudes, Float radius)
{
	m_radius = radius;
	m_longitudes = longitudes;
	m_latitudes = latitudes;

	return generate();
}

Convex* SphereGenerator::generate()
{
	Convex* pConvex = new Convex();

	assert(m_latitudes >= 1);
	assert(m_longitudes >= 3);

	Point northPole(0, m_radius, 0);
	Point southPole(0, -m_radius, 0);

	int iNorth = pConvex->addVertex(northPole);
	int iSouth = pConvex->addVertex(southPole);

	double lonDelta = 2*M_PI / m_longitudes;
	double latDelta = M_PI / (m_latitudes+1);

	std::vector< std::vector<int> > vertIndices;
	vertIndices.resize(m_latitudes);

	// 计算所有顶点
	for (int lat=0; lat<m_latitudes; ++lat)
	{
		vertIndices[lat] = std::vector<int>(size_t(m_longitudes));
		Float y = m_radius * glm::sin(M_PI/2 - (lat+1)*latDelta);
		Float r = m_radius * glm::cos(M_PI/2 - (lat+1)*latDelta); // important!!
		for (int i=0; i<m_longitudes; ++i)
		{
			Point pt(
				r * glm::cos(i * lonDelta),
				y,
				-r * glm::sin(i * lonDelta)
				);
			vertIndices[lat][i] = pConvex->addVertex(pt);
		}
	}

	// 连接南北两极附近三角形面
	for (int i=0; i<m_longitudes; ++i)
	{
		int next = i+1 < m_longitudes ? i+1 : 0;
		Triangle triN(vertIndices[0][i], vertIndices[0][next], iNorth);
		pConvex->addTriangle(triN);

		Triangle triS(vertIndices[m_latitudes-1][next], vertIndices[m_latitudes-1][i], iSouth);
		pConvex->addTriangle(triS);
	}

	// 连接中间的三角形面
	if (m_latitudes >= 2)
	{
		for (int lat=0; lat<m_latitudes-1; ++lat)
		{
			int nextLat = lat+1;
			for (int i=0; i<m_longitudes; ++i)
			{
				int nextI = i+1 < m_longitudes ? i+1 : 0;
				int A = vertIndices[lat][i];
				int B = vertIndices[nextLat][i];
				int C = vertIndices[nextLat][nextI];
				int D = vertIndices[lat][nextI];
				pConvex->addTriangle(Triangle(A, B, D));
				pConvex->addTriangle(Triangle(B, C, D));
			}
		}
	}

	return pConvex;
}

演示程序

使用GLUT写的一个演示程序:

上键——增加纬线,

下键——减少纬线,

左键——减少经线,

右键——增加纬线。

#include <windows.h> // windows API 实现的OpenGL
#include <gl/gl.h>
#include <gl/glut.h>
#include <stdio.h>

#include "GlutApp.h"
#include "SphereGenerator.h"

class DemoSphereGen : public GlutApp
{
	Convex* pConvex;

	SphereGenerator* generator;

	float angle;

	int depth;

	void onKey(unsigned char key, int x, int y)
	{
		switch(key)
		{
		case ' ':
			int lon = generator->getLongitude();
			int lat = generator->getLatitude();
			static char buf[128];
			sprintf(buf, "sphere%d-%d.obj", lon, lat);
			printf("serialized to %s!\n", buf);
			pConvex->serialize(buf);
			break;
		}
	}

	virtual void onSpecialKey(int key, int x, int y)
	{
		int lon = generator->getLongitude();
		int lat = generator->getLatitude();
		bool flag=false;
		switch(key)
		{
		case GLUT_KEY_UP:
			lat++;
			flag = true;
			break;
		case GLUT_KEY_DOWN:
			lat--;
			if (lat < 1)
			{
				lat = 1;
			}
			flag = true;
			break;
		case GLUT_KEY_LEFT:
			lon--;
			if (lon < 3)
			{
				lon = 3;
			}
			flag = true;
			break;
		case GLUT_KEY_RIGHT:
			lon++;
			flag = true;
			break;
		default:
			break;
		}

		if (flag)
		{
			Convex* pOld = pConvex;
			pConvex = generator->generate(lon, lat);
			printf("longitudes: %d, latitudes:%d\n", lon, lat);
			printf("vertices: %d, triangles: %d\n", pConvex->getNumVertices(), pConvex->getNumTriangles());
			delete pOld;
		}
	}

	virtual void onTimer()
	{
		//static int count = 0;
		//printf("Alarm %d!\n", count++);
		angle += 1.0;
		glutPostRedisplay();
	}

	virtual void onInit()
	{
		angle = 0;
		depth = 1;
		printf("OnInit\n");

		generator = new SphereGenerator(3, 1);
		pConvex = generator->generate();

		printf("vertices: %d, triangles: %d\n", pConvex->getNumVertices(), pConvex->getNumTriangles());

		//pConvex->serialize("convex1.obj");

		glClearColor(0.0, 0.0, 0.0, 0.0);
		//glShadeModel(GL_SMOOTH);

		glEnable(GL_DEPTH_TEST);

		glPolygonMode(GL_FRONT, GL_LINE);
		// glPolygonMode(GL_FRONT, GL_FILL);
		//glPolygonMode(GL_BACK, GL_LINE); // 背面显示线条
		glCullFace(GL_BACK); // 剔除背面
	}

	void onResize(int w, int h)
	{
		glViewport(0, 0, w, h);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluOrtho2D(-2.0 * w / h, 2.0 * w / h, -2.0, 2.0);

		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity(); // 不能少
	}

	virtual void onDisplay()
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glTranslatef(0, -0.5, 0);
		glRotated(angle, 0, 1, 0); // 旋转
		//glRotated(angle, 1, 1, 1);

		pConvex->render();

		//glutPostRedisplay();
	}
};

int main(int argc, char **argv)
{
	GlutApp* app = new DemoSphereGen();

	app->initGlut(argc, argv);
	app->setTitle("test Regular convex generator\n");
	app->setWindowsSize(600, 600);

	app->setTimer(1000, 100);

	app->run();

	return 0;
}

上图:


5经线3纬线



9经线5纬线



15经线9纬线


其余源码

GlutApp.h
#ifndef GLUT_APP_H
#define GLUT_APP_H

class GlutApp 
{
public:
	typedef void (*MenuFuncPtr)(void);

	struct MenuEntry 
	{
		int id;
		const char* str;
		MenuFuncPtr fun;
	};

	// 当前 App 实例指针,指向子类实例
	static GlutApp* s_pCurrentApp;

	// 右键菜单 项数最大值
	static const int MAX_MENU = 32;

	// ctor
	GlutApp();

	// getter and setters:
	static void initGlut(int argc, char** argv) { s_argc = argc; s_argv = argv; }

	void setDisplayMode(unsigned int mode) { m_displayMode = mode; }

	void setWindowsSize(int w, int h) { m_winWidth = w; m_winHeight = h; }

	int getWindowWidth() { return m_winWidth; }

	int getWindowHeight() { return m_winHeight; }

	void setWindowsPos(int x, int y) { m_winPosX = x; m_winPosY = y; }

	void setTitle(char *title) { m_title = title; }

	void run();

	void addRightMenu(const char *str, MenuFuncPtr fun);

	// 初始化
	virtual void onInit(){}

	//
	// GLUT delegate callbacks:

	// 空闲函数
	virtual void onIdle(){}

	// 图形显示(OpenGL绘图指令)
	virtual void onDisplay() = 0; // 子类必须重写;不能实例化该类

	// 窗口大小改变
	virtual void onResize(int w, int h){}

	//
	// 键盘事件响应 方法:

	// 一般按键(可打印字符,ESC)
	virtual void onKey(unsigned char key, int x, int y){}

	// 一般按键 按下
	virtual void onKeyDown(unsigned char key, int x, int y) {}

	// 特殊按键(除一般按键外按键)
	virtual void onSpecialKey(int key, int x, int y){}

	// 特殊按键按下
	virtual void onSpecialKeyDown(int key, int x, int y){}

	//
	// 鼠标事件响应 方法:

	// 鼠标按键
	//! @param button: The button parameter is one of GLUT LEFT BUTTON, GLUT MIDDLE BUTTON, or GLUT RIGHT BUTTON.
	//! @param state: The state parameter is either GLUT UP or GLUT DOWN indicating 
	//                 whether the callback was due to a release or press respectively.
	virtual void onMousePress(int button, int state, int x, int y){}

	// 鼠标移动
	virtual void onMouseMove(int x, int y){}

	// 鼠标拖动
	virtual void onMousePressMove(int x,int y){}

	//
	// 定时器相关 方法:
	virtual void onTimer() {}

	void setTimer(int delay, int period = 0);

protected:
	void registerMenus();

	// actual GLUT callback functions:
	static void KeyboardCallback(unsigned char key, int x, int y);

	static void KeyboardUpCallback(unsigned char key, int x, int y);

	static void SpecialKeyboardCallback(int key, int x, int y);

	static void SpecialKeyboardUpCallback(int key, int x, int y);

	static void ReshapeCallback(int w, int h);

	static void IdleCallback();

	static void MouseFuncCallback(int button, int state, int x, int y);

	static void	MotionFuncCallback(int x,int y);

	static void MousePassiveCallback(int x, int y);

	static void DisplayCallback();

	static void MenuCallback(int menuId);
	
	static void TimerCallback(int period);
private:
	unsigned int m_displayMode;

	// for glutInit
	static int s_argc;
	static char** s_argv;

	char *m_title;

	// for glutSetWindowSize
	int m_winWidth;
	int m_winHeight;

	// for windows position
	int m_winPosX;
	int m_winPosY;

	// for menus:
	int       m_menuCount;
	MenuEntry m_menuEntry[MAX_MENU];

	// for timer:
	int m_delay;
	int m_period;
};

#endif // GLUT_APP_H

GlutApp.cpp
#include <gl/glut.h>
#include <assert.h>
#include <stdio.h>

#include "GlutApp.h"

int GlutApp::s_argc = 0;

char** GlutApp::s_argv = 0;

GlutApp* GlutApp::s_pCurrentApp = 0;

int g_iLastWindow = 0;

void GlutApp::run()
{
	GlutApp* lastApp = GlutApp::s_pCurrentApp;
	GlutApp::s_pCurrentApp = this;

	GlutApp* app = GlutApp::s_pCurrentApp;
	assert(app);

	int screenW = glutGet(GLUT_SCREEN_WIDTH);
	int screenH = glutGet(GLUT_SCREEN_HEIGHT);

	if (!app->m_winWidth)
	{
		app->m_winWidth = screenW / 2;
		app->m_winHeight = screenH / 2;
	}
	
	if (!app->m_winPosX)
	{
		app->m_winPosX = (screenW - app->m_winWidth) / 2;
		app->m_winPosY = (screenH - app->m_winHeight) / 2;
	}

	if (!lastApp) // first time calling Glut::run().
	{
		// glutInit that should only be called exactly once in a GLUT program.
		glutInit(&this->s_argc, this->s_argv);

		glutInitDisplayMode(this->m_displayMode);
		glutInitWindowPosition(app->m_winPosX, app->m_winPosY);
		glutInitWindowSize(app->m_winWidth, app->m_winHeight);

		glutCreateWindow(app->m_title);
		g_iLastWindow = glutGetWindow();
		printf("create window: %d\n", g_iLastWindow); // debug [6/2/2014 xu]
	}
	else
	{
		glutDestroyWindow(g_iLastWindow);

		glutInitDisplayMode(this->m_displayMode);
		glutInitWindowPosition(app->m_winPosX, app->m_winPosY);
		glutInitWindowSize(app->m_winWidth, app->m_winHeight);

		glutCreateWindow(app->m_title);
		g_iLastWindow = glutGetWindow();
		printf("create window: %d\n", g_iLastWindow); // debug [6/2/2014 xu]
	}

	app->onInit();

	// register keyboard callbacks
	glutKeyboardFunc(GlutApp::KeyboardCallback);
	glutKeyboardUpFunc(GlutApp::KeyboardUpCallback);
	glutSpecialFunc(GlutApp::SpecialKeyboardCallback);
	glutSpecialUpFunc(GlutApp::SpecialKeyboardUpCallback);

	// register mouse callbacks
	glutMouseFunc(GlutApp::MouseFuncCallback);
	glutMotionFunc(GlutApp::MotionFuncCallback);
	glutPassiveMotionFunc(GlutApp::MousePassiveCallback);

	// register menus:
	registerMenus();

	// regitser windows resize callback
	glutReshapeFunc(GlutApp::ReshapeCallback);

	// register render callback
	glutDisplayFunc(GlutApp::DisplayCallback);

	// register timer callbacks:
	if (app->m_delay)
	{
		glutTimerFunc(app->m_delay, GlutApp::TimerCallback, app->m_period);
	}

	// register idle callback
	glutIdleFunc(GlutApp::IdleCallback);

	GlutApp::IdleCallback();

	glutMainLoop();
}

GlutApp::GlutApp()
{
	m_displayMode = GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL;
	m_menuCount = 0;
	m_delay = 0;
	m_period = 0;

	m_winPosX = 0;
	m_winPosY = 0;
	m_winWidth = 0;
	m_winHeight = 0;
}

void GlutApp::KeyboardCallback( unsigned char key, int x, int y )
{
	GlutApp::s_pCurrentApp->onKey(key,x,y);
}

void GlutApp::KeyboardUpCallback( unsigned char key, int x, int y )
{
	GlutApp::s_pCurrentApp->onKeyDown(key,x,y);
}

void GlutApp::SpecialKeyboardCallback( int key, int x, int y )
{
	GlutApp::s_pCurrentApp->onSpecialKey(key,x,y);
}

void GlutApp::SpecialKeyboardUpCallback( int key, int x, int y )
{
	GlutApp::s_pCurrentApp->onSpecialKeyDown(key,x,y);
}

void GlutApp::ReshapeCallback( int w, int h )
{
	GlutApp::s_pCurrentApp->setWindowsSize(w, h);
	GlutApp::s_pCurrentApp->onResize(w,h);
}

void GlutApp::IdleCallback()
{
	GlutApp::s_pCurrentApp->onIdle();
}

void GlutApp::MouseFuncCallback( int button, int state, int x, int y )
{
	GlutApp::s_pCurrentApp->onMousePress(button,state,x,y);
}

void GlutApp::MotionFuncCallback( int x,int y )
{
	GlutApp::s_pCurrentApp->onMousePressMove(x,y);
}

void GlutApp::MousePassiveCallback( int x, int y )
{
	GlutApp::s_pCurrentApp->onMouseMove(x, y);
}

void GlutApp::DisplayCallback( void )
{
	GlutApp::s_pCurrentApp->onDisplay();
}

void GlutApp::addRightMenu( const char *str, MenuFuncPtr fun )
{
	m_menuEntry[m_menuCount].id = m_menuCount;
	m_menuEntry[m_menuCount].str = str;
	m_menuEntry[m_menuCount].fun = fun;
	m_menuCount++;
}

void GlutApp::registerMenus()
{
	if (m_menuCount > 0)
	{
		glutCreateMenu(GlutApp::MenuCallback);
		for (int i=0; i<m_menuCount; ++i)
		{
			glutAddMenuEntry(m_menuEntry[i].str, m_menuEntry[i].id);
		}
		glutAttachMenu(GLUT_RIGHT_BUTTON);
	}
}

void GlutApp::MenuCallback( int menuId )
{
	for (int i=0; i<GlutApp::s_pCurrentApp->m_menuCount; ++i)
	{
		if (menuId == GlutApp::s_pCurrentApp->m_menuEntry[i].id)
		{
			GlutApp::s_pCurrentApp->m_menuEntry[i].fun();
		}
	}
}

void GlutApp::setTimer( int delay, int period )
{
	this->m_delay = delay;
	this->m_period = period;
}

void GlutApp::TimerCallback( int period )
{
	// printf("Timer Alarm!\n");
	GlutApp::s_pCurrentApp->onTimer();
	if (period)
	{
		glutTimerFunc(period, GlutApp::TimerCallback, period);
	}
}

ShpereGennerator.h
#ifndef SPHERE_GENERATOR_H
#define SPHERE_GENERATOR_H

#include "Convex.h"

class SphereGenerator
{
public:
	typedef Convex::Float Float;
	typedef Convex::Point Point;
	typedef Convex::Triangle Triangle;

	SphereGenerator(void);

	SphereGenerator(int longitudes, int latitudes, Float radius=1.0f);

	Convex* generate(int longitudes, int latitudes, Float radius=1.0f);

	Convex* generate();

	int getLongitude() { return m_longitudes; }

	int getLatitude() { return m_latitudes; }
private:
	Float m_radius;
	int m_longitudes; // 经线数
	int m_latitudes; // 纬线数
};

#endif

SphereGenerator.cpp
#include "SphereGenerator.h"

#include <vector>
#include <math.h>

#include <glm/glm.hpp>

#define M_PI       3.14159265358979323846

SphereGenerator::SphereGenerator(void)
{
	m_radius = 1.0;
	m_longitudes = 3;
	m_latitudes = 1;
}

SphereGenerator::SphereGenerator(int longitudes, int latitudes, Float radius)
{
	m_radius = radius;
	m_longitudes = longitudes;
	m_latitudes = latitudes;
}

Convex* SphereGenerator::generate(int longitudes, int latitudes, Float radius)
{
	m_radius = radius;
	m_longitudes = longitudes;
	m_latitudes = latitudes;

	return generate();
}

Convex* SphereGenerator::generate()
{
	Convex* pConvex = new Convex();

	assert(m_latitudes >= 1);
	assert(m_longitudes >= 3);

	Point northPole(0, m_radius, 0);
	Point southPole(0, -m_radius, 0);

	int iNorth = pConvex->addVertex(northPole);
	int iSouth = pConvex->addVertex(southPole);

	double lonDelta = 2*M_PI / m_longitudes;
	double latDelta = M_PI / (m_latitudes+1);

	std::vector< std::vector<int> > vertIndices;
	vertIndices.resize(m_latitudes);

	// 计算所有顶点
	for (int lat=0; lat<m_latitudes; ++lat)
	{
		vertIndices[lat] = std::vector<int>(size_t(m_longitudes));
		Float y = m_radius * glm::sin(M_PI/2 - (lat+1)*latDelta);
		Float r = m_radius * glm::cos(M_PI/2 - (lat+1)*latDelta); // important!!
		for (int i=0; i<m_longitudes; ++i)
		{
			Point pt(
				r * glm::cos(i * lonDelta),
				y,
				-r * glm::sin(i * lonDelta)
				);
			vertIndices[lat][i] = pConvex->addVertex(pt);
		}
	}

	// 连接南北两极附近三角形面
	for (int i=0; i<m_longitudes; ++i)
	{
		int next = i+1 < m_longitudes ? i+1 : 0;
		Triangle triN(vertIndices[0][i], vertIndices[0][next], iNorth);
		pConvex->addTriangle(triN);

		Triangle triS(vertIndices[m_latitudes-1][i], vertIndices[m_latitudes-1][next], iSouth);
		pConvex->addTriangle(triS);
	}

	// 连接中间的三角形面
	if (m_latitudes >= 2)
	{
		for (int lat=0; lat<m_latitudes-1; ++lat)
		{
			int nextLat = lat+1;
			for (int i=0; i<m_longitudes; ++i)
			{
				int nextI = i+1 < m_longitudes ? i+1 : 0;
				int A = vertIndices[lat][i];
				int B = vertIndices[nextLat][i];
				int C = vertIndices[nextLat][nextI];
				int D = vertIndices[lat][nextI];
				pConvex->addTriangle(Triangle(A, B, D));
				pConvex->addTriangle(Triangle(B, C, D));
			}
		}
	}
	return pConvex;
}

Convex.h
#ifndef CONVEX_H
#define CONVEX_H

#include <vector>

#include <glm/glm.hpp>

class Convex
{
public:
	typedef float      Float;
	typedef glm::vec3  Point;
	typedef glm::uvec3 Triangle;

	// ctor and dtor
	Convex(void);
	~Convex(void);

	// 
	int addVertex(const Point& vert);

	int addTriangle(const Triangle& tria);

	void clear();

	// getters:
	const Point& getPos() const { return m_position; }

	int getNumVertices() const { return m_numVertices; }

	int getNumTriangles() const { return m_numTriangles; }

	const Triangle& getTriangle(int triIdx) const { return m_triangles[triIdx]; }

	Point getVertex(int vIndex) const { return m_vertices[vIndex]; }

	void setPos(const Point& pos) { m_position = pos; }

	void setVertex(const Point& vert, int vIndex) { m_vertices[vIndex] = vert; }

	// show
	void render();

	// to obj file
	void serialize(const char* filename);
private:
	int m_numVertices;
	int m_numTriangles;

	Point m_position;

	std::vector<Point> m_vertices;
	std::vector<Triangle> m_triangles;
};

#endif // CONVEX_H

Convex.cpp
#include "Convex.h"

#include <windows.h>
#include <gl/GL.h>
#include <gl/glut.h>

#include <fstream>

Convex::Convex(void)
{
	m_numVertices = 0;
	m_numTriangles = 0;
}

Convex::~Convex(void)
{
}

int Convex::addVertex( const Point& vert )
{
	m_vertices.push_back(vert);
	return m_numVertices++;
}

int Convex::addTriangle( const Triangle& tria )
{
	m_triangles.push_back(tria);
	return m_numTriangles++;
}

void Convex::clear()
{
	m_vertices.clear();
	m_triangles.clear();
	m_numVertices = 0;
	m_numTriangles = 0;
}

void Convex::render()
{
	//glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();

	glTranslatef(m_position.x, m_position.y, m_position.z);

	// glPolygonMode(GL_FRONT, GL_LINE); // 更改多边形绘制形
#if 0
	glBegin(GL_TRIANGLES); // 开始绘图
	for (int i=0; i<m_numTriangles; ++i)
	{
		int idx0 = m_triangles[i][0];
		int idx1 = m_triangles[i][1];
		int idx2 = m_triangles[i][2];

		glColor3fv((float*)&m_vertices[idx0]);
		glVertex3fv((float*)&m_vertices[idx0]);

		glColor3fv((float*)&m_vertices[idx1]);
		glVertex3fv((float*)&m_vertices[idx1]);

		glColor3fv((float*)&m_vertices[idx2]);
		glVertex3fv((float*)&m_vertices[idx2]);
	}
	glEnd(); // 结束绘图
#else
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 0, &m_vertices[0]);

	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(3, GL_FLOAT, 0, &m_vertices[0]);
	
#if 1
	glBegin(GL_TRIANGLES);
	for (int i=0; i<m_numTriangles; ++i)
	{
		glArrayElement(m_triangles[i][0]);
		glArrayElement(m_triangles[i][1]);
		glArrayElement(m_triangles[i][2]);
	}
	glEnd();
	// glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, &m_triangles[0]);
#endif

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
#endif
	// glPolygonMode(GL_FRONT, GL_FILL);

	glFlush();
	glutSwapBuffers();
}


void Convex::serialize( const char* filename )
{
	int vtNum = this->m_vertices.size();
	int faceN = this->m_triangles.size();

#if 1
	FILE* fout = NULL;
	fout = fopen(filename, "w");

	if(fout == NULL) 
	{
		fprintf(stderr, "file %s open failed!\n", filename);
		return ;
	}

	fprintf(fout, 
		"# serialized Convex data file.\n"
		"# It is simplest .obj file.\n"
		);
	fprintf(fout, "# number of vertices: %d\n", vtNum);
	fprintf(fout, "# number of triangles: %d\n\n", faceN);

	fprintf(fout, "# vertices:\n");
	for (int i=0; i<vtNum; ++i)
	{
		fprintf(fout, "v %g %g %g\n", m_vertices[i][0], m_vertices[i][1], m_vertices[i][2]);
	}

	fprintf(fout, "\n# faces:\n");
	for (int i=0; i<faceN; ++i)
	{
		fprintf(fout, "f %d %d %d\n", m_triangles[i][0]+1, m_triangles[i][1]+1, m_triangles[i][2]+1);
	}

	fclose(fout);
#endif
}


  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码工许师傅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值