(C++) Better createMeshWithTangents()

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
WhiteNoise
Posts: 27
Joined: Wed Apr 19, 2006 5:10 pm
Contact:

(C++) Better createMeshWithTangents()

Post by WhiteNoise »

I re-wrote the createMeshWithTangents function to be more efficient and produce better looking results. Before the function would duplicate the vertices of every triangle (resulting in extra vertices) and calculate the tangent/binormal/normal for each one. This is redundant since the three vectors are the same for each face. The new function does not duplicate any vertices (so models should be more efficient) and it also smooths the vectors to get a more pleasing result. I did a quick test and it seems to work fine - let me know if I'm missing something.

Code: Select all

//! Creates a copy of the mesh, which will only consist of S3DVertexTangents vertices.
IMesh* CMeshManipulator::createMeshWithTangents(IMesh* mesh) const
{
	if (!mesh)
		return 0;

	// copy mesh and fill data into SMeshBufferTangents

	SMesh* clone = new SMesh();
	s32 meshBufferCount = mesh->getMeshBufferCount();
		
	s32 b=0;
    for (; b<meshBufferCount; ++b)
	{
		s32 vtxCnt = mesh->getMeshBuffer(b)->getVertexCount();
		s32 idxCnt = mesh->getMeshBuffer(b)->getIndexCount();
		const u16* idx = mesh->getMeshBuffer(b)->getIndices();

		SMeshBufferTangents* buffer = new SMeshBufferTangents();
		buffer->Material = mesh->getMeshBuffer(b)->getMaterial();

		// copy vertices
		s32 i;

		switch(mesh->getMeshBuffer(b)->getVertexType())
		{
		case video::EVT_STANDARD:
			{
				video::S3DVertex* v = 
					(video::S3DVertex*)mesh->getMeshBuffer(b)->getVertices();

				for (i=0; i<vtxCnt; ++i) {					
					buffer->Vertices.push_back(video::S3DVertexTangents (v[i].Pos, v[i].TCoords, v[i].Color));
				}
			}
			break;
		case video::EVT_2TCOORDS:
			{
				video::S3DVertex2TCoords* v = 
					(video::S3DVertex2TCoords*)mesh->getMeshBuffer(b)->getVertices();

				for (i=0; i<vtxCnt; ++i) {					
					buffer->Vertices.push_back(video::S3DVertexTangents (v[i].Pos, v[i].TCoords, v[i].Color));
				}
			}
			break;
		case video::EVT_TANGENTS:
			{
				video::S3DVertexTangents* v = 
					(video::S3DVertexTangents*)mesh->getMeshBuffer(b)->getVertices();

				for (i=0; i<vtxCnt; ++i) {					
					buffer->Vertices.push_back(video::S3DVertexTangents (v[i].Pos, v[i].TCoords, v[i].Color));
				}
			}
			break;
		}


		for(i=0; i<idxCnt; i++)
			buffer->Indices.push_back(idx[i]);

		// add new buffer 
		clone->addMeshBuffer(buffer);
		buffer->drop();
	}

	clone->BoundingBox = mesh->getBoundingBox();

	// now calculate tangents
	b=0;
    for (; b<meshBufferCount; ++b)
	{
		u32 vtxCnt = clone->getMeshBuffer(b)->getVertexCount();
		u32 idxCnt = clone->getMeshBuffer(b)->getIndexCount();


		core::array<s32> counts;

		u16* idx = clone->getMeshBuffer(b)->getIndices();

		video::S3DVertexTangents* v = 
			(video::S3DVertexTangents*)clone->getMeshBuffer(b)->getVertices();

		u32 i, j;
		for(j=0; j<vtxCnt; j++)
			counts.push_back(0);


        for (i=0; i<idxCnt; i+=3)
		{
			video::S3DVertexTangents polyTangentSpace;

			calculateTangents(
				polyTangentSpace.Normal,
				polyTangentSpace.Tangent,
				polyTangentSpace.Binormal,
				v[idx[i+0]].Pos, 
				v[idx[i+1]].Pos,
				v[idx[i+2]].Pos,
				v[idx[i+0]].TCoords,
				v[idx[i+1]].TCoords, 
				v[idx[i+2]].TCoords);

			v[idx[i+0]].Normal += polyTangentSpace.Normal;
			v[idx[i+0]].Tangent += polyTangentSpace.Tangent;
			v[idx[i+0]].Binormal += polyTangentSpace.Binormal;

			v[idx[i+1]].Normal += polyTangentSpace.Normal;
			v[idx[i+1]].Tangent += polyTangentSpace.Tangent;
			v[idx[i+1]].Binormal += polyTangentSpace.Binormal;

			v[idx[i+2]].Normal += polyTangentSpace.Normal;
			v[idx[i+2]].Tangent += polyTangentSpace.Tangent;
			v[idx[i+2]].Binormal += polyTangentSpace.Binormal;

			counts[idx[i+0]] += 1;
			counts[idx[i+1]] += 1;
			counts[idx[i+2]] += 1;

		}

		for(j=0; j<vtxCnt; j++) {
			v[j].Normal /= (f32)counts[j];
			v[j].Tangent /= (f32)counts[j];
			v[j].Binormal /= (f32)counts[j];
		}
			
	}

	//recalculateNormals(clone);

	return clone;
}
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Sound very good! This Tangents problem is indeed rather disturbing. Since all the calculations are hard wired it would be perfectly possible to make the conversion automatically when required. But the vertex object structure doesn't make it that easy.
I think to keep smoothed and edgy normals as wanted it would be best to reuse the original normals of the vertices. As soon as the mesh loaders will support smoothing normals these should not be recalculated.
WhiteNoise
Posts: 27
Joined: Wed Apr 19, 2006 5:10 pm
Contact:

