# Textureing subdivided Icosahedron in XNA, seam problem

**Look below for the updated source code**

I have started to to make some different types of objects and create them by code in XNA. Right now I have finished the code for creating a Icosahedron. And works grate as it should. I have managed to get it textured by some tips from this page.

Like the guy in this thread I get a seam. Not sure how to solve it properly.

Here is my code for what sets up the resulting Vertices and Indices

*(since update this “IndexVertives” is not really what is used anymore. Check the “Init” in the update bellow)*

```
private void IndexVertices(List<Vector3> originalVertices, out VertexPositionNormalTexture[] outVertices, out int[] outIndices)
{
Dictionary<Vector3, int> vertexIndices = new Dictionary<Vector3, int>();
List<VertexPositionNormalTexture> vertices = new List<VertexPositionNormalTexture>();
int indexCounter = 0;
int runTime = 0;
foreach (Vector3 position in originalVertices)
{
if (!vertexIndices.ContainsKey(position))
{
Vector3 normal = new Vector3(0, 0, 1);
Vector2 texCord = GetTextureCoord(position);
vertices.Add(new VertexPositionNormalTexture() { Position = position, Normal = normal, TextureCoordinate = texCord });
runTime++;
vertexIndices.Add(position, indexCounter++);
}
}
List<int> indices = new List<int>();
foreach (Vector3 position in originalVertices)
{
indices.Add(vertexIndices[position]);
}
outVertices = CalculateNormals(vertices.ToArray(), indices.ToArray());
outIndices = indices.ToArray();
}
```

And here comes my version of the “GetTextureCoord” from the forum I mentioned earlier.

```
private Vector2 GetTextureCoord(Vector3 normal)
{
float targetU;
float targetV;
float normalisedX = 0;
float normalisedZ = -1;
if (((normal.X * normal.X) + (normal.Z * normal.Z)) > 0)
{
normalisedX = (float)Math.Sqrt((normal.X * normal.X) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
if (normal.X < 0)
{
normalisedX = -normalisedX;
}
normalisedZ = (float)Math.Sqrt((normal.Z * normal.Z) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
if (normal.Z < 0)
{
normalisedZ = -normalisedZ;
}
}
if (normalisedZ == 0)
{
targetU = (float)((normalisedX * Math.PI) / 2);
}
else
{
targetU = (float)Math.Atan(normalisedX / normalisedZ);
if (normalisedZ < 0)
{
targetU += (float)Math.PI;
}
}
if (targetU < 0)
{
targetU += (float)(2 * Math.PI);
}
targetU /= (float)(2 * Math.PI);
targetV = (-normal.Y + 1) / 2;
return new Vector2 { X = targetU, Y = targetV };
}
```

In this forum thread There is apparently two ways it was managed to get solved. One way was to “*count the number of faces used for a given level of subdivision, and thus be able to tell when I was outputting a face if it lay on the seam*“.

The other on was adding this to the “DrawTriangel” code.

```
point.Set (v1[0], v1[1], v1[2]);
GetTextureCoord (&point, &texU1, &texV);
point.Set (v2[0], v2[1], v2[2]);
GetTextureCoord (&point, &texU2, &texV);
point.Set (v3[0], v3[1], v3[2]);
GetTextureCoord (&point, &texU3, &texV);
if (texU2 - texU1 > 0.2 || texU3 - texU1 > 0.2){
texAdd1 = 1;
}
if (texU1 - texU2 > 0.2 || texU3 - texU2 > 0.2){
texAdd2 = 1;
}
if (texU1 - texU3 > 0.2 || texU2 - texU3 > 0.2){
texAdd3 = 1;
}
```

And now to my question. Im not really sure how I could apply any of these ideas for solving this issue with the seam that appears.

So, anyone who have an idea how to solve this?

Not sure how I could convert and apply the code mentioned to my Vertices foreach-loop, or how to do this “counting” as mentioned.

**EDIT 2012-08-11**

OK, Iv updated my entire code to generate the subdivided Icosahedron as it was kind of complex before. It works like before with the seam.

So here it comes. should be easier to try out this code also.

