Search Unity

Procedural Isosphere Scripting issue (UV related)

Discussion in 'Scripting' started by Deadphyre, Oct 16, 2013.

  1. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    This is my first time posting here and in all honesty I'm relatively new to programming. Aside from that, I have done the run of the mill search the forums, searched google, searched stackoverflow, searched a lot. Gone through tutorials left and right and I've been able to come up with some code, with some modification from what's been posted on the net for procedurally creating an isosphere by subdividing faces etc etc. Got the Collision working just fine and such. I'm stuck at trying to get the UV set up properly. It's just not clicking, and I'm sure it's just something simple. Not really wanting to ask for help but I'm just stumped. I usually try to figure things out myself because...well, the information is there. Somewhere, I just can't find it. Anyways, enough of my rambling. I'm gonna post the code here in it's entirety and maybe someone smrt enough could let me know what I'm doing wrong. Hell, even some optimizations would be awesome.

    Also...thanks for reading and possibly helping. FYI, I don't get any error's in Unity but when I run it, the sphere generates with collision and all that but the texture (png format) doesn't display correctly, the entire thing just shows up grey-ish. It's supposed to be the moon. (Just temporary until I can figure this out) The file is formated correctly since if I just create a sphere from the GameObject menu it displays like it's supposed to be. So my UV coding is all screwed up.

    This portion of the code is figure out the middle point so it knows where to do it's thing for the vertices and triangles and all that jazz.

    I think the getMiddleUVPoint is where my problem is.

    Code (csharp):
    1. public int getMiddleUVPoint(int v1, int v2, ref List<Vector2> uv, ref Dictionary<long, int> uvcache, float radius){
    2.         //first check if it already exists
    3.         bool firstUVIsSmaller = v1 < v2;
    4.         long smallerUVIndex = firstUVIsSmaller ? v1 : v2;
    5.         long greaterUVIndex = firstUVIsSmaller ? v2 : v1;
    6.         long UVkey = (smallerUVIndex << 32) + greaterUVIndex;
    7.        
    8.         int UVret;
    9.         if (uvcache.TryGetValue (UVkey, out UVret)){
    10.             return UVret;
    11.         }
    12.        
    13.         //not in uvcache, then calculate
    14.         Vector2 UVpoint1 = uv[v1];
    15.         Vector2 UVpoint2 = uv[v2];
    16.         Vector2 UVmiddle = new Vector2((UVpoint1.x + UVpoint2.x) / 2f,
    17.                                        (UVpoint1.y + UVpoint2.y) / 2f);
    18.        
    19.         //add UV
    20.         int UVi = uv.Count;
    21.         uv.Add (UVmiddle.normalized * radius);
    22.        
    23.         //store it
    24.         uvcache.Add (UVkey, UVi);
    25.        
    26.         return UVi;
    27.         /*
    28.         Vector2 tempVector = new Vector2();
    29.         tempVector = ((v2-v1) * 0.5) + v1;
    30.         return tempVector;
    31.         */
    32.     }
    33.     // return index of point in the middle of p1 and p2
    34.     public int getMiddlePoint(int p1, int p2, ref List<Vector3> vertices, ref Dictionary<long, int> cache, float radius){
    35.         // first check if we have it already
    36.         //Debug.Log ("getMiddlePoint is called");
    37.         bool firstIsSmaller = p1 < p2;
    38.         long smallerIndex = firstIsSmaller ? p1 : p2;
    39.         long greaterIndex = firstIsSmaller ? p2 : p1;
    40.         long key = (smallerIndex << 32) + greaterIndex;
    41.  
    42.         int ret;
    43.         if (cache.TryGetValue(key, out ret)){
    44.             return ret;
    45.         }
    46.  
    47.         // not in cache, calculate it
    48.         //Debug.Log ("Calculating middle vertice");
    49.         Vector3 point1 = vertices[p1];
    50.         Vector3 point2 = vertices[p2];
    51.         Vector3 middle = new Vector3((point1.x + point2.x) / 2f,
    52.                                      (point1.y + point2.y) / 2f,
    53.                                      (point1.z + point2.z) / 2f);
    54.  
    55.         // add vertex makes sure point is on unit sphere
    56.         int i = vertices.Count;
    57.         vertices.Add( middle.normalized * radius );
    58.  
    59.         // store it, return index
    60.         cache.Add(key, i);
    61.  
    62.         return i;
    63.     }
    Self explanatory

    Code (csharp):
    1. public struct TriangleIndices{
    2.         public int v1;
    3.         public int v2;
    4.         public int v3;
    5.  
    6.         public TriangleIndices(int v1, int v2, int v3){
    7.             this.v1 = v1;
    8.             this.v2 = v2;
    9.             this.v3 = v3;
    10.         }
    11.     }
    Start code...doesn't have anything really

    Code (csharp):
    1. public void Start(){
    2.         //Debug.Log ("Start() initialized");
    3.         Create ();
    4.     }
    These are put in the beginning just so I can change them on the fly...

    Code (csharp):
    1. public int rLevel = 3;
    2.     public float rad = 200f;
    This is the meat of the code, it's not entirely well organized and I will fix that once I get past this extremely annoying part.

    Code (csharp):
    1. public void Create(){
    2.         //Debug.Log ("Creating ISOSPHERE");
    3.         GameObject gameObject;
    4.         Material mat;
    5.         gameObject = new GameObject();
    6.         gameObject.AddComponent ("MeshRenderer");
    7.         MeshFilter filter = gameObject.AddComponent<MeshFilter>();
    8.         MeshCollider col = gameObject.AddComponent<MeshCollider>();
    9.         Mesh mesh = filter.mesh;
    10.         mesh.Clear();
    11.        
    12.         List<Vector3> vertList = new List<Vector3>();
    13.         Dictionary<long, int> middlePointIndexCache = new Dictionary<long, int>();
    14.         //int index = 0;
    15.  
    16.         int recursionLevel = rLevel;
    17.         float radius = rad;
    18.  
    19.         // create 12 vertices of a icosahedron
    20.         float t = (1f + Mathf.Sqrt(5f)) / 2f;
    21.        
    22.         //Debug.Log ("Creating vertList");
    23.         vertList.Add(new Vector3(-1f,  t,  0f).normalized * radius);
    24.         vertList.Add(new Vector3( 1f,  t,  0f).normalized * radius);
    25.         vertList.Add(new Vector3(-1f, -t,  0f).normalized * radius);
    26.         vertList.Add(new Vector3( 1f, -t,  0f).normalized * radius);
    27.  
    28.         vertList.Add(new Vector3( 0f, -1f,  t).normalized * radius);
    29.         vertList.Add(new Vector3( 0f,  1f,  t).normalized * radius);
    30.         vertList.Add(new Vector3( 0f, -1f, -t).normalized * radius);
    31.         vertList.Add(new Vector3( 0f,  1f, -t).normalized * radius);
    32.  
    33.         vertList.Add(new Vector3( t,  0f, -1f).normalized * radius);
    34.         vertList.Add(new Vector3( t,  0f,  1f).normalized * radius);
    35.         vertList.Add(new Vector3(-t,  0f, -1f).normalized * radius);
    36.         vertList.Add(new Vector3(-t,  0f,  1f).normalized * radius);
    37.         //Debug.Log ("vertList complete");
    38.  
    39.         // create 20 triangles of the icosahedron
    40.         List<TriangleIndices> faces = new List<TriangleIndices>();
    41.         //Debug.Log ("Creating triangle faces");
    42.         // 5 faces around point 0
    43.         faces.Add(new TriangleIndices(0, 11, 5));
    44.         faces.Add(new TriangleIndices(0, 5, 1));
    45.         faces.Add(new TriangleIndices(0, 1, 7));
    46.         faces.Add(new TriangleIndices(0, 7, 10));
    47.         faces.Add(new TriangleIndices(0, 10, 11));
    48.  
    49.         // 5 adjacent faces
    50.         faces.Add(new TriangleIndices(1, 5, 9));
    51.         faces.Add(new TriangleIndices(5, 11, 4));
    52.         faces.Add(new TriangleIndices(11, 10, 2));
    53.         faces.Add(new TriangleIndices(10, 7, 6));
    54.         faces.Add(new TriangleIndices(7, 1, 8));
    55.  
    56.         // 5 faces around point 3
    57.         faces.Add(new TriangleIndices(3, 9, 4));
    58.         faces.Add(new TriangleIndices(3, 4, 2));
    59.         faces.Add(new TriangleIndices(3, 2, 6));
    60.         faces.Add(new TriangleIndices(3, 6, 8));
    61.         faces.Add(new TriangleIndices(3, 8, 9));
    62.  
    63.         // 5 adjacent faces
    64.         faces.Add(new TriangleIndices(4, 9, 5));
    65.         faces.Add(new TriangleIndices(2, 4, 11));
    66.         faces.Add(new TriangleIndices(6, 2, 10));
    67.         faces.Add(new TriangleIndices(8, 6, 7));
    68.         faces.Add(new TriangleIndices(9, 8, 1));
    69.         //Debug.Log ("Triangle faces complete");
    70.  
    71.         // refine triangles
    72.         //Debug.Log ("Refining triangles");
    73.         for (int i = 0; i < recursionLevel; i++){
    74.             List<TriangleIndices> faces2 = new List<TriangleIndices>();
    75.             foreach (var tri in faces){
    76.                 // replace triangle by 4 triangles
    77.                 //Debug.Log ("Replacing triangle by 4 triangles");
    78.                 int a = getMiddlePoint(tri.v1, tri.v2, ref vertList, ref middlePointIndexCache, radius);
    79.                 int b = getMiddlePoint(tri.v2, tri.v3, ref vertList, ref middlePointIndexCache, radius);
    80.                 int c = getMiddlePoint(tri.v3, tri.v1, ref vertList, ref middlePointIndexCache, radius);
    81.  
    82.                 faces2.Add(new TriangleIndices(tri.v1, a, c));
    83.                 faces2.Add(new TriangleIndices(tri.v2, b, a));
    84.                 faces2.Add(new TriangleIndices(tri.v3, c, b));
    85.                 faces2.Add(new TriangleIndices(a, b, c));
    86.             }
    87.             faces = faces2;
    88.         }
    89.  
    90.         List<int> triList = new List<int>();
    91.         //Debug.Log ("Creating triList");
    92.         for( int i = 0; i < faces.Count; i++ ){
    93.             triList.Add( faces[i].v1 );
    94.             triList.Add( faces[i].v2 );
    95.             triList.Add( faces[i].v3 );
    96.         }      
    97.        
    98.         //Creating the UV list
    99.        
    100.         float w = 5.5f;
    101.         float h = 3f;
    102.        
    103.         //Creating the 22 Points for UV
    104.         List<Vector2> UVPoints = new List<Vector2>();
    105.         Dictionary<long, int> middleUVPointIndexCache = new Dictionary<long, int>();
    106.        
    107.         UVPoints.Add(new Vector2(0.5f / w, 0));
    108.         UVPoints.Add(new Vector2(1.5f / w, 0));
    109.         UVPoints.Add(new Vector2(2.5f / w, 0));
    110.         UVPoints.Add(new Vector2(3.5f / w, 0));
    111.         UVPoints.Add(new Vector2(4.5f / w, 0));
    112.        
    113.         UVPoints.Add(new Vector2(0     ,  1 / h));
    114.         UVPoints.Add(new Vector2(1f / w,  1 / h));
    115.         UVPoints.Add(new Vector2(2f / w,  1 / h));
    116.         UVPoints.Add(new Vector2(3f / w,  1 / h));
    117.         UVPoints.Add(new Vector2(4f / w,  1 / h));
    118.         UVPoints.Add(new Vector2(5f / w,  1 / h));
    119.    
    120.         UVPoints.Add(new Vector2(0.5f / w, 2 / h));
    121.         UVPoints.Add(new Vector2(1.5f / w, 2 / h));
    122.         UVPoints.Add(new Vector2(2.5f / w, 2 / h));
    123.         UVPoints.Add(new Vector2(3.5f / w, 2 / h));
    124.         UVPoints.Add(new Vector2(4.5f / w, 2 / h));
    125.         UVPoints.Add(new Vector2(1, 2 / h));
    126.    
    127.         UVPoints.Add(new Vector2(1f / w, 1));
    128.         UVPoints.Add(new Vector2(2f / w, 1));
    129.         UVPoints.Add(new Vector2(3f / w, 1));
    130.         UVPoints.Add(new Vector2(4f / w, 1));
    131.         UVPoints.Add(new Vector2(5f / w, 1));
    132.        
    133.         //Creating the UV faces with the UV points
    134.         List<TriangleIndices> UVfaces = new List<TriangleIndices>();
    135.        
    136.         //first row
    137.         UVfaces.Add(new TriangleIndices(0, 5,  6));
    138.         UVfaces.Add(new TriangleIndices(1, 6,  7));
    139.         UVfaces.Add(new TriangleIndices(2, 7,  8));
    140.         UVfaces.Add(new TriangleIndices(3, 8,  9));
    141.         UVfaces.Add(new TriangleIndices(4, 9, 10));
    142.    
    143.         //second row
    144.         UVfaces.Add(new TriangleIndices( 7, 6, 12));
    145.         UVfaces.Add(new TriangleIndices( 6, 5, 11));
    146.         UVfaces.Add(new TriangleIndices(10, 9, 15));
    147.         UVfaces.Add(new TriangleIndices( 9, 8, 14));
    148.         UVfaces.Add(new TriangleIndices( 8, 7, 13));
    149.            
    150.         //fourth row
    151.         UVfaces.Add(new TriangleIndices(17, 12, 11));
    152.         UVfaces.Add(new TriangleIndices(21, 16, 15));
    153.         UVfaces.Add(new TriangleIndices(20, 15, 14));
    154.         UVfaces.Add(new TriangleIndices(19, 14, 13));
    155.         UVfaces.Add(new TriangleIndices(18, 13, 12));
    156.            
    157.         //third row
    158.         UVfaces.Add(new TriangleIndices(11, 12,  6));
    159.         UVfaces.Add(new TriangleIndices(15, 16, 10));
    160.         UVfaces.Add(new TriangleIndices(14, 15,  9));
    161.         UVfaces.Add(new TriangleIndices(13, 14,  8));
    162.         UVfaces.Add(new TriangleIndices(12, 13,  7));
    163.        
    164.         for (int UVi = 0; UVi < recursionLevel; UVi++){
    165.             List<TriangleIndices> UVfaces2 = new List<TriangleIndices>();
    166.             foreach (var tri in UVfaces){
    167.                 // replace triangle by 4 triangles
    168.                 //Debug.Log ("Replacing triangle by 4 triangles");
    169.                 int a = getMiddleUVPoint(tri.v1, tri.v2, ref UVPoints, ref middleUVPointIndexCache, radius);
    170.                 int b = getMiddleUVPoint(tri.v2, tri.v3, ref UVPoints, ref middleUVPointIndexCache, radius);
    171.                 int c = getMiddleUVPoint(tri.v3, tri.v1, ref UVPoints, ref middleUVPointIndexCache, radius);
    172.  
    173.                 UVfaces2.Add(new TriangleIndices(tri.v1, a, c));
    174.                 UVfaces2.Add(new TriangleIndices(tri.v2, b, a));
    175.                 UVfaces2.Add(new TriangleIndices(tri.v3, c, b));
    176.                 UVfaces2.Add(new TriangleIndices(a, b, c));
    177.             }
    178.             UVfaces = UVfaces2;
    179.         }
    180.         /*
    181.         int nVertices = mesh.vertices.Length;
    182.         Vector2[] UVs = new Vector2[nVertices];
    183.         //Debug.Log ("Creating UVs");
    184.         for(int i= 0; i < nVertices; i++){
    185.             UVs[i] = mesh.vertices[i];
    186.         }
    187.         */
    188.  
    189.         Vector3[] normales = new Vector3[vertList.Count];
    190.         //Debug.Log ("Creating normales");
    191.         for(int i = 0; i < normales.Length; i++){
    192.             normales[i] = vertList[i].normalized;
    193.         }
    194.        
    195.         mat = Resources.Load("Material/Moon") as Material;
    196.         if(mat == null)
    197.             Debug.Log ("No material found");
    198.        
    199.         mesh.vertices = vertList.ToArray();
    200.         mesh.triangles = triList.ToArray();
    201.         //mesh.uv = UVs;
    202.         mesh.normals = normales;
    203.         col.sharedMesh = null;
    204.         col.sharedMesh = mesh;
    205.         mesh.RecalculateBounds();
    206.         mesh.RecalculateNormals ();
    207.         gameObject.renderer.material = mat;
    208.         mesh.Optimize();
    209.         //Debug.Log ("Completed ISOSPHERE");
    210.     }
    The whole nVertices and UV portion that's commented out doesn't give me any errors and the material/texture displays exactly the same as the stuff I put in there. Basically what I found is that I have to subdivide the UVs since thats what happens to the verts/faces/triangles. The UV is pretty much the only part I'm currently stuck at. Any help would greatly be appreciated and yes. Again, I did search...with the magical search function on here and Google. A couple of helpful pages popped up..which is how I got this far. I realize that the posted code is out of order but I wanted to break it up some. The public int and float for recursion and radius are at the beginning followed by the static triangleindices, then the getMiddlePoint stuff, followed by start() and then the create().
     
    rsodre likes this.
  2. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    Still having trouble wrapping my brain about how to deal with my UV issue. Is there anyone willing to let me know what I've done wrong with my getMiddleUVPoint? Just not able to process how to deal with and get the middle point of a Vector2. I did find a page where they have a spit of code and said it was the same as dealing with a Vector3 but when I plugged it in I got an error because it couldn't convert a Vector2 to an int. Which is understandable.
     
  3. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    Well, since I'm not getting any help here...no, I'm not upset. It's cool, but at least tell me some more populated boards that I could and ask the question at. Maybe get someone who knows how to direct me or tell me what I did wrong with my code. And no, I'm not going to find the answer in the reference page.
     
  4. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Too many words, not enough pictures
    Why do this in code? It's just a 12-point isosphere yeah? Use a modelling program?
    I guess you are aware that spheres do not map to a plane perfectly?
    Might have a run of your code tomorrow, i like procedural stuff
     
  5. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    Believe you me, there is a method to my madness and a reason for why I'm doing this procedurally instead of just creating a model. It would be a lot easier to just model an object and then apply the material to that but then I wouldn't be able to use it in my end goal. I do know that sphere don't map perfectly but I have run across a few projects that have been able to map out the UV's and apply the material and it's turned out really well but they are closed source projects. I would like to know how to do it myself and I figured the hardest thing to do would be to UV a procedurally generated icosphere. Although it starts out as a 12-point sphere, it's subdivided 3 times (which can be lowered or raised by changing the rLevel number. I've gotten the collision and stuff worked out but the UV's (since it's a 2DVector, or Vector2) I'm just not able to figure it out. Maybe if I have some other code, even in java, that someone has used to do a similar thing (not a cube) then I could figure it out. Thanks for responding though.
     
  6. exiguous

    exiguous

    Joined:
    Nov 21, 2010
    Posts:
    1,749
    maybe noone is able to help you because noone knows what an isosphere is. i at least have no clue. i just know icosphere which also has some meaningfull founds on google.

    well, i wonder why you interpolate the uv coordinates instead of calculating them. you need to calculate them for the initial vertices anyway so also use this method for all subdivided vertices.
    i would project the vertex vectors onto the x-z plane for longitude and x-y plane for latitude. these should be transferable into uv quite easily.

    edit: xy plane through the vertex of course.
     
    Last edited: Oct 18, 2013
  7. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    Ok, I think I get the problem
    You are creating a 22-length array for the UVs, which is probably to get this:

    Which is what 3dsmax makes as default for a 12-icosahedron
    The problem is you are trying to assign this 22-length uv array to a mesh with 12 verts, and as stated here
    http://docs.unity3d.com/Documentation/ScriptReference/Mesh.html
    all the arrays must be of equal length.
    You will need to split the verts along the uv seams, to get 22 verts, each of which gets a single UV coordinate (atm, the poles need to have 5 different UVs)

    Another thing to note, you have (i assume) a temporary UV solution commented out, line 85 (UVs = mesh.vertices) and those around it, this wont work, because mesh.vertices isn't assigned yet, so you get a 0 length array (UVs)
     
  8. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    This is probably a pretty safe bet. I'm pretty familiar with Icospheres myself. The primary benefit is that you get triangular faces everywhere so when you UV and texture you don't get "pinching" at the poles (since there technically are none). A few years ago I wrote some code that generates Icospheres that was used in XNA. It allowed for refining and "degrading" (for lack of a better term) the Icosphere so it could be LODed (you could increase mesh detail as you got closer). However, I never did anything with UVs so I'm personally not sure how you'd go about generating them.

    One thing you could do is just create an Icosphere in Blender and UV Unwrap it. Try some different projections (like smart unwrap) and just see how it generates the UVs. You can probably look at the UV image and the generated UVs and get a pretty good idea of how it's done.
     
  9. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    Yeah, I meant an Icosphere. I was pretty tired when I wrote this up but I figured people would kind of figure it out. Which, in a way you did. So to get the even the temp code to work I would need to get the length of vertList instead of mesh.vertices but after I put it ToArray()? Or is there a way to get the length of the list? Sorry if this all sounds noobish like. But hey, we all start somewhere right?
     
  10. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    Well, I finally got it mostly working other than this gigantic seam stretching problem down the Y axis. Just in case anyone was wondering what I did.

    Code (csharp):
    1. var nVertices = mesh.vertices;
    2.         Vector2[] UVs = new Vector2[nVertices.Length];
    3.        
    4.         //Debug.Log ("Creating UVs");
    5.         for(var i= 0; i < nVertices.Length; i++){
    6.             var unitVector = nVertices[i].normalized;
    7.             Vector2 ISOuv = new Vector2(0, 0);
    8.             ISOuv.x = (Mathf.Atan2(unitVector.x, unitVector.z) + Mathf.PI) / Mathf.PI / 2;
    9.             ISOuv.y = (Mathf.Acos(unitVector.y) + Mathf.PI) / Mathf.PI - 1;
    10.             UVs[i] = new Vector2(ISOuv.x, ISOuv.y);
    11.         }
     
  11. sirshelley

    sirshelley

    Joined:
    Aug 14, 2014
    Posts:
    26
    Yo dude I'm doing the same shiz
    based on this
    https://en.wikipedia.org/wiki/Miller_cylindrical_projection

    Which is what I've seen you've attempted.
    Just trying out your particular way about the algorithm : I unfortunately got a problem, suspect this is due to my use of arrays instead of lists, mine sharing your thoughts?

    P.s Even stack exchange has these issues with communicating ideas: you really gotta spell it out for people, no matter how obvious :{.
     
  12. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    What exactly is the error you are getting or problem that is happening?
     
  13. sirshelley

    sirshelley

    Joined:
    Aug 14, 2014
    Posts:
    26
    I simply loose all visuals when I apply this method, no errors given :(
    P.S sorry for the late reply, very busy.
    I can't think of any reason other than incorrect uv/array lengths.
     
  14. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    it's possible you need to flip to UV's. Facing the wrong direction makes it so that the mesh or object...essentially disappear. So maybe make a simple function that goes through and flips them back and let me know if that makes any difference. The code above is also for icosahedrons where every face is the same size. A cylinder has various sizes. are you proceduraly creating the cylinder?