这里用到的Mesh数据结构如果不清楚可以参照我的上一篇。
参考了一个牛人的文章, 他的代码是C#的,我用C++改写了一遍,为了便于理解,省去了他的cache优化。
一般3D球体的建模有两种Mesh可以选择,UVSphere和Iconsphere
左图为UVSphere,右图为Iconsphere。在一些情况下UVphere有很好的表现,但是,在另一些情境中,比如改变球体形状时,UVSphere两极高密度的顶点给变换带来了麻烦。这时Iconsphere这样顶点分布平均的方式就很受欢迎。
Iconsphere的生成基于一个由20个等边三角形组成的20面体,将三角形每个边2等分,它们的连线将一个三角形分为4个等边三角形,再对生成的小三角形重复以上步骤。
维基百科有一个很好的20面体的图片表示
创建顶点列表很直接:
float t = (1.0 + sqrt(5.0)) / 2.0;
mesh->points.push_back(new Point3D(-1, t, 0));
mesh->points.push_back(new Point3D(1, t, 0));
mesh->points.push_back(new Point3D(-1, -t, 0));
mesh->points.push_back(new Point3D(1, -t, 0));
mesh->points.push_back(new Point3D(0, -1, t));
mesh->points.push_back(new Point3D(0, 1, t));
mesh->points.push_back(new Point3D(0, -1, -t));
mesh->points.push_back(new Point3D(0, 1, -t));
mesh->points.push_back(new Point3D(t, 0, -1));
mesh->points.push_back(new Point3D(t, 0, 1));
mesh->points.push_back(new Point3D(-t, 0, -1));
mesh->points.push_back(new Point3D(-t, 0, 1));
我按照每个顶点加入数组的顺序给图片标了号:
加入每个面中的顶点都要严格按照既定顺序,我使用从物体外部观察的逆时针方向,接下来只要小心地将points加入没个Face中:
int ni = 0;
Normal* temp = NULL;
temp = new Normal(mesh->points[0], mesh->points[11], mesh->points[5]);
mesh->faces.push_back(new Face(0, 11, 5, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[5], mesh->points[1]);
mesh->faces.push_back(new Face(0, 5, 1, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[1], mesh->points[7]);
mesh->faces.push_back(new Face(0, 1, 7, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[7], mesh->points[10]);
mesh->faces.push_back(new Face(0, 7, 10, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[10], mesh->points[11]);
mesh->faces.push_back(new Face(0, 10, 11, ni));
mesh->normals.push_back(temp);
++ni;
// 5 adjacent faces
temp = new Normal(mesh->points[1], mesh->points[5], mesh->points[9]);
mesh->faces.push_back(new Face(1, 5, 9, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[5], mesh->points[11], mesh->points[4]);
mesh->faces.push_back(new Face(5, 11, 4, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[11], mesh->points[10], mesh->points[2]);
mesh->faces.push_back(new Face(11, 10, 2, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[10], mesh->points[7], mesh->points[6]);
mesh->faces.push_back(new Face(10, 7, 6, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[7], mesh->points[1], mesh->points[8]);
mesh->faces.push_back(new Face(7, 1, 8, ni));
mesh->normals.push_back(temp);
++ni;
// 5 faces around point 3
temp = new Normal(mesh->points[3], mesh->points[9], mesh->points[4]);
mesh->faces.push_back(new Face(3, 9, 4, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[4], mesh->points[2]);
mesh->faces.push_back(new Face(3, 4, 2, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[2], mesh->points[6]);
mesh->faces.push_back(new Face(3, 2, 6, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[6], mesh->points[8]);
mesh->faces.push_back(new Face(3, 6, 8, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[8], mesh->points[9]);
mesh->faces.push_back(new Face(3, 8, 9, ni));
mesh->normals.push_back(temp);
++ni;
// 5 adjacent faces
temp = new Normal(mesh->points[4], mesh->points[9], mesh->points[5]);
mesh->faces.push_back(new Face(4, 9, 5, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[2], mesh->points[4], mesh->points[11]);
mesh->faces.push_back(new Face(2, 4, 11, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[6], mesh->points[2], mesh->points[10]);
mesh->faces.push_back(new Face(0, 1, 7, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[8], mesh->points[6], mesh->points[7]);
mesh->faces.push_back(new Face(8, 6, 7, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[9], mesh->points[8], mesh->points[1]);
mesh->faces.push_back(new Face(9, 8, 1, ni));
mesh->normals.push_back(temp);
++ni;
接下来将三角形分成4个,但是不能仅仅用Xnew’ = (X1 + X2) / 2线性生成坐标,要确保新生成的点在球面上。线性生成的点记为Nnew’,length’=|Nnew’|。所求最终的点记为Nnew,(length=|Nnew|)==R,那么将Nnew’再做一次线性变换就可以得到Nnew。
// refine triangles
for (int i = 0; i < recursionLevel; i++)
{
vector<Face*> faces2;
for(int j = 0; j < mesh->faces.size(); ++j)
{
// replace triangle by 4 triangles
Face* f = mesh->faces[j];
int a = getMiddlePoint(mesh->faces[j]->vert[0]->vertIndex, mesh->faces[j]->vert[1]->vertIndex);
int b = getMiddlePoint(mesh->faces[j]->vert[1]->vertIndex, mesh->faces[j]->vert[2]->vertIndex);
int c = getMiddlePoint(mesh->faces[j]->vert[2]->vertIndex, mesh->faces[j]->vert[0]->vertIndex);
temp = new Normal(mesh->points[f->vert[0]->vertIndex], mesh->points[a], mesh->points[c]);
faces2.push_back(new Face(f->vert[0]->vertIndex, a, c, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[f->vert[1]->vertIndex], mesh->points[b], mesh->points[a]);
faces2.push_back(new Face(f->vert[1]->vertIndex, b, a, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[f->vert[2]->vertIndex], mesh->points[c], mesh->points[b]);
faces2.push_back(new Face(f->vert[2]->vertIndex, c, b, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[a], mesh->points[b], mesh->points[c]);
faces2.push_back(new Face(a, b, c, ni));
mesh->normals.push_back(temp);
++ni;
}
mesh->faces = faces2;
}
// add vertex to mesh, fix position to be on unit sphere, //return index
int addVertex(Point3D* p)
{
double length = sqrt(p->X * p->X + p->Y * p->Y + p->Z * p->Z);
mesh->points.push_back(new Point3D(p->X / length * 1.902, p->Y / length * 1.902, p->Z / length * 1.902));
return mesh->points.size() - 1;
}
// return index of point in the middle of p1 and p2
int getMiddlePoint(int p1, int p2)
{
// first check if we have it already
int ret;
// not in cache, calculate it
Point3D *point1 = mesh->points[p1];
Point3D *point2 = mesh->points[p2];
Point3D *middle = new Point3D(
(point1->X + point2->X) / 2.0,
(point1->Y + point2->Y) / 2.0,
(point1->Z + point2->Z) / 2.0);
// add vertex makes sure point is on unit sphere
int i = addVertex(middle);
return i;
}
完整代码如下:
#pragma once
#include "TriangleIndices.h"
#include "Point3D.h"
#include "Mesh.h"
#include <math.h>
using namespace std;
class IcoSphereCreator
{
public:
Mesh* mesh = new Mesh();
int index;
// return index of point in the middle of p1 and p2
/*int getMiddlePoint(int p1, int p2)
{
// first check if we have it already
bool firstIsSmaller = p1 < p2;
int smallerIndex = firstIsSmaller ? p1 : p2;
int greaterIndex = firstIsSmaller ? p2 : p1;
int key = (smallerIndex << 32) + greaterIndex;
int ret;
if (this.middlePointIndexCache.TryGetValue(key, out ret))
{
return ret;
}
// not in cache, calculate it
Point3D point1 = this.geometry.Positions[p1];
Point3D point2 = this.geometry.Positions[p2];
Point3D middle = new Point3D(
(point1.X + point2.X) / 2.0,
(point1.Y + point2.Y) / 2.0,
(point1.Z + point2.Z) / 2.0);
// add vertex makes sure point is on unit sphere
int i = addVertex(middle);
// store it, return index
this.middlePointIndexCache.Add(key, i);
return i;
}*/
Mesh* Create(int recursionLevel)
{
index = 0;
// create 12 vertices of a icosahedron
float t = (1.0 + sqrt(5.0)) / 2.0;
mesh->points.push_back(new Point3D(-1, t, 0));
mesh->points.push_back(new Point3D(1, t, 0));
mesh->points.push_back(new Point3D(-1, -t, 0));
mesh->points.push_back(new Point3D(1, -t, 0));
mesh->points.push_back(new Point3D(0, -1, t));
mesh->points.push_back(new Point3D(0, 1, t));
mesh->points.push_back(new Point3D(0, -1, -t));
mesh->points.push_back(new Point3D(0, 1, -t));
mesh->points.push_back(new Point3D(t, 0, -1));
mesh->points.push_back(new Point3D(t, 0, 1));
mesh->points.push_back(new Point3D(-t, 0, -1));
mesh->points.push_back(new Point3D(-t, 0, 1));
// create 20 triangles of the icosahedron
//vector<Face> faces;
// 5 faces around point 0
int ni = 0;
Normal* temp = NULL;
temp = new Normal(mesh->points[0], mesh->points[11], mesh->points[5]);
mesh->faces.push_back(new Face(0, 11, 5, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[5], mesh->points[1]);
mesh->faces.push_back(new Face(0, 5, 1, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[1], mesh->points[7]);
mesh->faces.push_back(new Face(0, 1, 7, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[7], mesh->points[10]);
mesh->faces.push_back(new Face(0, 7, 10, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[0], mesh->points[10], mesh->points[11]);
mesh->faces.push_back(new Face(0, 10, 11, ni));
mesh->normals.push_back(temp);
++ni;
// 5 adjacent faces
temp = new Normal(mesh->points[1], mesh->points[5], mesh->points[9]);
mesh->faces.push_back(new Face(1, 5, 9, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[5], mesh->points[11], mesh->points[4]);
mesh->faces.push_back(new Face(5, 11, 4, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[11], mesh->points[10], mesh->points[2]);
mesh->faces.push_back(new Face(11, 10, 2, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[10], mesh->points[7], mesh->points[6]);
mesh->faces.push_back(new Face(10, 7, 6, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[7], mesh->points[1], mesh->points[8]);
mesh->faces.push_back(new Face(7, 1, 8, ni));
mesh->normals.push_back(temp);
++ni;
// 5 faces around point 3
temp = new Normal(mesh->points[3], mesh->points[9], mesh->points[4]);
mesh->faces.push_back(new Face(3, 9, 4, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[4], mesh->points[2]);
mesh->faces.push_back(new Face(3, 4, 2, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[2], mesh->points[6]);
mesh->faces.push_back(new Face(3, 2, 6, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[6], mesh->points[8]);
mesh->faces.push_back(new Face(3, 6, 8, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[3], mesh->points[8], mesh->points[9]);
mesh->faces.push_back(new Face(3, 8, 9, ni));
mesh->normals.push_back(temp);
++ni;
// 5 adjacent faces
temp = new Normal(mesh->points[4], mesh->points[9], mesh->points[5]);
mesh->faces.push_back(new Face(4, 9, 5, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[2], mesh->points[4], mesh->points[11]);
mesh->faces.push_back(new Face(2, 4, 11, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[6], mesh->points[2], mesh->points[10]);
mesh->faces.push_back(new Face(0, 1, 7, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[8], mesh->points[6], mesh->points[7]);
mesh->faces.push_back(new Face(8, 6, 7, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[9], mesh->points[8], mesh->points[1]);
mesh->faces.push_back(new Face(9, 8, 1, ni));
mesh->normals.push_back(temp);
++ni;
// refine triangles
for (int i = 0; i < recursionLevel; i++)
{
vector<Face*> faces2;
for(int j = 0; j < mesh->faces.size(); ++j)
{
// replace triangle by 4 triangles
Face* f = mesh->faces[j];
int a = getMiddlePoint(mesh->faces[j]->vert[0]->vertIndex, mesh->faces[j]->vert[1]->vertIndex);
int b = getMiddlePoint(mesh->faces[j]->vert[1]->vertIndex, mesh->faces[j]->vert[2]->vertIndex);
int c = getMiddlePoint(mesh->faces[j]->vert[2]->vertIndex, mesh->faces[j]->vert[0]->vertIndex);
temp = new Normal(mesh->points[f->vert[0]->vertIndex], mesh->points[a], mesh->points[c]);
faces2.push_back(new Face(f->vert[0]->vertIndex, a, c, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[f->vert[1]->vertIndex], mesh->points[b], mesh->points[a]);
faces2.push_back(new Face(f->vert[1]->vertIndex, b, a, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[f->vert[2]->vertIndex], mesh->points[c], mesh->points[b]);
faces2.push_back(new Face(f->vert[2]->vertIndex, c, b, ni));
mesh->normals.push_back(temp);
++ni;
temp = new Normal(mesh->points[a], mesh->points[b], mesh->points[c]);
faces2.push_back(new Face(a, b, c, ni));
mesh->normals.push_back(temp);
++ni;
}
mesh->faces = faces2;
}
return mesh;
}
// add vertex to mesh, fix position to be on unit sphere, return index
int addVertex(Point3D* p)
{
double length = sqrt(p->X * p->X + p->Y * p->Y + p->Z * p->Z);
mesh->points.push_back(new Point3D(p->X / length * 1.902, p->Y / length * 1.902, p->Z / length * 1.902));
return mesh->points.size() - 1;
}
// return index of point in the middle of p1 and p2
int getMiddlePoint(int p1, int p2)
{
// first check if we have it already
int ret;
// not in cache, calculate it
Point3D *point1 = mesh->points[p1];
Point3D *point2 = mesh->points[p2];
Point3D *middle = new Point3D(
(point1->X + point2->X) / 2.0,
(point1->Y + point2->Y) / 2.0,
(point1->Z + point2->Z) / 2.0);
// add vertex makes sure point is on unit sphere
int i = addVertex(middle);
return i;
}
};