前回ICO球の作り方をやったので、今回はそれをC++で実装する。
https://suzulang.com/cpp-code-ico-q-1/
#pragma warning(disable:4996) #include <GL/glut.h> #include <fstream> #include <sstream> #include <vector> #include <algorithm> #include <unordered_map> #include <array>//! @brief 三次元の座標値 struct xyz { double x, y, z; xyz() {} xyz(const double X, const double Y, const double Z) : x(X), y(Y), z(Z) {} }; //! @brief 三つの頂点IDで表す三角形 struct tri { size_t a, b, c; tri() {} tri(const size_t A, const size_t B, const size_t C) : a(A), b(B), c(C) {} }; //////////////////////////////////////////////////////////////// //! @brief ハッシュ値を統合する ( 汎用的に使用できる関数 ) //! @param [in,out] seed in:既存のハッシュ値 out:元のseedとvから作成したハッシュ値を統合した値 //! @param [in] v 新たにハッシュ値を作成する値 template<typename T> void hash_combine(size_t& seed, T const& v) { //基本型に関するハッシュ生成は標準ライブラリが提供している std::hash<T> primitive_type_hash; //生成したハッシュを合成する。このコードはboostものを使用する seed ^= primitive_type_hash(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } namespace ICOQ { //! @brief 二つの頂点IDで表すエッジ struct edge { size_t a, b; edge(const size_t A, const size_t B) :a(A), b(B) {} //! @brief 比較演算子。順序が逆でも同じと見なす bool operator==(const edge& e)const { return (e.a == a && e.b == b) || (e.a == b && e.b == a); } }; //! @brief エッジ用のハッシュ関数 struct EHash { public: size_t operator()(const edge& data)const { //クラスのメンバの値それぞれについてハッシュ生成して、それらを結合して一つのハッシュ値にする std::size_t seed = 0; if (data.a < data.b) { hash_combine(seed, data.a); hash_combine(seed, data.b); } else { hash_combine(seed, data.b); hash_combine(seed, data.a); } return seed; } }; //! @brief ICO球作成 class icoQ { public: //結果を格納する配列 std::vector<xyz> vlist; std::vector<tri> tlist; private: double R; //! @brief 頂点を正規化して原点からの距離Rにする //! @param [in,out] vtx 頂点座標 //! @param [in] R 半径 static void normalize(xyz& vtx,double R) { double x = vtx.x; double y = vtx.y; double z = vtx.z; double len = sqrt(x * x + y * y + z * z); vtx.x /= len; vtx.y /= len; vtx.z /= len; vtx.x *= R; vtx.y *= R; vtx.z *= R; } public: icoQ() { R = 1; } //! @brief 指定したエッジの中点(球上)の登録 void calc_middle(std::unordered_map<edge, size_t, EHash> * ehash, std::vector<xyz> * vtx, const size_t j, const size_t k) { double p[3] = { (*vtx)[j].x,(*vtx)[j].y,(*vtx)[j].z }; double q[3] = { (*vtx)[k].x,(*vtx)[k].y,(*vtx)[k].z }; xyz newV( (p[0] + q[0]) / 2, (p[1] + q[1]) / 2, (p[2] + q[2]) / 2 ); normalize(newV, R); vtx->push_back(newV); (*ehash)[edge(j, k)] = vtx->size() - 1; } //! @brief エッジの中間点を計算し、頂点をアップデートする //! @param [in,out] ehash エッジからエッジの中点のIDを取り出すためのハッシュ //! @param [in,out] vtx 頂点一覧。増えることはあっても順序は変わらない //! @param [in] tri_now 現在の三角形一覧 void update_middle_hash(std::unordered_map<edge, size_t, EHash> * ehash, std::vector<xyz> * vtx, const std::vector<tri> & tri_now) { ehash->clear(); // エッジの中間点となる頂点を作成し、ハッシュに登録する for (auto& t : tri_now) { std::unordered_map<edge, size_t, EHash>::iterator ei; // t.a -> t.b の中間点 ei = ehash->find(edge(t.a, t.b)); if (ei == std::end(*ehash)) { calc_middle(ehash, vtx, t.a, t.b); } // t.b -> t.c の中間点 ei = ehash->find(edge(t.b, t.c)); if (ei == std::end(*ehash)) { calc_middle(ehash, vtx, t.b, t.c); } // t.c -> t.a の中間点 ei = ehash->find(edge(t.c, t.a)); if (ei == std::end(*ehash)) { calc_middle(ehash, vtx, t.c, t.a); } } } //! @brief 初期状態の正二十面体を作成する void setSeeds( std::vector<xyz>& svtx, std::vector<tri>& stri){ svtx.resize(12); // create 12 vertices of a icosahedron // 正二十面体を構成する頂点を全て列挙する const double t = (1.0 + sqrt(5.0)) / 2.0; svtx[0] = xyz(-1, t, 0); svtx[1] = xyz(1, t, 0); svtx[2] = xyz(-1, -t, 0); svtx[3] = xyz(1, -t, 0); svtx[4] = xyz(0, -1, t); svtx[5] = xyz(0, 1, t); svtx[6] = xyz(0, -1, -t); svtx[7] = xyz(0, 1, -t); svtx[8] = xyz(t, 0, -1); svtx[9] = xyz(t, 0, 1); svtx[10] = xyz(-t, 0, -1); svtx[11] = xyz(-t, 0, 1); //すべての頂点座標を球の上に置く for (size_t i = 0; i < svtx.size(); i++) { normalize(svtx[i], R); } stri.resize(20); // create 20 triangles of the icosahedron // 正二十面体を構成する三角形を全て列挙する // 5 faces around point 0 stri[0] = tri(0, 11, 5); stri[1] = tri(0, 5, 1); stri[2] = tri(0, 1, 7); stri[3] = tri(0, 7, 10); stri[4] = tri(0, 10, 11); // 5 adjacent faces stri[5] = tri(1, 5, 9); stri[6] = tri(5, 11, 4); stri[7] = tri(11, 10, 2); stri[8] = tri(10, 7, 6); stri[9] = tri(7, 1, 8); // 5 faces around point 3 stri[10] = tri(3, 9, 4); stri[11] = tri(3, 4, 2); stri[12] = tri(3, 2, 6); stri[13] = tri(3, 6, 8); stri[14] = tri(3, 8, 9); // 5 adjacent faces stri[15] = tri(4, 9, 5); stri[16] = tri(2, 4, 11); stri[17] = tri(6, 2, 10); stri[18] = tri(8, 6, 7); stri[19] = tri(9, 8, 1); }//! @brief 球の作成
void refine(const double r,const int level) { //半径の決定 R = r; // エッジの中間点を表すハッシュ std::unordered_map<edge, size_t, EHash> ehash; //初期状態の頂点・三角形群 std::vector<tri> tri_now; std::vector<xyz> vtx; setSeeds(vtx, tri_now); //初期形状の頂点情報の配列 //ICO球となる三角形 std::vector<tri> retQ; //「全ての三角形を4分割する」という作業をlevel回繰り返す for (size_t i = 0; i < level; i++) { //エッジの中点を求め、頂点一覧に追加し、そのハッシュを作成する update_middle_hash(&ehash, &vtx, tri_now); //今までの結果をクリア retQ.clear(); //全ての三角形を分割 for (auto& t : tri_now) { size_t a = t.a; size_t b = t.b; size_t c = t.c; size_t d = ehash[edge(a, b)]; size_t e = ehash[edge(b, c)]; size_t f = ehash[edge(c, a)]; //△t、つまり△abcを分割し新三角形を4つ作る /* a /\ / \ / \ d / \ f / \ / \ / \ / \ b ~~~~~~~e~~~~~~~ c */ retQ.push_back(tri(a, d, f)); //△adf retQ.push_back(tri(d, b, e)); //△dbe retQ.push_back(tri(d, e, f)); //△def retQ.push_back(tri(f, e, c)); //△fec } tri_now = retQ; } vlist = vtx; tlist = retQ; } }; }void display(void); void timer(int value) { glutPostRedisplay(); glutTimerFunc(20, timer, 0); } int width, height; void resize(int w, int h) { width = w; height = h; } ICOQ::icoQ icoq; int main(int argc, char* argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGBA); glutCreateWindow(argv[0]); glutDisplayFunc(display); glutReshapeFunc(resize); glutTimerFunc(20, timer, 0); glutReshapeFunc(resize); icoq.refine(1,4); glutMainLoop(); return 0; } void display(void) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45, width/(double)height, 0.1, 20); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslated(0, 0, -4); static int kk = 0; kk++; glRotated(kk, 1, 1.5, 1); glDisable(GL_LIGHTING); glColor3d(1, 1, 1); glLineWidth(2);
//ICO球エッジ表示
for (size_t i = 0; i < icoq.tlist.size(); i++) { xyz a = icoq.vlist[icoq.tlist[i].a]; xyz b = icoq.vlist[icoq.tlist[i].b]; xyz c = icoq.vlist[icoq.tlist[i].c]; glBegin(GL_LINE_LOOP); glVertex3d(a.x, a.y, a.z); glVertex3d(b.x, b.y, b.z); glVertex3d(c.x, c.y, c.z); glEnd(); } glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); GLfloat abl[4] = { 1,1,1,1 }; GLfloat zero[4] = { 0,0,0,0 }; glLightfv(GL_LIGHT0, GL_AMBIENT, abl); glLightfv(GL_LIGHT0, GL_DIFFUSE, zero); glLightfv(GL_LIGHT0, GL_SPECULAR, zero); glLightfv(GL_LIGHT0, GL_POSITION, zero); GLfloat ambr[4] = { 1,0,0,1 }; GLfloat ambb[4] = { 0,0,1,1 }; glMaterialfv(GL_FRONT, GL_AMBIENT, ambb); glMaterialfv(GL_BACK, GL_AMBIENT, ambr);//ICO球表示 for (size_t i = 0; i < icoq.tlist.size(); i++) { xyz a = icoq.vlist[icoq.tlist[i].a]; xyz b = icoq.vlist[icoq.tlist[i].b]; xyz c = icoq.vlist[icoq.tlist[i].c]; glBegin(GL_TRIANGLES); glVertex3d(a.x, a.y, a.z); glVertex3d(b.x, b.y, b.z); glVertex3d(c.x, c.y, c.z); glEnd(); }glFlush(); }
Creating an icosphere mesh in code
http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
Weblog on mebius.tokaichiba.jp
http://ynomura.dip.jp/archives/2009/08/20.html