- Home /
Marching cubes problem, some tris not drawing properly
I'm trying to get a marching squares algorithm from a tutorial working in 3D as a bit of an exercise, but I'm having a problem with the code. I'm not exactly sure what it is, as I've checked it numerous times.
The problem should be obivous.
using UnityEngine;
using System.Collections.Generic;
public class Chunk : MonoBehaviour {
public Voxel[] Voxels;
public GameObject voxelFab;
private int chunkResolution;
private float voxelSize;
public Mesh mesh;
private List<Vector3> vertices;
private List<Vector2> uv;
private List<int> triangles;
public void Awake (/*int resolution, float size*/) {
chunkResolution = 8;
voxelSize = 1f / chunkResolution;
Voxels = new Voxel[chunkResolution * chunkResolution * chunkResolution];
vertices = new List<Vector3>();
uv = new List<Vector2>();
triangles = new List<int>();
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
GenerateChunkData();
RefreshChunk();
}
public void GenerateChunkData() {
for (int i = 0, y = 0; y < chunkResolution; y++)
{
for (int z = 0; z < chunkResolution; z++)
{
for (int x = 0; x < chunkResolution; x++, i++)
{
CreateVoxel (i, x, y, z);
Debug.Log ("Voxel at: " + Voxels[i].position + "has it's edge positions as follows: " + "x: " + Voxels[i].xEdge + " z: " + Voxels[i].zEdge + " y: " + Voxels[i].yEdge);
}
}
}
SetVoxels();
}
//Creates helper objects at the position of each voxel. Also create the voxel objects.
public void CreateVoxel (int i, int x, int y, int z) {
GameObject o = Instantiate (voxelFab) as GameObject;
o.transform.parent = transform;
o.transform.localPosition = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
o.transform.localScale = Vector3.one;
o.GetComponent<MeshRenderer>().material.color = new Color (0.3f, 0.3f, 0.3f, 0.15f);
Voxels[i] = new Voxel (x, y, z, 1f);
}
//Manually set the state of each voxel. The index is y * chunkResolution squared + z * chunkResolution + x;
void SetVoxels() {
Voxels[5 * chunkResolution * chunkResolution + 5 * chunkResolution + 8].State = true;
Voxels[2 * chunkResolution * chunkResolution + 1 * chunkResolution + 0].State = true;
}
//Function for refreshing the chunk. Currently only does the triangulation.
public void RefreshChunk() {
Triangulate();
}
//Clears the old lists of vertices, uvs and triangle indices and triangulates the mesh, then sets the new mesh variables.
public void Triangulate() {
mesh.Clear ();
vertices.Clear ();
uv.Clear ();
triangles.Clear ();
TriangulateCellRows();
mesh.vertices = vertices.ToArray ();
mesh.uv = uv.ToArray ();
mesh.triangles = triangles.ToArray ();
mesh.RecalculateNormals ();
}
//Loops through each cell and triangulates it.
public void TriangulateCellRows() {
int cells = chunkResolution - 1;
for (int i = 0, y = 0; y < cells; y++, i++)
{
for (int z = 0; z < cells; z++, i++)
{
for (int x = 0; x < cells; x++, i++)
{
TriangulateCell (
Voxels[i],
Voxels[i + 1],
Voxels[i + chunkResolution],
Voxels[i + chunkResolution + 1],
Voxels[i + chunkResolution * chunkResolution],
Voxels[i + chunkResolution * chunkResolution + 1],
Voxels[i + chunkResolution * chunkResolution + chunkResolution],
Voxels[i + chunkResolution * chunkResolution + chunkResolution + 1]
);
}
}
}
}
public void TriangulateCell (Voxel a, Voxel b, Voxel c, Voxel d, Voxel e, Voxel f, Voxel g, Voxel h) {
int cellType = 0;
//Add a number to cellType depending on which voxels of that cell are active.
if (a.State)
cellType |= 1;
if (b.State)
cellType |= 2;
if (c.State)
cellType |= 4;
if (d.State)
cellType |= 8;
if (e.State)
cellType |= 16;
if (f.State)
cellType |= 32;
if (g.State)
cellType |= 64;
if (h.State)
cellType |= 128;
#region Single Triangles and 0, 255 cases
//Adds a triangle when either one voxel of the cell is cell active, or only one is inactive.
switch (cellType)
{
//None of the voxels are active, don't draw the cell.
case 0:
return;
//The bottom left front (0,0,0) of the cell is the only active/inactive, draw a triangle there.
case 1:
AddTriangle (a.yEdge, a.zEdge, a.xEdge);
break;
case 254:
AddTriangle (a.xEdge, a.zEdge, a.yEdge);
break;
//The bottom right front (1,0,0) of the cell is the only active/inactive, draw a triangle there.
case 2:
AddTriangle (b.yEdge, a.xEdge, b.zEdge);
break;
case 253:
AddTriangle (b.zEdge, a.xEdge, b.yEdge);
break;
//The bottom left back (0,0,1) of the cell is the only active/inactive, draw a triangle there.
case 4:
AddTriangle (c.yEdge, c.xEdge, a.zEdge);
break;
case 251:
AddTriangle (a.zEdge, c.xEdge, c.yEdge);
break;
//The bottom right back (1,0,1) of the cell is the only active/inactive, draw a triangle there.
case 8:
AddTriangle (d.yEdge, b.zEdge, c.xEdge);
break;
case 247:
AddTriangle (c.xEdge, b.zEdge, d.yEdge);
break;
//The top left front (0,1,0) of the cell is the only active/inactive, draw a triangle there.
case 16:
AddTriangle (a.yEdge, e.xEdge, e.zEdge);
break;
case 239:
AddTriangle (e.zEdge, e.xEdge, a.yEdge);
break;
//The top right front (1,1,0) of the cell is the only active/inactive, draw a triangle there.
case 32:
AddTriangle (b.yEdge, f.zEdge, e.xEdge);
break;
case 223:
AddTriangle (e.xEdge, f.zEdge, b.yEdge);
break;
//The top left back (0,1,1) of the cell is the only active/inactive, draw a triangle there.
case 64:
AddTriangle (c.yEdge, e.zEdge, g.xEdge);
break;
case 191:
AddTriangle (c.yEdge, g.xEdge, e.zEdge);
break;
//The top right back (1,1,1) of the cell is the only active/inactive, draw a triangle there.
case 128:
AddTriangle (d.yEdge, g.xEdge, f.zEdge);
break;
case 127:
AddTriangle (f.zEdge, g.xEdge, d.yEdge);
break;
//All voxels are active, don't draw the cell.
case 255:
return;
}
#endregion
}
//Adds a triangle based on the given points. The UV values are arbitrary guesses for now.
void AddTriangle (Vector3 a, Vector3 b, Vector3 c) {
int triIndex = vertices.Count;
vertices.Add (a);
vertices.Add (b);
vertices.Add (c);
uv.Add (new Vector2 (0,0));
uv.Add (new Vector2 (0.5f,1));
uv.Add (new Vector2 (1,0));
triangles.Add (triIndex);
triangles.Add (triIndex + 1);
triangles.Add (triIndex + 2);
}
}
What are you expecting to see? It might be easier to debug if you generate consistent data in some pattern where you know what it should look like, rather than using random data.
I've done that now, the problem only appears along the edges of the chunk. The indices get set at the right point, but on the wrong voxel, although it should remain in the same voxel. It only happens on the XZ plane. Logging edge position seems to indicate the edges are set properly, so the only two problems are either the indices are getting set wrongly, or the method I'm using to index the voxels for triangulation is wrong.
I would be able to help better with some more comments in the code. I have no idea what is supposed to be happening in the triangulateCell function. for example, why does it not take every possible value of cellType into consideration, only the ones listed in the switch(cellType)?
The switch statement currently only checks for cases where a single triangle should be drawn. I'll edit with some commented code.
You could always debug by creating each of the cube states you're using, and then seeing what you're doing wrong. $$anonymous$$aybe set the 8 voxels in public variables in the script, and attach it to each test cube?
Answer by SoupCanist4r · Jan 18, 2015 at 10:21 PM
I've solved the problem. Turns out that when looping through Y on TriangulateCellRows(), I had to increment i by chunkResolution, and not by 1. The glitchy triangles weren't even supposed to draw.
Answer by carrollh · Jan 18, 2015 at 05:20 PM
I don't use voxel's, but I do dynamically generate a lot of meshes. For MeshFilter meshes you draw faces as a pair of triangles, in triangle list form. So for every face, you have 4 verts, 4 uvs, 4 normals, but then 6 triangle points.
The vertex indicies look like this, with these triangles
3 ------ 0
| /|
| / |
| / |
|/ |
2 ------ 1
If you do this your tris to be added will be 0, 1, 2, 0, 2, 3. If this is out of order, then the triangles will be flipped on the same face. If you only do 4 of the 6 required triangle points then you loose some triangles, and the points are all over the place. Maybe your triangle points aren't right? Looking at your code, the second triangle of every quad would have it's first vertex off by one. The second triangle would need something like triangles.Add(triIndex - 1);
as it's first point.
EDIT From here on out: I made a test case for the 4th example from the wikipedia page's picture. Here's the (heavily commented) script I attached to the cube:
using UnityEngine;
using System.Collections;
public class CreateTrianglesFromVoxels : MonoBehaviour {
public bool[] isVoxelSet;
private Vector3[] voxels;
private byte voxelSum;
void Awake()
{
byte voxelZero = 1;
for(int i = 0; i < 8; i++)
{
if (isVoxelSet[i]) voxelSum += (byte)(voxelZero << i);
}
voxels = new Vector3[8];
SetOriginalCubeVerts();
}
private Vector3[] vertices;
private int[] triangles;
void Start()
{
switch (voxelSum)
{
case 0:
break;
case 136:
// voxel 3 requires a triangle covering the corner of the voxel.
// Thus edges are to cube verts 0, 2, and 4.
// voxel 7 requires a similar thing, with edges going to 0, 4, and 6.
// So we need 2 separate tris, with 6 total verts.
vertices = new Vector3[6];
triangles = new int[6];
// voxel 3 tri, ensuring clockwise order
vertices[0] = Vector3.Lerp(voxels[3], voxels[2], 0.5f);
vertices[1] = Vector3.Lerp(voxels[3], voxels[4], 0.5f);
vertices[2] = Vector3.Lerp(voxels[3], voxels[0], 0.5f);
triangles[0] = 0;
triangles[1] = 1;
triangles[2] = 2;
// voxel 7 tri, again ordering matters
vertices[3] = Vector3.Lerp(voxels[7], voxels[0], 0.5f);
vertices[4] = Vector3.Lerp(voxels[7], voxels[4], 0.5f);
vertices[5] = Vector3.Lerp(voxels[7], voxels[6], 0.5f);
triangles[3] = 3;
triangles[4] = 4;
triangles[5] = 5;
break;
default:
break;
}
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
GetComponent<MeshFilter>().mesh = mesh;
}
/* There are 8 voxels total, one at each of the corners in the cube.
*
* The top four are indexed 0 through 3 starting at the top-front-left
* point, going clockwise around the top of the cube.
*
* The other 4 are indexed 4 through 7 and start at the bottom-front-right
* corner, again going clockwise around the bottom.
*
* Thus voxels = { false, true, false, false, true, true, true, false } would
* mean that the back-left corner was set on the top, and all the voxels except
* the one on the bottom-left-front.
*
* The example I am doing is number 136; voxels 3 and 7 are set (0-indexed).
*
* I doing the fourth one in the top row here:
* http://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/MarchingCubes.svg/501px-MarchingCubes.svg.png
*/
private void SetOriginalCubeVerts()
{
voxels[0] = new Vector3(-0.5f, 0.5f, -0.5f);
voxels[1] = new Vector3(-0.5f, 0.5f, 0.5f);
voxels[2] = new Vector3(0.5f, 0.5f, 0.5f);
voxels[3] = new Vector3(0.5f, 0.5f, -0.5f);
voxels[4] = new Vector3(0.5f, -0.5f, -0.5f);
voxels[5] = new Vector3(0.5f, -0.5f, 0.5f);
voxels[6] = new Vector3(-0.5f, -0.5f, 0.5f);
voxels[7] = new Vector3(-0.5f, -0.5f, -0.5f);
}
}
Take a look at the cube properties from this image:
I'm not actually drawing quads, yet. The reason I've grouped 2 cases is because they use the same indices, only the second one is flipped, because 7 of the 8 voxels of that cell are on.
I'm not sure it matters really. You're adding them all to a list, right? Then when you convert that triangle list to an array and stuff it into a mesh, it gets treated this way anyway...
Are you in college? This seems like a very academic project.
I've solved the problem, actually. I was incrementing the index wrong. The xEdge, yEdge and zEdge are calculated in the Voxel class, I'm not lerping.