Marching Cubes "Fun"

Started by
12 comments, last by thelordposeidon 6 years ago

Hey guys, I've been working on some marching cubes, and I've been having some weird issues that I really can't seem to understand. Basically the mesh isn't forming properly. Here are some screenshots and the code as it will show you the issue quicker than me describing it.

3yDmnZ9.pngtY5ZubD.pngwAQe0fH.png

 

As you can see the cube kinda forms but some areas are messed up, Here is the relevant code.

 


package voxels;

import java.util.ArrayList;
import java.util.List;

import javax.swing.plaf.basic.BasicTreeUI.TreeCancelEditingAction;

import org.lwjgl.util.vector.Vector3f;
import models.MarchingCubesCases;
import toolbox.UniversalFunctions;

public class MeshGenerator {

	private static int[][] vertexOffset = new int[][] { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 }, { 0, 0, 1 },
			{ 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 } };
	private static float target = 0.5f;

	public static Voxel[][][] generateVoxels(int size) {
		Voxel[][][] voxels = new Voxel[size][size][size];

		for (int i = 0; i < size; i++) {
			for (int j = 0; j < size; j++) {
				for (int k = 0; k < size; k++) {
					voxels[i][j][k] = new Voxel(VoxelMaterial.Grass);
				}
			}
		}

		return voxels;
	}

	public static MeshData generateMeshMarchingCubes(Voxel[][][] voxels) {

		List<Vector3f> verts = new ArrayList<Vector3f>();
		List<Float> normals = new ArrayList<Float>();
		List<Integer> indices = new ArrayList<Integer>();
		List<Float> textureCoords = new ArrayList<Float>();

		for (int x = 0; x < voxels.length; x++) {
			for (int y = 0; y < voxels.length; y++) {
				for (int z = 0; z < voxels.length; z++) {

					if (voxels[x][y][z].getMaterial() == VoxelMaterial.Air) {
						continue;
					}

					float[] cube = new float[8];

					if (checkIfExpose(voxels, x, y, z, cube) == false) {
						continue;
					}

					MarchCube(new Vector3f(x, y, z), cube, verts, indices);

					normals.add(0f);
					normals.add(1f);
					normals.add(0f);

					textureCoords.add(0f);
					textureCoords.add(1f);
				}
			}
		}

		MeshData data = new MeshData();
		data.setIndices(UniversalFunctions.intListToArray(indices));
		data.setVertsFromVector3f(verts);
		data.setNormals(UniversalFunctions.floatListToArray(normals));
		data.setTextureCoords(UniversalFunctions.floatListToArray(textureCoords));

		return data;
	}
	
	private static boolean checkIfExpose(Voxel[][][] voxels, int x, int y, int z, float[] cube) {

		Voxel[] surroundingVoxels = getSurroundingVoxels(voxels, x, y, z);
		boolean bool = false;

		for (int i = 0; i < cube.length; i++) {
			cube[i] = 1;
		}

		if (surroundingVoxels[0].getMaterial().equals(VoxelMaterial.Air)) {

			cube[0] = 0;
			cube[1] = 0;
			cube[4] = 0;
			cube[5] = 0;
			bool = true;

		}

		if (surroundingVoxels[1].getMaterial().equals(VoxelMaterial.Air)) {

			cube[0] = 0;
			cube[3] = 0;
			cube[4] = 0;
			cube[7] = 0;
			bool = true;

		}

		if (surroundingVoxels[2].getMaterial().equals(VoxelMaterial.Air)) {

			cube[4] = 0;
			cube[5] = 0;
			cube[6] = 0;
			cube[7] = 0;
			bool = true;

		}

		if (surroundingVoxels[3].getMaterial().equals(VoxelMaterial.Air)) {

			cube[0] = 0;
			cube[1] = 0;
			cube[2] = 0;
			cube[3] = 0;
			bool = true;

		}

		if (surroundingVoxels[4].getMaterial().equals(VoxelMaterial.Air)) {

			cube[1] = 0;
			cube[2] = 0;
			cube[5] = 0;
			cube[6] = 0;
			bool = true;

		}

		if (surroundingVoxels[5].getMaterial().equals(VoxelMaterial.Air)) {

			cube[2] = 0;
			cube[3] = 0;
			cube[6] = 0;
			cube[7] = 0;
			bool = true;

		}

		return bool;
	}

	private static Voxel[] getSurroundingVoxels(Voxel[][][] voxels, int x, int y, int z) {

		int maxLength = voxels.length;
		List<Voxel> voxelList = new ArrayList<Voxel>();

		if (y - 1 > 0 && y - 1 < maxLength) {
			voxelList.add(voxels[x][y - 1][z]);
		} else {
			voxelList.add(new Voxel(VoxelMaterial.Air));
		}

		if (x - 1 > 0 && x - 1 < maxLength) {
			voxelList.add(voxels[x - 1][y][z]);
		} else {
			voxelList.add(new Voxel(VoxelMaterial.Air));
		}

		if (z - 1 > 0 && z - 1 < maxLength) {
			voxelList.add(voxels[x][y][z - 1]);
		} else {
			voxelList.add(new Voxel(VoxelMaterial.Air));
		}

		if (z + 1 > 0 && z + 1 < maxLength) {
			voxelList.add(voxels[x][y][z + 1]);
		} else {
			voxelList.add(new Voxel(VoxelMaterial.Air));
		}

		if (x + 1 > 0 && x + 1 < maxLength) {
			voxelList.add(voxels[x + 1][y][z]);
		} else {
			voxelList.add(new Voxel(VoxelMaterial.Air));
		}

		if (y + 1 > 0 && y + 1 < maxLength) {
			voxelList.add(voxels[x][y + 1][z]);
		} else {
			voxelList.add(new Voxel(VoxelMaterial.Air));
		}

		return voxelList.toArray(new Voxel[voxelList.size()]);
	}

	// MarchCube performs the Marching Cubes algorithm on a single cube
	private static void MarchCube(Vector3f pos, float[] cube, List<Vector3f> vertList, List<Integer> indexList) {
		int i, j, vert, idx;
		int flagIndex = 0;
		float offset = 0.0f;

		Vector3f[] edgeVertex = new Vector3f[12];

		for (int inter = 0; inter < 12; inter++) {
			edgeVertex[inter] = new Vector3f();
		}

		// Find which vertices are inside of the surface and which are outside
		for (i = 0; i < 8; i++) {
			if (cube[i] <= target) {
				flagIndex |= 1 << i;
			}
		}

		// Find which edges are intersected by the surface
		int edgeFlags = MarchingCubesCases.cubeEdgeFlags[flagIndex];

		// If the cube is entirely inside or outside of the surface, then there will be
		// no intersections
		if (edgeFlags == 0)
			return;

		// Find the point of intersection of the surface with each edge
		for (i = 0; i < 12; i++) {

			// if there is an intersection on this edge
			if ((edgeFlags & (1 << i)) != 0) {
				offset = GetOffset(cube[MarchingCubesCases.edgeConnection[i][0]],
						cube[MarchingCubesCases.edgeConnection[i][1]]);

				edgeVertex[i].x = pos.x + (vertexOffset[MarchingCubesCases.edgeConnection[i][0]][0]
						+ offset * MarchingCubesCases.edgeDirection[i][0]);
				edgeVertex[i].y = pos.y + (vertexOffset[MarchingCubesCases.edgeConnection[i][0]][1]
						+ offset * MarchingCubesCases.edgeDirection[i][1]);
				edgeVertex[i].z = pos.z + (vertexOffset[MarchingCubesCases.edgeConnection[i][0]][2]
						+ offset * MarchingCubesCases.edgeDirection[i][2]);

			}
		}

		// Save the triangles that were found. There can be up to five per cube
		for (i = 0; i < 5; i++) {

			if (MarchingCubesCases.triangleConnectionTable[flagIndex][3 * i] < 0) {
				break;
			}

			idx = vertList.size();

			for (j = 0; j < 3; j++) {
				vert = MarchingCubesCases.triangleConnectionTable[flagIndex][3 * i + j];
				indexList.add(idx + MarchingCubesCases.windingOrder[j]);
				vertList.add(edgeVertex[vert]);
			}

		}
	}

	
	// GetOffset finds the approximate point of intersection of the surface
	// between two points with the values v1 and v2
	private static float GetOffset(float v1, float v2) {
		float delta = v2 - v1;
		return (delta == 0.0f) ? 0.5f : (target - v1) / delta;
	}
}

