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

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

算法过程

  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][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;
}

演示程序

使用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
}

类球多面体生成——经纬划分法,布布扣,bubuko.com

时间: 2024-10-07 01:23:42

类球多面体生成——经纬划分法的相关文章

:after伪类+content内容生成经典应用举例——张鑫旭

一.简单说说content内容生成 content内容生成就是通过content属性生成内容,content属性早在CSS2.1的时候就被引入了,可以使用:before以及:after伪元素生成内容.此特性目前已被大部分的浏览器支持: (Firefox 1.5+, Safari 3.5+, IE 8+, Opera 9.2+, Chrome 0.2+).另外,目前Opera 9.5+ 和 Safari 4已经支持所有元素的content属性,而不仅仅是:before和:after伪元素. 例如下

等价类划分法

等价类划分法设计测试用例完全不考虑程序内部结构,只需要根据需求规格说明书,对其输入和输出的要求提取区分出来即划分等价类,然后列出等价类表 等价类划分采用的办法就是:把程序的输入域划分成若干部门,然后从每个部分中选取少数代表性数据作为测试用例 等价类划分结果有两种:有效等价类(对于需求规格来说合理的数据集合).无效等价类(对于需求规格来说异常的数据集合) 等价类划分6条确认原则: 1:输入条件规定了取值范围或值的个数情况下,可以确立一个有效等价类和两个无效等价类 a:输入条件规定了取值范围等价类表

通用的树型类,可以生成任何树型结构

<?php namespace Vendor\Tree; /** * 通用的树型类,可以生成任何树型结构 */ class Tree { /** * 生成树型结构所需要的2维数组 * @var array */ public $arr = array(); /** * 生成树型结构所需修饰符号,可以换成图片 * @var array */ public $icon = array('│', '├', '└'); public $nbsp = " "; private $str =

:after伪类+content内容生成经典应用举例

一.简单说说content内容生成 content内容生成就是通过content属性生成内容,content属性早在CSS2.1的时候就被引入了,可以使用:before以 及:after伪元素生成内容.此特性目前已被大部分的浏览器支持: (Firefox 1.5+, Safari 3.5+, IE 8+, Opera 9.2+, Chrome 0.2+).另外,目前Opera 9.5+ 和 Safari 4已经支持所有元素的content属性,而不仅仅是:before和:after伪元素. 例如

黑盒测试用例设计技术--等价类划分法

本文通过案例的形式,详细讲解黑盒测试用例设计技术中的等价类划分法. 等价类划分是一种典型的黑盒测试方法,其原理是把程序的输入域划分成若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例. 通过等价类划分,可以在尽可能覆盖所有测试路径的前提下,大幅度减少测试用例的数目. 本文的主要内容有: 等价类的概念介绍 划分等价类的原则 根据等价类设计测试用例的方法 案例演示 划分等价类 等价类是指某个输入域的子集合.在该子集合中,各个输入数据对于揭露程序中的错误都是等效的.并合理的假设,

PowerDesigner(八)-面向对象模型(用例图,序列图,类图,生成Java源代码及Java源代码生成类图)(转)

面向对象模型 面向对象模型是利用UML(统一建模语言)的图形来描述系统结构的模型,它从不同角度实现系统的工作状态.这些图形有助于用户,管理人员,系统分析人员,开发人员,测试人员和其他人员之间进行信息交流.这里主要介绍用例图,序列图和类图.   1.面向对象模型OOM 面向对象模型是利用UML的图形描述系统结构的模型,可以利用PowerDesigner的面向对象模型进行创建.PowerDesigner支持UML的下列图形. 用例图(User Case Diagram):通常用来定义系统的高层次草图

软件测试方法-等价类划分法

等价类划分法 等价类划分法是测试工作中频繁使用的方法,每一步的测试工作都与它密切相关,对这个方法的深入理解,以及灵活使用是软件测试工作的基础 等价类划分法是把所有的可能的输入数据,即程序的输入域划分为若干部分(子集),然后从每一个子集中选取少量具有代表性的数据作为测试用例 等价类是指某个输入域的子集盒.在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,可以合理的假定:测试某等价类的代表值就等于对这一类其他值的测试. 等价类划分有两种不同的情况:有效等价类和无效等价类.设计时要同时考虑这两

等价类划分法设计测试用例

序内部结构,设计测试用例的唯一依据是软件需求规格说明书. 等价类 所谓等价类,是输入条件的一个子集合,该输入集合中的数据对于揭示程序中的错误是等价的.等价类又分为有效等价类和无效等价类.有效等价类代表对程序有效的输入,而无效等价类则是其他任何可能的输入(即不正确的输入值).有效等价类和无效等价类都是使用等价类划分法设计用例时所必须的,因为被测程序若是正确的,就应该既能接受有效的输入,也能接受无效输入的考验. 划分等价类的标准: 1.完备测试.避免冗余; 2.划分等价类重要的是:集合的划分,划分为

Mybitis根据工具类反射数据库生成映射+整合springboot

一 反向生成数据库mapper的工具类: 添加依赖 <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <scope>test</scope> <version>1.3.2</version> <optional>true</opt