规则多面体生成算法,算法本身并不复杂。开始想百度一份的,结果没百度到。贴出来,希望以后有用得到的同学可在直接拿去用。
算法过程
- 根据经纬线数目求出多面体表面所有点的坐标;
- 连接南北极附近的三角形面;
- 连接中间的四边形(或两个三角形);
算法实现
下面是该算法的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
}