```
public class Icosahedron2 : Component, I3DComponent
{
#region // I3DComponent values
Vector3 position = Vector3.Zero;
Matrix rotation = Matrix.Identity;
Vector3 scale = new Vector3(1, 1, -1);
BoundingBox boundingBox = new BoundingBox(new Vector3(-1), new Vector3(1));
public Vector3 Position { get { return position; } set { position = value; } }
public Vector3 EulerRotation
{
get { return MathUtil.MatrixToVector3(Rotation); }
set { this.Rotation = MathUtil.Vector3ToMatrix(value); }
}
public Matrix Rotation { get { return rotation; } set { rotation = value; } }
public Vector3 Scale { get { return scale; } set { scale = value; } }
public BoundingBox BoundingBox { get { return boundingBox; } }
#endregion
// Effect
BasicEffect basicEffect;
// Terrain texture
Texture2D terrainTexture;
//Icosahedron variables
private struct Triangle
{
public Vector3 v1;
public Vector3 v2;
public Vector3 v3;
public bool OnSeam;
public string vectorOnSeam;
public override string ToString()
{
return "On seam:" + OnSeam.ToString() + " " + vectorOnSeam + ", " + v1.ToString() + ", " + v2.ToString() + ", " + v3.ToString();
}
}
List<Triangle> BaseTrangleList = new List<Triangle>();
List<Triangle> SubTringleList = new List<Triangle>();
private float Radius;
Dictionary<Vector3, int> vertexIndices = new Dictionary<Vector3, int>();
List<VertexPositionNormalTexture> vertices = new List<VertexPositionNormalTexture>();
VertexPositionNormalTexture[] renderVertecis;
List<int> indices = new List<int>();
int[] renderIndices;
#region //Base Indices for Ichsahedron
/// <summary>
/// The indices of an icosahedron.
/// </summary>
static readonly int[] tindices =
{
1,4,0, 4,9,0, 4,5,9, 8,5,4, 1,8,4,
1,10,8, 10,3,8, 8,3,5, 3,2,5, 3,7,2,
3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,
10,1,6, 11,0,9, 2,11,9, 5,2,9, 11,2,7
};
#endregion
#region //Base positions for Icosahedron
/// <summary>
/// The vertices of an icosahedron.
/// </summary>
Vector3[] vdata;
float X_BASE = 0.525731112119133606f;
float Z_BASE = 1.0f;
private void SetBasePositions()
{
// {-X_BASE,0.0,Z_BASE},{X_BASE,0.0,Z_BASE},{-X_BASE,0.0,-Z_BASE},{X_BASE,0.0,-Z_BASE},
//{0.0,Z_BASE,X_BASE},{0.0,Z_BASE,-X_BASE},{0.0,-Z_BASE,X_BASE},{0.0,-Z_BASE,-X_BASE},
//{Z_BASE,X_BASE,0.0},{-Z_BASE,X_BASE,0.0},{Z_BASE,-X_BASE,0.0},{-Z_BASE,-X_BASE,0.0}};
vdata = new Vector3[]
{
Vector3.Normalize(new Vector3(-X_BASE, 0, Z_BASE)),
Vector3.Normalize(new Vector3(X_BASE, 0, Z_BASE)),
Vector3.Normalize(new Vector3(-X_BASE, 0, -Z_BASE)),
Vector3.Normalize(new Vector3(X_BASE, 0, -Z_BASE)),
Vector3.Normalize(new Vector3(0, Z_BASE, X_BASE)),
Vector3.Normalize(new Vector3(0, Z_BASE, -X_BASE)),
Vector3.Normalize(new Vector3(0, -Z_BASE, X_BASE)),
Vector3.Normalize(new Vector3(0, -Z_BASE, -X_BASE)),
Vector3.Normalize(new Vector3(Z_BASE, X_BASE, 0)),
Vector3.Normalize(new Vector3(-Z_BASE, X_BASE, 0)),
Vector3.Normalize(new Vector3(Z_BASE, -X_BASE, 0)),
Vector3.Normalize(new Vector3(-Z_BASE, -X_BASE, 0))
};
}
#endregion
#region //Init(float radius, int subdivisions)
private void Init(float radius, int subdivisions)
{
//Builds base triangles
Radius = radius;
SetBasePositions();
for (int i = 0; i < tindices.Length; i += 3)
{
Triangle tri = new Triangle()
{
v1 = vdata[tindices[i]],
v2 = vdata[tindices[i + 1]],
v3 = vdata[tindices[i + 2]],
};
BaseTrangleList.Add(tri);
}
//Creates the subdevided triangles to make all of them
foreach (Triangle triangle in BaseTrangleList)
{
Subdivide(triangle, subdivisions);
}
//checks if triangles are placed on seam
CheckForSeamPlacement();
//Creates verticis for all subdevided triangels
int idCounter = 0;
foreach(Triangle sub in SubTringleList)
{
if (!vertexIndices.ContainsKey(sub.v1))
{
Vector3 normal = new Vector3(0, 0, 1);
Vector2 texCord = GetTextureCoord(sub.v1);
vertices.Add(new VertexPositionNormalTexture() { Position = sub.v1, Normal = normal, TextureCoordinate = texCord });
vertexIndices.Add(sub.v1, idCounter++);
if (sub.vectorOnSeam.Contains("1"))
{
//texCord.X = 1;
//Vector3 v12 = sub.v1;
//v12.X = 0.0000001f;
//vertices.Add(new VertexPositionNormalTexture() { Position = v12, Normal = normal, TextureCoordinate = texCord });
//vertexIndices.Add(v12, idCounter++);
}
}
if (!vertexIndices.ContainsKey(sub.v2))
{
Vector3 normal = new Vector3(0, 0, 1);
Vector2 texCord = GetTextureCoord(sub.v2);
vertices.Add(new VertexPositionNormalTexture() { Position = sub.v2, Normal = normal, TextureCoordinate = texCord });
vertexIndices.Add(sub.v2, idCounter++);
if (sub.vectorOnSeam.Contains("2"))
{
//texCord.X = 1;
//Vector3 v22 = sub.v2;
//v22.X = 0.0000001f;
//vertices.Add(new VertexPositionNormalTexture() { Position = v22, Normal = normal, TextureCoordinate = texCord });
//vertexIndices.Add(v22, idCounter++);
}
}
if (!vertexIndices.ContainsKey(sub.v3))
{
Vector3 normal = new Vector3(0, 0, 1);
Vector2 texCord = GetTextureCoord(sub.v3);
vertices.Add(new VertexPositionNormalTexture() { Position = sub.v3, Normal = normal, TextureCoordinate = texCord });
vertexIndices.Add(sub.v3, idCounter++);
if (sub.vectorOnSeam.Contains("3"))
{
//texCord.X = 1;
//Vector3 v22 = sub.v2;
//v22.X = 0.0000001f;
//vertices.Add(new VertexPositionNormalTexture() { Position = v22, Normal = normal, TextureCoordinate = texCord });
//vertexIndices.Add(v22, idCounter++);
}
}
}
foreach (Triangle pos in SubTringleList)
{
indices.Add(vertexIndices[pos.v1]);
indices.Add(vertexIndices[pos.v2]);
indices.Add(vertexIndices[pos.v3]);
}
renderIndices = indices.ToArray();
renderVertecis = CalculateNormals(vertices.ToArray(), indices.ToArray());
//renderVertecis = vertices.ToArray();
}
#endregion
#region //CheckForSeamPlacement()
/// <summary>
/// Checks if any of the triangles are placed on the texture coord 0
/// </summary>
private void CheckForSeamPlacement()
{
List<Triangle> subSeamCheck = new List<Triangle>();
foreach (Triangle sub in SubTringleList)
{
Triangle tri = sub;
tri.vectorOnSeam = string.Empty;
if (GetTextureCoord(tri.v1).X == 0)
{
tri.OnSeam = true;
tri.vectorOnSeam += "1";
}
if (GetTextureCoord(tri.v2).X == 0)
{
tri.OnSeam = true;
tri.vectorOnSeam += "2";
}
if (GetTextureCoord(tri.v3).X == 0)
{
tri.OnSeam = true;
tri.vectorOnSeam += "3";
}
subSeamCheck.Add(tri);
}
SubTringleList = subSeamCheck;
}
#endregion
//private bool FoundIndice(Vector3 pos)
//{
// bool contains = false;
// foreach (Vector3 indi in vertexIndices)
// {
// if (indi == pos)
// {
// contains = true;
// break;
// }
// }
// return contains;
//}
#region //Vector2 GetTextureCoord(Vector3 normal)
private Vector2 GetTextureCoord(Vector3 normal)
{
float targetU;
float targetV;
float normalisedX = 0;
float normalisedZ = -1;
if (((normal.X * normal.X) + (normal.Z * normal.Z)) > 0)
{
normalisedX = (float)Math.Sqrt((normal.X * normal.X) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
if (normal.X < 0)
{
normalisedX = -normalisedX;
}
normalisedZ = (float)Math.Sqrt((normal.Z * normal.Z) / ((normal.X * normal.X) + (normal.Z * normal.Z)));
if (normal.Z < 0)
{
normalisedZ = -normalisedZ;
}
}
if (normalisedZ == 0)
{
targetU = (float)((normalisedX * Math.PI) / 2);
}
else
{
targetU = (float)Math.Atan(normalisedX / normalisedZ);
if (normalisedZ < 0)
{
targetU += (float)Math.PI;
}
}
if (targetU < 0)
{
targetU += (float)(2 * Math.PI);
}
targetU /= (float)(2 * Math.PI);
targetV = (-normal.Y + 1) / 2;
return new Vector2 { X = targetU, Y = targetV };
}
#endregion
#region //Subdivide(Triangle triangle, long subdiv)
/// <summary>
/// Devides the triangel according to the subdiv value
/// </summary>
/// <param name="triangle"></param>
/// <param name="subdiv"></param>
private void Subdivide(Triangle triangle, long subdiv)
{
Vector3 v12, v23, v31, v12_2, v23_2, v31_2;
if (subdiv == 0)
{
SubTringleList.Add(triangle);
return;
}
v12 = triangle.v1 + triangle.v2;
v23 = triangle.v2 + triangle.v3;
v31 = triangle.v3 + triangle.v1;
v12_2 = Vector3.Multiply(Vector3.Normalize(v12),Radius);
v23_2 = Vector3.Multiply(Vector3.Normalize(v23),Radius);
v31_2 = Vector3.Multiply( Vector3.Normalize(v31),Radius);
Triangle t1 = new Triangle { v1 = triangle.v1, v2 = v12_2, v3 = v31_2 };
Triangle t2 = new Triangle { v1 = triangle.v2, v2 = v23_2, v3 = v12_2 };
Triangle t3 = new Triangle { v1 = triangle.v3, v2 = v31_2, v3 = v23_2 };
Triangle t4 = new Triangle { v1 = v12_2, v2 = v23_2, v3 = v31_2 };
Subdivide( t1, subdiv - 1);
Subdivide( t2, subdiv - 1);
Subdivide( t3, subdiv - 1);
Subdivide( t4, subdiv - 1);
}
#endregion
#region //CalculateNormals()
/// <summary>
/// Calculates Noramals for vertices
/// </summary>
private static VertexPositionNormalTexture[] CalculateNormals(VertexPositionNormalTexture[] vertices, int[] indices)
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = new Vector3(0, 0, 0);
for (int i = 0; i < indices.Length / 3; i++)
{
int index1 = indices[i * 3];
int index2 = indices[i * 3 + 1];
int index3 = indices[i * 3 + 2];
Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
Vector3 normal = Vector3.Cross(side1, side2);
vertices[index1].Normal += normal;
vertices[index2].Normal += normal;
vertices[index3].Normal += normal;
}
for (int i = 0; i < vertices.Length; i++)
{
vertices[i].Normal.Normalize();
}
return vertices;
}
#endregion
public Icosahedron2(float radius, int subdevisions)
: base()
{
basicEffect = new BasicEffect(Engine.GraphicsDevice);
SetupEffect();
Radius = radius;
Init(radius, subdevisions);
}
public Icosahedron2(float radius, int subdevisions, Texture2D Texture)
: base()
{
basicEffect = new BasicEffect(Engine.GraphicsDevice);
terrainTexture = Texture;
SetupEffect();
Radius = radius;
Init(radius, subdevisions);
}
#region// Setup BasicEffect
/// <summary>
/// Setsup basic effect parameters
/// </summary>
private void SetupEffect()
{
basicEffect.Texture = terrainTexture;
basicEffect.TextureEnabled = true;
//basicEffect.VertexColorEnabled = false;
//basicEffect.EnableDefaultLighting();
//basicEffect.DirectionalLight0.Direction = new Vector3(1, -1, 1);
//basicEffect.DirectionalLight0.Enabled = true;
//basicEffect.AmbientLightColor = new Vector3(0.3f, 0.3f, 0.3f);
//basicEffect.DirectionalLight1.Enabled = false;
//basicEffect.DirectionalLight2.Enabled = false;
basicEffect.SpecularColor = new Vector3(0, 0, 0);
}
#endregion
#region //Draw
/// <summary>
/// Draw object
/// </summary>
public override void Draw()
{
// Look for a camera in the service container
Camera camera = Engine.Services.GetService<Camera>();
// Throw an exception if one isn't present
if (camera == null)
{
throw new Exception("Camera not found in engine's"
+ "service container, cannot draw");
}
// Set effect values
basicEffect.World = MathUtil.CreateWorldMatrix(position, rotation, scale);
basicEffect.View = camera.View;
basicEffect.Projection = camera.Projection;
//RasterizerState rs = new RasterizerState();
//rs.CullMode = CullMode.None;
//rs.FillMode = FillMode.WireFrame;
//Engine.GraphicsDevice.RasterizerState = rs;
Engine.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
//Engine.GraphicsDevice.BlendState = BlendState.AlphaBlend;
//Engine.GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
//VertexPositionNormalTexture
pass.Apply();
Engine.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, renderVertecis, 0, renderVertecis.Length, renderIndices, 0, renderIndices.Length / 3, VertexPositionNormalTexture.VertexDeclaration);
}
}
#endregion
}
```