I can attach the look up tables if needed. The MeshData class is just a class to hold verts, indices, etc. The voxel data is generated using the generateVoxels method and it is for a size of 16. Finally the voxel material only maters if the material is air as then the voxel will need to be rendered. Any help would be amazing.

Thanks

Euan

Advertisement

Your images are terrible quality, but it looks like you are not getting a manifold mesh out of your algorithm, so I expect you've got the cases wrong. Perhaps with larger, better lit images it would be clearer? While I'm not going to review your cases, I've recently posted a marching cubes tutorial, so you can look over my cases instead? I have unit tests for the case table, you could port that to see if your own table passes.

Otherwise, I'd start debugging your code. Marching Cubes is very easy to test a single cube in isolation, so I'd start with one you know to be wrong.

Thanks for the advice, I'll get some better quality images tomorrow for you, unfortunately I had to take them under some time pressure but tomorrow I shouldn't have that issue. I'll take a look at the tutorial and thanks for the unit tests, again I'll look at them tomorrow.

Can you link to a pasteboard of the tables. I'm not sure why your marching cubes uses a windingOrder table (unless your culling direction is the wrong way)?

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Here's the pastebin : here, and the pictures will go up later. I added it because I was having some issues I think with direction culling and it seemed to help. This was awhile ago so I can't quite remember what I was doing.

Well, your tables look like standard marching cubes tables, so you can probably rule out issues there.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

28 minutes ago, swiftcoder said:

Well, your tables look like standard marching cubes tables, so you can probably rule out issues there.

Yeah, I'm pretty sure it's me making a silly mistake or a mistake in logic

So i took some more pictures and did some debugging. First and foremost here are some better pictures of the cube at the original size (16 x 16 x 16 in opengl world coords). I uploaded it to imgur here.

Next I tried some debuging. No cube shows when I have the grid size as 1 x 1 x 1. When it is 2 x 2 x 2 I get a small triangle (pic here) and finally when I do 4 x 4 x 4 I get this load of weirdness. Obviously my logic has gone wrong but honestly I have no idea where.

Any ideas would be amazing.

It looks like your corner ordering is just incorrect. I'd try and validate the direction of each corner against a known-working marching cubes algorithm.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Do you know of any off the top of your head that is similar enough that I can compare it easily?

Edit : So I found that good old scrawk's code is similar to mine (though in c# and using unity). So the plan is to try and use that as a debug tool as that is definitely confirmed working. See where the difference in logic and code is. If I find anything I'll post it here, if there's nothing feel free to offer suggestions on what's wrong in the code as I've probably not solved it.

This topic is closed to new replies.

Advertisement