i'm a newbie and i am following a tutorial on procedural landmass generation. However, my plane does not look right. It has a lot of seams/cracks. Is there someone who can point me in the right direction?
Below is my MeshGenerator scripts:
public static class MeshGenerator
{
public static MeshData GenerateTerrainMesh(float[,] heightMap, float heightMultiplier, AnimationCurve heightCurve)
{
int width = heightMap.GetLength(0);
int height = heightMap.GetLength(1);
float topLeftX = (width - 1) / -2f;
float topLeftZ = (height - 1) / 2f;
MeshData meshData = new MeshData(width, height);
int vertexIndex = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
meshData.vertices[vertexIndex] = new Vector3(topLeftX + x, heightCurve.Evaluate(heightMap[x,y]) * heightMultiplier, topLeftZ - y);
meshData.uvs[vertexIndex] = new Vector2(x / (float)width, y / (float)height);
if (x < width - 1 && y < height - 1)
{
meshData.AddTriangle(vertexIndex, vertexIndex + width + 1, vertexIndex + width);
meshData.AddTriangle(vertexIndex, + width + 1, vertexIndex + 1);
}
vertexIndex++;
}
}
return meshData;
}
}
public class MeshData
{
public Vector3[] vertices;
public int[] triangles;
public Vector2[] uvs;
int triangleIndex;
public MeshData(int meshWidth, int meshHeight)
{
vertices = new Vector3[meshWidth * meshHeight];
uvs = new Vector2[meshWidth * meshHeight];
triangles = new int[(meshWidth-1) * (meshHeight-1)*6];
}
public void AddTriangle(int a, int b, int c)
{
triangles[triangleIndex] = a;
triangles[triangleIndex+1] = b;
triangles[triangleIndex+2] = c;
triangleIndex += 3;
}
public Mesh CreateMesh()
{
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uvs;
mesh.RecalculateNormals();
return mesh;
}
}
You triangle indices are wrong, this is rather obvious since you have a bunch of triangle-shaped holes. Notably
meshData.AddTriangle(vertexIndex, + width + 1, vertexIndex + 1);
the second vertex of the second triangle is a constant value, and that is most likely incorrect
You should not need to keep a running total of vertexIndex, you should be perfectly able to compute the triangle indices from the grid indices:
var v1 = y * (width+1) + x; // You should have one more column of vertices than you have grid cells
var v2 = v2 + 1; // the vertex one column to the right
var v3 = v1 + width+1; // the vertex one row down
var v4 = v3 + 1;
meshData.AddTriangle(v1, v2, v4);
meshData.AddTriangle(v1, v4, v3);
You may need to invert the vertex order to ensure the normals are oriented correctly.
I use the following code to generate a Cube as a single mesh. My purpose is to generate a sphere from it by normalizing as I have shown in the commented line (I just have to do that to all those statements in the following lines). The problem here is that the mesh changes from a cube to a flat plane as I keep increasing the resolution (parameter given as public int resolution).
(This code was inspired by this video https://youtu.be/QN39W020LqU . But I am using the technique in my own way as given by the following code, so that I can generate a single mesh instead of a combination of 6 meshes, this is required for my work)
[code=CSharp]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sc_Planet : MonoBehaviour
{
[Range(2, 512)]
public int resolution = 2;
[Range(2, 256)]
public int radius = 10;
MeshFilter meshFilter;
void OnValidate()
{
Initialize();
}
void Initialize()
{
if (meshFilter == null)
{
GameObject meshObj = new GameObject("mesh_Planet");
meshObj.transform.parent = transform;
meshObj.AddComponent<MeshRenderer>().sharedMaterial = new Material(Shader.Find("Standard"));
meshFilter = meshObj.AddComponent<MeshFilter>();
meshFilter.sharedMesh = new Mesh();
}
int xmax = resolution + 1;
int ymax = resolution + 1;
float dx = 1.0f / resolution;
float dy = 1.0f / resolution;
Vector3[] vertsTop = new Vector3[xmax * ymax];
Vector3[] vertsRight = new Vector3[xmax * ymax];
Vector3[] vertsFront = new Vector3[xmax * ymax];
Vector3[] vertsBottom = new Vector3[xmax * ymax];
Vector3[] vertsLeft = new Vector3[xmax * ymax];
Vector3[] vertsBack = new Vector3[xmax * ymax];
for (int y = 0; y < ymax; y++)
{
for (int x = 0; x < xmax; x++)
{
float px = dx * x - 0.5f;
float py = dy * y - 0.5f;
int t = x + y * xmax;
//vertsTop[t] = new Vector3(py, 0.5f, px).normalized * radius;
vertsTop[t] = new Vector3(py, 0.5f, px);
vertsRight[t] = new Vector3(px, py, 0.5f);
vertsFront[t] = new Vector3(0.5f, px, py);
vertsBottom[t] = new Vector3(px, -0.5f, py);
vertsLeft[t] = new Vector3(py, px, -0.5f);
vertsBack[t] = new Vector3(-0.5f, py, px);
}
}
List<int> trianglesList = new List<int>();
for (int y = 0; y < ymax - 1; ++y)
{
for (int x = 0; x < xmax; ++x)
{
if (x % xmax != xmax - 1)
{
int f = x + y * xmax;
trianglesList.Add(f);
trianglesList.Add(f + 1);
trianglesList.Add(f + 1 + xmax);
trianglesList.Add(f);
trianglesList.Add(f + 1 + xmax);
trianglesList.Add(f + xmax);
}
}
}
List<Vector3> verts = new List<Vector3>();
Dictionary<Vector3, int> vdict = new Dictionary<Vector3, int>();
List<int> triangles = new List<int>();
int nextIndex = 0;
void addFace(Vector3 [] in_verts, List<int> in_triangles)
{
for(int i = 0; i < in_verts.Length; ++i)
{
if (!vdict.ContainsKey(in_verts[i]))
{
vdict.Add(in_verts[i], nextIndex);
verts.Add(in_verts[i]);
++nextIndex;
}
}
for(int i = 0; i < in_triangles.Count; ++i)
{
triangles.Add(vdict[in_verts[in_triangles[i]]]);
}
}
addFace(vertsTop, trianglesList);
addFace(vertsRight, trianglesList);
addFace(vertsFront, trianglesList);
addFace(vertsBottom, trianglesList);
addFace(vertsLeft, trianglesList);
addFace(vertsBack, trianglesList);
var mesh = meshFilter.sharedMesh;
mesh.Clear();
mesh.vertices = verts.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
}
}
[/code]
This code works in Blender (I used python to script it on Blender and it works very well for any resolution).
The only problem is that when I use this in Unity, the meshes become weird as I have shown in the images I have attached below.
At Resolution = 96 :
At Resolution = 122 :
At Resolution = 182 :
At Resolution = 344:
Why is this happening?
How should I correct it?
(I have also posted this in unity forums: Why cube mesh becomes a plane when in high resolution?)
Ok I found the answer. This is exceeding the limit of vertices on unity api for 16-bit based meshes. I had to change it to a 32-bit indexed mesh to correct it.
Details are in this docuemntaiton page : https://docs.unity3d.com/ScriptReference/Rendering.IndexFormat.html?_ga=2.9556401.501737799.1635227368-67181881.1629608252
I just had to add the code :
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
That was it.
I am following a tutorial about voxel meshes and ran into an error that I did not expect. Here is the tutorial, I am on the second part if you go and take a look at that. I also thought maybe this is a unity version issue as this is from 2014. The errors I have searched and found no conclusive results. I checked for the void statements over and over and seen many repetitive stackoverflow questions. I have also tried restarting unity multiple times. More specific information on the errors: On Line 118,119,and 120 i get these two errors. Error CS0501: Method Must have return type. Error CS0501: 'PolygonGenerator.PolygonGenerator()' must declare a body because it is not marked abstract, extern, or partial. Anyways, my code is as follows: (also, please correct my comments and add more)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PolygonGenerator : MonoBehaviour
{
// This first list contains every vertex of the mesh that we are going to render
public List<Vector3> newVertices = new List<Vector3>();
// The triangles tell Unity how to build each section of the mesh joining
// the vertices
public List<int> newTriangles = new List<int>();
// The UV list is unimportant right now but it tells Unity how the texture is
// aligned on each polygon
public List<Vector2> newUV = new List<Vector2>();
private float tUnit = 0.25f;
private Vector2 tStone = new Vector2(0, 0);
private Vector2 tGrass = new Vector2(0, 1);
// A mesh is made up of the vertices, triangles and UVs we are going to define,
// after we make them up we'll save them as this mesh
private Mesh mesh;
private int squareCount;
public byte[,] blocks; //0=air 1=rock, 2=grass
// Start is called before the first frame update
void Start()
{
//gets the mesh of the gameobject
mesh = GetComponent<MeshFilter>().mesh;
//gets the x,y, and z values of the gameobject
//writing x is easier than transform.position.x many times
float x = transform.position.x;
float y = transform.position.y;
float z = transform.position.z;
// defines what corners of the mesh to use for the four corners of the texture
newUV.Add(new Vector2(tUnit * tStone.x, tUnit * tStone.y + tUnit));
newUV.Add(new Vector2(tUnit * tStone.x + tUnit, tUnit * tStone.y + tUnit));
newUV.Add(new Vector2(tUnit * tStone.x + tUnit, tUnit * tStone.y));
newUV.Add(new Vector2(tUnit * tStone.x, tUnit * tStone.y));
//clear anything within the meshes boundries
mesh.Clear();
mesh.vertices = newVertices.ToArray(); //set the meshes vertecies to the new ones we just made
mesh.triangles = newTriangles.ToArray();
mesh.uv = newUV.ToArray(); // applys uvs to the mesh
mesh.Optimize(); //unity does some stuff
mesh.RecalculateNormals(); //
}
void GenSquare(int x, int y, Vector2 texture)
{
//defines the vertexes of the new square
newVertices.Add(new Vector3(x, y, z));
newVertices.Add(new Vector3(x + 1, y, z));
newVertices.Add(new Vector3(x + 1, y - 1, z));
newVertices.Add(new Vector3(x, y - 1, z));
//without triangels all we have is points in space, no connections
//these are added clockwise
newTriangles.Add(squareCount * 4); // 0,0
newTriangles.Add((squareCount * 4) +1); // 1,0
newTriangles.Add((squareCount * 4)+3); // -1,0
newTriangles.Add((squareCount * 4)+1); // 1,0
newTriangles.Add((squareCount * 4)+2); // -1,1
newTriangles.Add((squareCount * 4)+3); // -1,0
// defines what corners of the mesh to use for the four corners of the texture
newUV.Add(new Vector2(tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2(tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2(tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2(tUnit * texture.x, tUnit * texture.y));
squareCount++;
}
void GenTerrain()
{
blocks = new byte[10, 10];
for (int px = 0; px < blocks.GetLength(0); px++)
{
for (int py = 0; py < blocks.GetLength(1); py++)
{
if (py >= 5)
{
blocks[px, py] = 2;
}
else if (py < 5)
{
blocks[px, py] = 1;
}
}
}
}
void BuildMesh()
{
for (int px = 0; px < blocks.GetLength(0); px++)
{
for (int py = 0; py < blocks.GetLength(1); py++)
{
if (blocks[px, py] == 1)
{
GenSquare(px, py, tStone);
}
else if (blocks[px, py] == 2)
{
GenSquare(px, py, tGrass);
}
}
}
}
GenTerrain();
BuildMesh();
UpdateMesh();
// Update is called once per frame
void Update()
{
//clear anything within the meshes boundries
mesh.Clear();
mesh.vertices = newVertices.ToArray(); //set the meshes vertecies to the new ones we just made
mesh.triangles = newTriangles.ToArray();
mesh.uv = newUV.ToArray(); // applys uvs to the mesh
mesh.Optimize(); //unity does some stuff
mesh.RecalculateNormals(); //
squareCount = 0;
newVertices.Clear();
newTriangles.Clear();
newUV.Clear();
}
}
GenTerrain();
BuildMesh();
UpdateMesh();
These three method calls should be in a method, probably start or update. The computer thinks you're trying to define new methods here.
I've been working on a program to generate an icosphere. (A sphere with evenly distributed vertexes across the face, to be used for terrain deformation)
I have pretty much everything done, the sphere is generated, subdivided and drawn. The problem I am running into is that somewhere in the code, some of the vertices (the twelve starting vertexes, I believe) are being set to twice the radius, rather than just the radius.
Here are three images, showing the icosphere at zero, one and two refinement passes:
http://i41.photobucket.com/albums/e262/cstgirllover/Cho/Ico0Refinement.png
http://i41.photobucket.com/albums/e262/cstgirllover/Cho/Ico1Refinement.png
http://i41.photobucket.com/albums/e262/cstgirllover/Cho/Ico2Refinement.png
and here is the code that generates the icosahedron, and then breaks it down into the icosphere:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Icosahedron_Test
{
class Icosahedron
{
int radius; // radius of the planet
int refinement; // number of times to refine the traingles
int faces = 20;
Vector3[] basePositions; // Vertex points for three defining rectangles
TriXYZ[] vertices; // Vertex points for triangles which define the spherical surface
public Icosahedron(int tRadius, int tRefinement, TriXYZ[] tVertices)
{
radius = tRadius;
refinement = tRefinement;
vertices = tVertices;
}
public TriXYZ[] InitializeArray()
{
double t = radius*((1+Math.Sqrt(5))/2);
Vector3[] basePositions =
{
//First Rectangle
new Vector3(-radius, (float)t, 0),
new Vector3(radius, (float)t, 0),
new Vector3(-radius, (float)-t, 0),
new Vector3(radius, (float)-t, 0),
//Seconds Rectangle
new Vector3(0, -radius, (float)t),
new Vector3(0, radius, (float)t),
new Vector3(0, -radius, (float)-t),
new Vector3(0, radius, (float)-t),
//Third Rectangle
new Vector3((float)t, 0, -radius),
new Vector3((float)t, 0, radius),
new Vector3((float)-t, 0, -radius),
new Vector3((float)-t, 0, radius)
};
TriXYZ[] vertices =
{
new TriXYZ(basePositions[5], basePositions[11], basePositions[0], 1),
new TriXYZ(basePositions[1], basePositions[5], basePositions[0], 1),
new TriXYZ(basePositions[7], basePositions[1], basePositions[0], 1),
new TriXYZ(basePositions[10], basePositions[7], basePositions[0], 1),
new TriXYZ(basePositions[11], basePositions[10], basePositions[0], 1),
new TriXYZ(basePositions[9], basePositions[5], basePositions[1], 1),
new TriXYZ(basePositions[4], basePositions[11], basePositions[5], 1),
new TriXYZ(basePositions[2], basePositions[10], basePositions[11], 1),
new TriXYZ(basePositions[6], basePositions[7], basePositions[10], 1),
new TriXYZ(basePositions[8], basePositions[1], basePositions[7], 1),
new TriXYZ(basePositions[4], basePositions[9], basePositions[3], 1),
new TriXYZ(basePositions[2], basePositions[4], basePositions[3], 1),
new TriXYZ(basePositions[6], basePositions[2], basePositions[3], 1),
new TriXYZ(basePositions[8], basePositions[6], basePositions[3], 1),
new TriXYZ(basePositions[9], basePositions[8], basePositions[3], 1),
new TriXYZ(basePositions[5], basePositions[9], basePositions[4], 1),
new TriXYZ(basePositions[11], basePositions[4], basePositions[2], 1),
new TriXYZ(basePositions[10], basePositions[2], basePositions[6], 1),
new TriXYZ(basePositions[7], basePositions[6], basePositions[8], 1),
new TriXYZ(basePositions[1], basePositions[8], basePositions[9], 1),
};
return vertices;
}
public TriXYZ[] Refine(TriXYZ[] rVertices, int rRefinement, float radius)
{
TriXYZ[] tVertices; // Temp list of triangles
Vector3 vertex1; // position of first vertex of base triangle
Vector3 vertex2; // position of second vertex of base triangle
Vector3 vertex3; // position of third vertex of base triangle
int tDepth; // depth of the current triangle
//int listPos = 0; // base list position integer
int nListPos = 0; // new list position integer
int cRefine = 0; // current refinement iteration
while(cRefine < rRefinement) // loop until the icosphere has been refined the inputted number of times
{
tVertices = new TriXYZ[20 + (4*rVertices.Length)]; // make the temporary list empty, and long enough for the original 20 triangles, plus four per triangle for each level of refinement.
for (int listPos = 0; listPos < rVertices.Length; listPos++ ) // Loop through every triangle in the list
{
TriXYZ cTriangle = rVertices[listPos];
tDepth = cTriangle.GetDepth;
vertex1 = cTriangle.GetVertex1; // point 0
vertex2 = cTriangle.GetVertex2; // point 1
vertex3 = cTriangle.GetVertex3; // point 2
if (tDepth == cRefine + 1) // if the depth of this triangle in the list equals the current refinement iteration;
// depth one for first refinement pass, depth two for second, etc; subdivide the triangle
// This prevents unnecessarily re-refining old triangles
{
TriXYZ[] parts = new TriXYZ[5];
parts = cTriangle.subDivide(radius);
tVertices[nListPos] = parts[0]; // Put the original larger triangle at the front if the list
tVertices[nListPos + 1] = parts[1]; // First subdivided triangle
tVertices[nListPos + 2] = parts[2]; // Second subdivided triangle
tVertices[nListPos + 3] = parts[3]; // Third subdivided triangle
tVertices[nListPos + 4] = parts[4]; // Fourth subdivided triangle
nListPos = nListPos + 5; // Move forward in the new triangle list so the next set of triangles doesn't overwrite this set.
}
else if (tDepth < cRefine + 1) // Ifthe triangle's depth is less than the current refinement iteration (depth 1 on refinement 2) then add the current triangle to the new list at nListPos
{
tVertices[nListPos] = new TriXYZ(vertex1, vertex2, vertex3, tDepth);
nListPos++;
}
// it shouldn't be possible for the tDepth to be greater than cRefine
} // end for loop: either move to the next triangel in the original list, or move on to the next level of refinement
rVertices = tVertices; // Replace the old list with the new one, so that the next time it
// runs through the refinement process, it will refine the new
// traingles
cRefine++; // increase refinement interation variable so that it will either refine the next set of triangles, or exit the refinement loop.
nListPos = 0; // reset the new list position integer so it overwrites the exiting data
} // end while loop: either move on to the next refinement set, or exit the loop
vertices = rVertices; // make sure the class=level vertices
return rVertices;
} // End Refinement Class
public int Length
{
get { return vertices.Length; }
private set { }
}
public VertexPositionColor[] BuildList(TriXYZ[] tList, int tDepth)
{
VertexPositionColor[] finalList = new VertexPositionColor[tList.Length*3]; // final list to be returned for drawing
int listPos = 0; // current position in the final list (where the vector 3 is being applied)
Vector3 pos1; // Vertex 1 position of TriXYZ triangle
Vector3 pos2; // Vertex 2 position of TriXYZ triangle
Vector3 pos3; // Vertex 3 position of TriXYZ triangle
int depth;
for(int cTri = 0; cTri<tList.Length; cTri+=1) // Loop through the TriXYZ list and get all the vertexes from it, then apply them to the final draw list
{
pos1 = tList[cTri].GetVertex1;
pos2 = tList[cTri].GetVertex2;
pos3 = tList[cTri].GetVertex3;
depth = tList[cTri].GetDepth;
if (depth == tDepth)
{
finalList[listPos] = new VertexPositionColor(pos1, Color.Blue);
finalList[listPos + 1] = new VertexPositionColor(pos2, Color.Red);
finalList[listPos + 2] = new VertexPositionColor(pos3, Color.Green);
listPos = listPos + 3;
}
}
return finalList;
}
}
}
and here is the TriXYZ class, that holds the triangle data:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Icosahedron_Test
{
class TriXYZ
{
Vector3 vertex1;
Vector3 vertex2;
Vector3 vertex3;
int depth;
float material1; // float for first material value amount (in %) deals with blending
float material2; // float for second material value amount (in %) deals with blending
public TriXYZ(Vector3 pos1, Vector3 pos2, Vector3 pos3, int tDepth)
{
vertex1 = pos1;
vertex2 = pos2;
vertex3 = pos3;
depth = tDepth;
}
public TriXYZ(Vector3 pos1, Vector3 pos2, Vector3 pos3, int tDepth, float tMaterial1, float tMaterial2)
{
vertex1 = pos1;
vertex2 = pos2;
vertex3 = pos3;
depth = tDepth;
material1 = tMaterial1;
material2 = tMaterial2;
}
// public access to triangle data, read-write
public Vector3 GetVertex1
{
get { return vertex1; }
set { vertex1 = value; }
}
public Vector3 GetVertex2
{
get { return vertex2; }
set { vertex2 = value; }
}
public Vector3 GetVertex3
{
get { return vertex3; }
set { vertex3 = value; }
}
public int GetDepth
{
get { return depth; }
set { depth = value; }
}
public static Vector3 Midpoint(Vector3 pos1, Vector3 pos2, float radius)
{
Vector3 midpoint; // returned midpoint between the two inputted vectors
float x;
float y;
float z;
x = (pos1.X + pos2.X)/2;
y = (pos1.Y + pos2.Y)/2;
z = (pos1.Z + pos2.Z)/2;
midpoint = new Vector3(x, y, z);
midpoint.Normalize();
midpoint = midpoint * radius;
return midpoint;
}
public TriXYZ[] subDivide(float radius)
{
Vector3 r; // placeholder for new vertex position, aligned to planet sphere radius
Vector3 UV; // new vector position
TriXYZ[] nTriangle = new TriXYZ[5]; // array of triangle values to return
Vector3 mid1 = Midpoint(vertex1, vertex2, radius);
Vector3 mid2 = Midpoint(vertex2, vertex3, radius);
Vector3 mid3 = Midpoint(vertex3, vertex1, radius);
nTriangle[0] = new TriXYZ(vertex1, vertex2, vertex3, depth); // Put the original larger triangle at the front if the list
nTriangle[1] = new TriXYZ(vertex1, mid1, mid3, depth + 1); // First subdivided triangle
nTriangle[2] = new TriXYZ(mid1, vertex2, mid2, depth + 1); // Second subdivided triangle
nTriangle[3] = new TriXYZ(mid3, mid2, vertex3, depth + 1); // Third subdivided triangle
nTriangle[4] = new TriXYZ(mid3, mid1, mid2, depth + 1); // Fourth subdivided triangle
return nTriangle;
}
}
}
Any help will be greatly appreciate. I imagine it's something simple, I just cannot seem to find the problem.
It's definitely the initial vectors. If memory serves (it's been a while since I've dealt with icosahedrons, etc.), you just use the golden ratio to create one with and edge length of 2 (which you're doing). Perhaps normalize the vectors before multiplying by the radius? The reason I say that is because those vertices never get updated in your code, so it has to be the initial values (unless of course I missed something, which is possible).