Search Unity

algorithm to sort out an icosphere's vertices, help needed

Discussion in 'Scripting' started by Krambolage, May 2, 2016.

  1. Krambolage

    Krambolage

    Joined:
    Jan 22, 2013
    Posts:
    25
    Hi guys,
    I'm making a grand strategy game in which the map is an icosphere (ie a sphere composed of hexagons and 12 pentagons).
    Papers about hexaginal grids I found on the net mention flat maps only and do not apply to my case (unless I misunderstand the core concept), so I'm calling for your help :)
    I think you need details (I'm affraid this makes for a lengthy post) :

    - What am I intending to do exactely :
    As the player clicks, or the mouse hovers over the sphere, data corresponding to the selected cell is retrieved and served for the player's masochistic pleasure.
    Obviously this system is the game's backbone, and nothing can be done without it !

    - What am I doing precisely :
    I divided planet Earth into 20 game objects, each of them representing the icosphere face (bcs meshes can't store more than 2^16 vertices and we do need plenty).
    Each face is subdivided 6 times (1 hexagon represents ~100km). Needless to say that there'll be TONS of hexagons ^_^
    Each vertex is the center of an hexagon (or pentagon). Actually it's more complicated (I want to save your synapses =), you'll see at the end of the post...
    The world cells data will be stored into an array[], in its own script : the data managing script.
    That script will access data by knowing the clicked/hovered face id and the closest vertex from the click.
    I want to acces a given cell like that :
    int index = faceId * faceLength + vertexId; // faceId, faceLength are passed by the face game object script. VertexId is retrieved by computing the shortest distance raycasthit.point <-> raycasthit.triangleIndex (we're making 3 checks)
    array[index].DoStuff ();
    In order this to work, array needs data be stored by face and vertex order (that's tricky to explain, example incoming)
    That's precisely my issue, the algorithm responsible for creating the geometry works that way ( http://sarvanz.blogspot.fr/2013/07/sphere-triangulation-using-icosahedron.html ) :
    Primary face vertices (* = vertex)
    Code (CSharp):
    1.              *A
    2.        
    3.  
    4.  
    5.   C*                  *B
    We get (beware pseudo code) mesh.vertices = { A,B,C }

    1st subdivision
    Code (CSharp):
    1.              *A
    2.  
    3.      1*           *2
    4.  
    5.   C*        *         *B
    6.               3
    mesh.vertices = { A,B,C,1,2,3 }

    The 2nd subdivision will break a,b,c into 4 triangles, leading to sth like mesh.vertices = { A,B,C,1,2,3,do,re,mi } // Hear the programing melody ? ;)
    Rince and repeat 4 more times. I let you imagine the mess inside mesh.vertices (and triangles)

    What I want to achieve is ordering vertices the same way as we, westerners, read text (upper row, left, to right, then next row, left right...) :
    mesh.vertices = { A,1,2,C,3,B }

    I can't find a way to achieve this, especially knowing that in addition to vertices reordering, we must reassign triangles.
    Oh, remember that I told you it gets dirtier ? every vertices are generated 3 times (the same texture is applied on every triangles, UVs will differ depending on terrain type)
    T_T

    I will greatly appreciate some help, really and I thank you a lot by advance
    p.s. : I'll take any simpler/better way to handle this visuals-data system
     
  2. Krambolage

    Krambolage

    Joined:
    Jan 22, 2013
    Posts:
    25
    Some people visited this thread without replying. Maybe that's because I didn't show my work ?
    I didn't want to make the post even more longer but it's ok to add it here.
    I don't want people to think that I'm waiting for a turnkey solution.

    Here's one side script, that is attached to one of the 20 game objects I mentioned earlier :
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. [RequireComponent (typeof (MeshFilter), typeof (MeshRenderer), typeof (MeshCollider))]
    5. public class F_00 : FaceScript
    6. {
    7.     MeshFilter filter;
    8.     Mesh mesh;
    9.     MeshCollider collider;
    10.     Vector3[] vertices;
    11.     /// <summary>
    12.     /// True if the face is oriented upwards.
    13.     /// Necessary to do the mapping correctly, not used yet
    14.     /// </summary>
    15.     bool isOrientedUpwards;
    16.  
    17.     void Awake () {
    18.         filter = (MeshFilter) gameObject.GetComponent< MeshFilter >();
    19.         mesh = filter.mesh;
    20.         collider = (MeshCollider) gameObject.GetComponent<MeshCollider> ();
    21.         vertices = mesh.vertices;
    22.     }
    23.  
    24.     private struct TriangleIndices
    25.     {
    26.         public int v0;
    27.         public int v1;
    28.         public int v2;
    29.      
    30.         public TriangleIndices(int v00, int v11, int v22)
    31.         {
    32.             this.v0 = v00;
    33.             this.v1 = v11;
    34.             this.v2 = v22;
    35.         }
    36.     }
    37.  
    38.     // return index of point in the middle of p1 and p2
    39.     private static int getMiddlePoint(int p1, int p2, ref List<Vector3> vertices, float radius)
    40.     {
    41.         // not in cache, calculate it
    42.         Vector3 point1 = vertices[p1];
    43.         Vector3 point2 = vertices[p2];
    44.         Vector3 middle = new Vector3
    45.             (
    46.                 (point1.x + point2.x) / 2f,
    47.                 (point1.y + point2.y) / 2f,
    48.                 (point1.z + point2.z) / 2f
    49.                 );
    50.      
    51.         // add vertex makes sure point is on unit sphere
    52.         int i = vertices.Count;
    53.         vertices.Add( middle.normalized * radius );
    54.      
    55.         return i;
    56.     }
    57.  
    58.     public override void Create (float radius, int recursionLevel)
    59.     {
    60.         mesh.Clear();
    61.      
    62.         List<Vector3> vertList = new List<Vector3>();
    63.      
    64.         // create 12 vertices of a icosahedron
    65.         float t = (1f + Mathf.Sqrt(5f)) / 2f;
    66.         Debug.Log ("magic number = " +t);
    67.  
    68.         #region VERTICES & TRIANGLES
    69.  
    70.         // V 0 (TOP VERTEX)
    71.         vertList.Add(new Vector3( 0f,  1f,  t).normalized * radius);
    72.         // V 1
    73.         vertList.Add(new Vector3( 1f,  t,  0f).normalized * radius);
    74.         // V 2
    75.         vertList.Add(new Vector3(-1f,  t,  0f).normalized * radius);
    76.      
    77.         // create face triangles
    78.         List<TriangleIndices> faces = new List<TriangleIndices>();
    79.  
    80.         // NORTH 0 (Face (0))
    81.         //      *0 (vertex 0)
    82.         //   a+   +b
    83.         //  2*  +  *1 (vertex 1)
    84.         //      c
    85.         faces.Add(new TriangleIndices(0, 1, 2)); // N0, 0, 1, 2
    86.         #endregion
    87.  
    88.         // refine triangles
    89.         for (int i = 0; i < recursionLevel; i++)
    90.         {
    91.             List<TriangleIndices> faces2 = new List<TriangleIndices>();
    92.             foreach (var tri in faces)
    93.             {
    94.                 // replace triangle by 4 triangles
    95.                 int a = getMiddlePoint(tri.v2, tri.v0, ref vertList, radius);
    96.                 int a2 = getMiddlePoint(tri.v2, tri.v0, ref vertList, radius);
    97.                 int a3 = getMiddlePoint(tri.v2, tri.v0, ref vertList, radius);
    98.  
    99.                 int b = getMiddlePoint(tri.v0, tri.v1, ref vertList, radius);
    100.                 int b2 = getMiddlePoint(tri.v0, tri.v1, ref vertList, radius);
    101.                 int b3 = getMiddlePoint(tri.v0, tri.v1, ref vertList, radius);
    102.              
    103.                 int c = getMiddlePoint(tri.v1, tri.v2, ref vertList, radius);
    104.                 int c2 = getMiddlePoint(tri.v1, tri.v2, ref vertList, radius);
    105.                 int c3 = getMiddlePoint(tri.v1, tri.v2, ref vertList, radius);
    106.  
    107.              
    108.                 faces2.Add(new TriangleIndices(tri.v0, b, a)); // Triangle UP
    109.                 faces2.Add(new TriangleIndices(a2, c2, tri.v2)); // Triangle LEFT
    110.                 faces2.Add(new TriangleIndices(a3, b3, c3)); // Triangle MIDDLE
    111.                 faces2.Add(new TriangleIndices(b2, tri.v1, c)); // Triangle RIGHT
    112.             }
    113.             faces = faces2;
    114.         }
    115.      
    116.         mesh.vertices = vertList.ToArray();
    117.      
    118.         List< int > triList = new List<int>();
    119.         for( int i = 0; i < faces.Count; i++ )
    120.         {
    121.             triList.Add( faces[i].v0 );
    122.             triList.Add( faces[i].v1 );
    123.             triList.Add( faces[i].v2 );
    124.         }      
    125.         mesh.triangles = triList.ToArray();
    126.         Vector2[] UVs = ComputeUVs ();
    127.         mesh.uv = UVs;
    128.      
    129.         Vector3[] normales = new Vector3[ vertList.Count];
    130.         for( int i = 0; i < normales.Length; i++ )
    131.             normales[i] = vertList[i].normalized;
    132.      
    133.      
    134.         mesh.normals = normales;
    135.      
    136.         mesh.RecalculateBounds();
    137.         mesh.Optimize();
    138.  
    139.         collider.sharedMesh = mesh;
    140.     }
    141.  
    142.     Vector2[] ComputeUVs () {
    143.  
    144.         Vector2[] UVs = new Vector2[ mesh.vertices.Length ];
    145.         int c = 0;
    146.      
    147.         for (int i = 0; i < mesh.triangles.Length; i++) {
    148.             int vertexId = mesh.triangles[i];
    149.          
    150.             if (c == 0) {
    151.                 UVs[vertexId] = new Vector2 (.5f, .05859385f);
    152.             }
    153.             else if (c == 1) {
    154.                 UVs[vertexId] = new Vector2 (.890625f, .735375f);
    155.             }
    156.             else {
    157.                 UVs[vertexId] = new Vector2 (.109375f, .735375f);
    158.                 c = -1;
    159.             }
    160.             c++;
    161.         }
    162.      
    163.         return UVs;
    164.     }
    165. }
    You can see that the triangles should be sorted out, well with the current vertices construction.
    All I need to know is how to sort the vertices and I'll figure out the triangles (hopefully =)

    Again, sorry for the massive post(s), but you'll agree that the issue is quite complicated.
     
    Last edited: May 2, 2016
  3. Krambolage

    Krambolage

    Joined:
    Jan 22, 2013
    Posts:
    25
    After spending a few days on it, I can tell that I've tackled the problem the wrong way.
    Actually the problem is the mesh subdivision method.
    Dividing edges by 2 recursively messes everything up, as shown in the 1st post above.

    There's a better way to do that, if you want to sort vertices.
    In case some people are interested, here's the principle :
    - You've got your initial triangle, with vertices A (top), B (left) and C (right)
    - Compute how many rows of hexagons u want to have between pentagons (say we want 5 hexas. Remember that Unity limits us to 65K vertices/mesh)
    - For eah row,
    - Compute row coeficient from vertex A to either B or C, it does not matter (row 1 coef = 1/ (5 hexas +2 pentas) = 1/7)
    - Compute the row's leftmost vertex (x = 1/7 (A.x+B.x), same for y and z) (in my case, for my UV needs, I created 3 vertices @ the same location). Add a condition to avoid adding 3 times B !
    - Do the same with the rightmost vertex (x = 1/7 (A.x+C.x), same for y and z) (in my case, I created 3 vertices @ the same location)
    - Then, compute vertices inbetween (same principle than above, in its own loop) (here too, I created 3 verts per position)
    - Out of the 2nd loop, add in that order leftmost, then vertices in between, then rightmost vertices to your final vertex list
    - Out of both loops, remove the last two vertices in order to have only one C vertex
    The post's getting long so I let you take care of the details

    Here you have your verices. Normalize them and apply radius and you get 1/20th of a sphere.
    Repeat the process with the icosahedron's other 19 triangles to get an isosphere with up to 64 subdivisions !
    64 subdiv' => 1 hexagon represents about 100km on Earth.
    Hoping all my explanations are clear :)

    Now, I've got difficulties to sort out triangles (this would've risen with the previous method by the way).
    I'll post the solution once I solved this last issue.

    Thanks for reading ;-)