[SOLVED] Smooth a mesh by JME

And yeah … the HashMap is a good idea. Just make sure it’s big enough when you create it (let it have sufficient size to fit in all triangles, use an according loadFactor and guaranteed size. Otherwise the HashMap might have to be resized several times, if you put in a lot of triangles…

Can’t see normals in that image. They might be pointing to the opposite direction and double sided rendering might be used. But it makes sense that a 3D scanner gives normals for each triangle. The direction matters though, because adding them up to a sum might make the sum smaller if the normal is inverted.

Sure you can… it’s lit. It has normals. It’s flat shaded… so they are per triangle and each triangle has its own three vertexes.

Yes, you are right that they could have been double-sided, etc… but that seemed unlikely. I also feel like (hope?) OP would have mentioned that. “Even to get that to work I had to set the rendering state blah blah…” Since OP didn’t, I did assume that no special material setup was required.

Edit: note that double-sided also would have taken a custom shader… which also seemed unlikely.

Okay. Seems legit.

@pspeed and @Ogli I have implemented your improvment suggestions.

	public void smoothMesh(Mesh mesh) {
	Map<Vector3f, Vector3f> triangleMap = new HashMap<>();
	for (int i = 0; i < mesh.getTriangleCount(); i++) {
		Triangle tri = new Triangle();
		mesh.getTriangle(i, tri);
		tri.calculateNormal();
		for (int y = 0; y < 3; y++) {
			if (!triangleMap.containsKey(tri.get(y)))
				triangleMap.put(tri.get(y), tri.getNormal().clone());
			else
				triangleMap.get(tri.get(y)).addLocal(tri.getNormal());
		}
	}

	float[] positions = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position));
	float[] normals = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal));
	IndexBuffer indexBuffer = mesh.getIndexBuffer();
	for (int i = 0; i < indexBuffer.size(); i++) {
		int index = indexBuffer.get(i) * 3;
		Vector3f p = new Vector3f(positions[index], positions[index + 1], positions[index + 2]);
		Vector3f n = triangleMap.get(p).normalize();
		normals[index] = n.x;
		normals[index + 1] = n.y;
		normals[index + 2] = n.z;
	}
	mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
}

Sort of. You made them less optimal at every turn though. Let me explain.

In that block of code, you effectively do the work of two hashmap lookups (containsKey() and get()) when one would work. Do the lookup once and check for null.

Here you are unnecessarily creating a new Vector3f… which seems like a small thing but you are also preventing the next pass for that same position from being able to take advantage of the fact that you already normalized.

Use normalizeLocal() instead… then you avoid creating a new Vector3f unnecessarily and the next pass will see that the length is already one and so won’t have to normalize it.

	public void smoothMesh(Mesh mesh) {
	Map<Vector3f, Vector3f> triangleMap = new HashMap<>();
	for (int i = 0; i < mesh.getTriangleCount(); i++) {
		Triangle tri = new Triangle();
		mesh.getTriangle(i, tri);
		tri.calculateNormal();
		for (int y = 0; y < 3; y++) {
			Vector3f accumulator = triangleMap.get(tri.get(y));
			if( accumulator == null ) {
				accumulator = tri.getNormal().clone();
			    triangleMap.put(tri.get(y), accumulator);
			} else {
			    accumulator.addLocal(tri.getNormal());
			}
		}
	}

	float[] positions = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position));
	float[] normals = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal));
	IndexBuffer indexBuffer = mesh.getIndexBuffer();
	for (int i = 0; i < indexBuffer.size(); i++) {
		int index = indexBuffer.get(i) * 3;
		Vector3f p = new Vector3f(positions[index], positions[index + 1], positions[index + 2]);
		Vector3f n = triangleMap.get(p);
		n.normalizeLocal();
		normals[index] = n.x;
		normals[index + 1] = n.y;
		normals[index + 2] = n.z;
	}
	mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
}

Why not make the HashMap big enough at start? I suggested that.

HashMap(int initialCapacity, float loadFactor)

with

initialCapacity = mesh.getTriangleCount();
loadFactor = 1.0f;

or (better)

initialCapacity = mesh.getTriangleCount() * 1.334; //put some brackets, convert to float/int
loadFactor = 0.75f; //or simply use the other c'tor and omit this value, it's 0.75 by default

Explanation here:
[HashMap (Java Platform SE 8 )]

@Ogli You are right. I added that to my code.
Thanks for your help.