**Final update 2012-8-12**

Thanks to David Lively this problem has finally be solved.

Using this code like this in the updated code will solve the problem.

```
RepairTextureWrapSeam(vertices, indices);
renderIndices = indices.ToArray();
renderVertecis = CalculateNormals(vertices.ToArray(), indices.ToArray());
```

MxrFxron November 30, -0001 @ 12:00 AMI’ve been strugling with issue myself, and finally made it work.

First of all, I learned that the octahedron should have the right orientation, because then you won’t have to split any triangles. This is hard for me to explain, so here a picture for clarity:

http://i.imgur.com/4Ip4C.jpg

In this picture, I’ve highlighted the vertices with red, where the texture U coordinate is 0.

As you can see, (A) produces the zig-zag-like distortion, while (B) produces only vertical distortion. You want the orientation in (B), as this will save you some trouble. You can simply do this by modifying the starting configuration for the vertices of the octahedron you’re subdiving.

My approach has been diffrent from yours, as I work with Points (Vector3’s) and Triangles (collection of 3 indices), and then convert them to proper Index and Vertex objects once I’m done modifying them. This turned out to be usefull when correcting this issue.

Now, to get rid of the distorion follow theese steps:

Check each triangle if it is on the seam.

1a. Get each texture coord for the triangle.

