*****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());