Tuesday, July 22, 2014

So, I'm starting to play around with Unity and C#, and found a nice Icosphere mesh generator here that seems to be referenced all around the web. However, this algorithm subdivides by a factor of 2 for each level, which is pretty coarse resolution:


20 faces -> 80 faces -> 320 faces -> 1280 faces -> 20 * 2^(2*N) faces

Instead I found a python Icosphere algorithm that allows a variable subdivision of each face, allowing a much finer resolution for the final number of faces:


20 faces -> 80 faces -> 180 faces -> 320 faces -> 500 faces -> 20 * N^2 faces

Below is version re-coded in C# for Unity, which calculates the Icosphere and generates a mesh. Just attach this script to an empty game object along with a mesh filter and mesh renderer. You can even add a texture to the mesh renderer, though there is a seam at the boundary and poles.


1:  //  
2:  // Author: Kevin Tritz (tritz at yahoo *spamfilter* com)  
3:  // copyright (c) 2014  
4:  // license: BSD style  
5:  // derived from python version: Icosphere.py  
6:  //  
7:  //         Author: William G.K. Martin (wgm2111@cu where cu=columbia.edu)  
8:  //         copyright (c) 2010  
9:  //         license: BSD style  
10:  //        https://code.google.com/p/mesh2d-mpl/source/browse/icosphere.py  
11:  using UnityEngine;  
12:  using System.Collections;  
13:  using System.Collections.Generic;  
14:  public class IcoSphere : MonoBehaviour  
15:  {  
16:      public int num;            // subdivision level of icosphere, max value of 79 to fit within 64k mesh index limit  
17:                                      // num = 1 is 1 level of subdivision, 80 faces, 42 verticies  
18:      public Vector3[] vertices;        // Vector3[M] array of verticies, M = 10*(num+1)^2 + 2  
19:      public int[] triangleIndices;     // int[3*N] flat triangle index list for mesh, N = 20*(num+1)^2  
20:      int[,] triangles;                // int[N,3] triangle verticies index list, N = 20*(num+1)^2  
21:      void Start()  
22:      {  
23:          Icosahedron ico = new Icosahedron();    // initialize base Icosahedron, 20 faces, 12 vertices, radius = 1  
24:          get_triangulation(num, ico);            // main function to subdivide and triangulate Icosahedron  
25:          Mesh mesh = new Mesh();                    // mesh initialization and display   
26:          GetComponent<MeshFilter>().mesh = mesh; // add Mesh Filter component to game object with this script  
27:          mesh.vertices = vertices;  
28:          mesh.normals = vertices;  
29:          mesh.uv = getUV(vertices);                // UV mapping is messed up near poles and longitude boundary  
30:                                                  // cube-mapping might work better  
31:          mesh.triangles = triangleIndices;  
32:      }  
33:      void get_triangulation(int num, Icosahedron ico)  
34:      {  
35:          Dictionary<Vector3, int> vertDict = new Dictionary<Vector3, int>();    // dict lookup to speed up vertex indexing  
36:          float[,] subdivision = getSubMatrix(num+2);                            // vertex subdivision matrix calculation  
37:          Vector3 p1, p2, p3;  
38:          int index = 0;  
39:          int vertIndex;  
40:          int len = subdivision.GetLength(0);  
41:          int triNum = (num+1)*(num+1)*20;            // number of triangle faces  
42:          vertices = new Vector3[triNum/2 + 2];        // allocate verticies, triangles, etc...  
43:          triangleIndices = new int[triNum*3];  
44:          triangles = new int[triNum,3];  
45:          Vector3[] tempVerts = new Vector3[len];        // temporary structures for subdividing each Icosahedron face  
46:          int[] tempIndices = new int[len];  
47:          int[,] triIndices = triangulate(num);        // precalculate generic subdivided triangle indices  
48:          int triLength = triIndices.GetLength(0);  
49:          for(int i=0; i < 20; i++)                    // calculate subdivided vertices and triangles for each face  
50:          {  
51:              p1 = ico.vertices[ico.triangles[i*3]];    // get 3 original vertex locations for each face  
52:              p2 = ico.vertices[ico.triangles[i*3+1]];  
53:              p3 = ico.vertices[ico.triangles[i*3+2]];  
54:              for(int j=0; j < len; j++)                // calculate new subdivided vertex locations  
55:              {  
56:                  tempVerts[j].x = subdivision[j,0]*p1.x + subdivision[j,1]*p2.x + subdivision[j,2]*p3.x;  
57:                  tempVerts[j].y = subdivision[j,0]*p1.y + subdivision[j,1]*p2.y + subdivision[j,2]*p3.y;  
58:                  tempVerts[j].z = subdivision[j,0]*p1.z + subdivision[j,1]*p2.z + subdivision[j,2]*p3.z;  
59:                  tempVerts[j].Normalize();  
60:                  if(!vertDict.TryGetValue(tempVerts[j], out vertIndex))    // quick lookup to avoid vertex duplication  
61:                  {  
62:                      vertDict[tempVerts[j]] = index;    // if vertex not in dict, add it to dictionary and final array  
63:                      vertIndex = index;  
64:                      vertices[index] = tempVerts[j];  
65:                      index += 1;  
66:                  }  
67:                  tempIndices[j] = vertIndex;            // assemble vertex indices for triangle assignment  
68:              }  
69:              for(int j=0; j < triLength; j++)        // map precalculated subdivided triangle indices to vertex indices  
70:              {  
71:                  triangles[triLength*i+j,0] = tempIndices[triIndices[j,0]];  
72:                  triangles[triLength*i+j,1] = tempIndices[triIndices[j,1]];  
73:                  triangles[triLength*i+j,2] = tempIndices[triIndices[j,2]];  
74:                  triangleIndices[3*triLength*i + 3*j] = tempIndices[triIndices[j,0]];  
75:                  triangleIndices[3*triLength*i + 3*j + 1] = tempIndices[triIndices[j,1]];  
76:                  triangleIndices[3*triLength*i + 3*j + 2] = tempIndices[triIndices[j,2]];  
77:              }  
78:          }  
79:      }  
80:      int[,] triangulate(int num)    // fuction to precalculate generic triangle indices for subdivided vertices  
81:      {  
82:          int n = num + 2;  
83:          int[,] triangles = new int[(n-1)*(n-1),3];  
84:          int shift = 0;  
85:          int ind = 0;  
86:          for(int row=0; row < n-1; row++)  
87:          {  
88:              triangles[ind,0] = shift + 1;  
89:              triangles[ind,1] = shift + n - row;  
90:              triangles[ind,2] = shift;  
91:              ind += 1;  
92:              for(int col = 1; col < n-1-row; col++)  
93:              {  
94:                  triangles[ind,0] = shift + col;  
95:                  triangles[ind,1] = shift + n - row + col;  
96:                  triangles[ind,2] = shift + n - row + col - 1;  
97:                  ind += 1;  
98:                  triangles[ind,0] = shift + col + 1;  
99:                  triangles[ind,1] = shift + n - row + col;  
100:                  triangles[ind,2] = shift + col;  
101:                  ind += 1;  
102:              }  
103:              shift += n - row;  
104:          }  
105:          return triangles;  
106:      }  
107:      Vector2[] getUV(Vector3[] vertices)    // standard Longitude/Latitude mapping to (0,1)/(0,1)  
108:      {  
109:          int num = vertices.Length;  
110:          float pi = (float)System.Math.PI;  
111:          Vector2[] UV = new Vector2[num];  
112:          for(int i=0; i < num; i++)  
113:          {  
114:              UV[i] = cartToLL(vertices[i]);  
115:              UV[i].x = (UV[i].x + pi)/(2.0f*pi);  
116:              UV[i].y = (UV[i].y + pi/2.0f)/pi;  
117:          }  
118:          return UV;  
119:      }  
120:      Vector2 cartToLL(Vector3 point)    // transform 3D cartesion coordinates to longitude, latitude  
121:      {  
122:          Vector2 coord = new Vector2();  
123:          float norm = point.magnitude;  
124:          if(point.x != 0.0f || point.y != 0.0f)  
125:              coord.x = -(float)System.Math.Atan2(point.y, point.x);  
126:          else  
127:              coord.x = 0.0f;  
128:          if(norm > 0.0f)  
129:              coord.y = (float)System.Math.Asin(point.z / norm);  
130:          else  
131:              coord.y = 0.0f;  
132:          return coord;  
133:      }  
134:      float[,] getSubMatrix(int num)    // vertex subdivision matrix, num=3 subdivides 1 triangle into 4  
135:      {  
136:          int numrows = num * (num + 1) / 2;  
137:          float[,] subdivision = new float[numrows,3];  
138:          float[] values = new float[num];  
139:          int[] offsets = new int[num];  
140:          int[] starts = new int[num];  
141:          int[] stops = new int[num];  
142:          int index;  
143:          for(int i=0;i < num; i++)  
144:          {  
145:              values[i] = (float)i / (float)(num - 1);  
146:              offsets[i] = (num - i);  
147:              if(i > 0)  
148:                  starts[i] = starts[i-1] + offsets[i-1];  
149:              else  
150:                  starts[i] = 0;  
151:              stops[i] = starts[i] + offsets[i];              
152:          }      
153:          for(int i=0; i < num; i++)  
154:          {  
155:              for(int j=0; j < offsets[i]; j++)  
156:              {  
157:                  index = starts[i] + j;  
158:                  subdivision[index,0] = values[offsets[i]-1-j];  
159:                  subdivision[index,1] = values[j];  
160:                  subdivision[index,2] = values[i];  
161:              }  
162:          }  
163:          return subdivision;  
164:      }  
165:  }  
166:  public class Icosahedron  
167:  {  
168:      public Vector3[] vertices;  
169:      public int[] triangles;  
170:      public Icosahedron()  
171:      {  
172:          vertices = getPoints();  
173:          triangles = getTriangles();      
174:      }  
175:      Vector3[] getPoints()  
176:      {  
177:          Vector3[] vertices = new Vector3[12];  
178:          // Define the verticies with the golden ratio  
179:          float a = (1.0f + (float)System.Math.Sqrt(5))/2.0f;   
180:          vertices[0] = new Vector3(a,        0.0f,    1.0f);  
181:          vertices[9] = new Vector3(-a,        0.0f,    1.0f);  
182:          vertices[11] = new Vector3(-a,        0.0f,    -1.0f);  
183:          vertices[1] = new Vector3(a,        0.0f,    -1.0f);  
184:          vertices[2] = new Vector3(1.0f,        a,        0.0f);  
185:          vertices[5] = new Vector3(1.0f,        -a,        0.0f);  
186:          vertices[10] = new Vector3(-1.0f,    -a,        0.0f);  
187:          vertices[8] = new Vector3(-1.0f,    a,        0.0f);  
188:          vertices[3] = new Vector3(0.0f,        1.0f,    a);  
189:          vertices[7] = new Vector3(0.0f,        1.0f,    -a);  
190:          vertices[6] = new Vector3(0.0f,        -1.0f,    -a);  
191:          vertices[4] = new Vector3(0.0f,        -1.0f,    a);  
192:          for(int i = 0; i < 12; i++)  
193:          {  
194:              vertices[i].Normalize();  
195:          }  
196:          // rotate top point to the z-axis  
197:          float angle = (float)System.Math.Atan(vertices[0].x / vertices[0].z);  
198:          float ca = (float)System.Math.Cos (angle);  
199:          float sa = (float)System.Math.Sin (angle);  
200:          Matrix4x4 rotation = Matrix4x4.identity;  
201:          rotation.m00 = ca;  
202:          rotation.m02 = -sa;  
203:          rotation.m20 = sa;  
204:          rotation.m22 = ca;  
205:          for(int i = 0; i < 12; i++)  
206:          {  
207:              vertices[i] = rotation.MultiplyPoint3x4(vertices[i]);  
208:          }  
209:          return vertices;          
210:      }  
211:      int[] getTriangles()  
212:      {  
213:          int[] tris = {  
214:              1,2,0,  
215:              2,3,0,  
216:              3,4,0,  
217:              4,5,0,  
218:              5,1,0,  
219:              6,7,1,  
220:              2,1,7,  
221:              7,8,2,  
222:              2,8,3,  
223:              8,9,3,  
224:              3,9,4,  
225:              9,10,4,  
226:              10,5,4,  
227:              10,6,5,  
228:              6,1,5,  
229:              6,11,7,  
230:              7,11,8,  
231:              8,11,9,  
232:              9,11,10,  
233:              10,11,6,              
234:          };  
235:          return tris;  
236:      }  
237:  }