1b. If one or two have their U coord = 0, it is on the seam

1c. If the remaining texcoords have U > 0.5 (closer to 1 than 0) this triangle is also causing distortion.

This approach will only work, if you have the right orintation of the octahderon as I mentioned earlier. I can’t quite tell you how to implement this solution in your approach, but I hope this information can help you.

Edit: Btw, you can use this method for getting texture coordinates instead:

Edit (2012-08-12)I’ve have cleaned and commented on my working solution – It’s far from optimized at this point, but it gets the job done though.

It’s quite a lot of code with the comments and all, so I’ve put it at http://pastebin.com/E6qG234u

David Livelyon November 30, -0001 @ 12:00 AMHere’s a method I wrote about a year ago to fix this.

Essentially, it looks for triangles that have UV coordinates that appear in the wrong winding order, which should only happen when a face would span a texture edge. For instance, an edge

`(v0,v1)`

with UV coordinates`(0.99,0),(0.01,0)`

. Your seam appears because the rasterizer doesn’t realize that you only want the far right and left edge – it interpolates from`0.99`

down to`0.01`

, which causes your entire texture width to be squeezed into that face.Call this method after you’ve completely generated your sphere. (Alternatively, you could examine the UV order as the sphere is generated, or even before subdivision, but that’s another topic.)

This method tracks the number of changes that are made during the repair; the idea being that, if it’s a LOT relative to the number of vertices in your model, something may be amiss.

I seem to recall writing this at about 4 AM before a presentation, so it’s probably not the quickest or cleanest implementation, but the approach is sound and should work for any closed polyhedron.

Daniel Carlssonon November 30, -0001 @ 12:00 AMTo texture map a model you supply a 2D coordinate for each vertice, this coordinate is used to map the texture (2D) to the model (3D). This works fine for most models, but when your texturing a continous spherical object (such as a model of earth) you will at some point have a vertice where one of the coordinates is at the highest (X or Y value), and the next one is at its lowest. At this point the graphics card won’t wrap around the texture the short way, instead it will go back over the visible texture, thus reflecting the texture and rendering the whole texture, causing a visible seam.

To fix this, as mentioned in the comments, you need to duplicate the vertices where the texture coordinate is at 0 (for one of the directions, X in this example), the duplicate vertice needs to have the texture coordinate set to 1, this allows the graphics card to understand that it should continue in a left to right direction in the texture, rather than going right to left.