Post by WhiteNoise »

Keeping the original normals might be a problem since it would break the orthogonality of the tagent/binormal/normal vectors.. You would have to calculate the tangent and binormal according to the normal which is a bit above my math level. Ideally the mesh format should store the tangent / binormal / normal info since it is time consuming to calculate. Another solution (and I think other games do this) is to add a few extra faces to preserve the sharpness of edges [ie, give it a very slight bevel].. For future versions of irrlicht we might want to just have one vertex format that has all the info or somehow make it more flexible through abstraction (though maybe this won't be efficient.. ).
xterminhate
Posts: 206
Joined: Thu Sep 01, 2005 9:26 pm
Location: France

Post by xterminhate »

It seems that irrlicht::createMeshW.. and your createMeshW.. alter original normals (those that come from mesh file). Let me know if I am wrong. I'm just getting familiar with Irrlicht internals.

So, do you plan to improve your code in order to reuse original normals ? I'm getting interested with a createMeshWith... that doesn't change those normals. Perhaps, I will try and write one.
Return to Irrlicht after years... I'm lovin it.
It's hard to be a Man !
Si vis pacem para belum
WhiteNoise
Posts: 27
Joined: Wed Apr 19, 2006 5:10 pm
Contact:

Post by WhiteNoise »

That's correct, it takes the average normal, the same as if you called 'recalculateNormals'. I don't have any plans to modify it - in most cases I think you'd want to use the average normal anyway.
TheGameMaker
Posts: 275
Joined: Fri May 12, 2006 6:37 pm
Location: Germany

Post by TheGameMaker »

why, for gods sake, is this patch not in Irrlicht for long time??
A big thanks to the author, this code helps a lot!!
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

Because the code has to be fixed such that it takes the original normals, maybe with smoothed normals via another parameter.
TheGameMaker
Posts: 275
Joined: Fri May 12, 2006 6:37 pm
Location: Germany

Post by TheGameMaker »

hmm.. but its times better than the totaly useless orginal code... :?
needforhint
Posts: 322
Joined: Tue Aug 30, 2005 10:34 am
Location: slovakia

Post by needforhint »

Because the code has to be fixed such that it takes the original normals
And now the CreateMeahWithTangents finds tangent and binormal and creates new normal as a cross product of those two.
Much proper aproach is to find tangent, cross it with smooth normal and get the binormal, hereby everything is orthogonal and smooth normal is untouched
what is this thing...
Post Reply