[Maya API] Vertex and Face matrix/tangent space

Hey guys,

I remember posting something like this a long time ago yet (here) I never figured it out.
Basically what I want to do is retrieve an orthonormal basis (or tangent space or matrix space; whatever it’s called) for a vertex and for a face through the Maya API.

Whilst reading about the topic of ‘tangent space’ I found a variety of stuff about ‘angle weighted’ and ‘area size weighted’ to fix specific situations like the 90 degree angle problems where the space would easily flip around. I found that in Crytek engine related stuff, it’s over here. I wonder how this translates over to the Maya API since the ‘vertex normals’ also have a parameter for ‘angleWeighted’

There’s likely a bunch of you out there who’ve written vector displacement deformers in the past. Maybe someone’s willing to pitch in and show some example code to get a stable orthonormal basis for each vertex.
I actually think this should be rather easy, yet Maya’s always been giving back some weird Matrices with for example an axis being of zero length (like on a perfect cube and the pinching on a sphere).

Thanks in advance.

Cheers,
Roy

I’ve been able to use the tangent space in Fabric Engine, as can be seen here: https://vimeo.com/103717638
Is there really no one who knows why Maya seems to be acting up when trying similar techniques through the Maya API? :slight_smile:

Hi Roy,

I was at the talk at Siggraph from the dudes at R&H, and there was passing mention of recreating the deformer in Maya and having a nightmare with getting the data you’re after. They sort of just mentioned it as an aside, so I can’t offer any more information (not that I offered any anyway :slight_smile:

I’d love to see this deformer make its way to a native Maya plugin myself. Nice work on the Fabric version!

Cheers,

Geoff

You might find this a useful reference, although it has to be translated to Maya-speak

http://www.terathon.com/code/tangent.html

The link provided above does indeed tell you what you want to know. I had to do this quite recently so here’s some snippets and such for completeness sake.

NOTE: I am assuming you want per vertex tangent matrices ignoring UV seams.
This is about getting an arbitrary average normal for a vertex and a relevant tangent matrix based on that normal.
If uvs are important you want to use face-vertices and maya’s MFnMesh::getNormals / MFnMesh::getTangents functions.

This is not a very trivial thing. If you construct the tangents by yourself rather than having maya do it you’ll be getting the most stable results. A clean UV map is a requirement however…
Maya’s mesh lacks a large amount of self awareness, most data is per face-vertex but there is no proper transitioning between vertex and face-vertices and not good way to get averaged data per vertex.
Best is to use a MItMeshPolygon to iterate the polygons.

MObject sInMeshAttr is the typed kMesh attribute, MDataBlock data is the io data as passed to compute


MItMeshPolygon inMeshIter(data.inputValue(sInMeshAttr).asMesh());

Then we need to iterate the individual triangles to get accurate tangent data


MIntArray parsed;
while(!inMeshIter.isDone())
{
	MPointArray points;
	MIntArray vertices;
	inMeshIter.getTriangles(points, vertices);
	MFloatArray u;
	MFloatArray v;
	inMeshIter.getUVs(u, v);

Now that we know the points, uvs ad vertex indices per triangle vertex we can start getting the tangent per triangle
and use that tangent for all vertices in the triangle. If a vertex is split and has multiple we can only use one of the
uv-space-triangles to get a 3D tangent per vertex.

This bit iterates each triangle of the poylgon’s triangulation (3 points)
and extracts the barycentric coordinates for the tangent.


for(unsigned int i = 0 ; i < vertices.length() ; i += 3)
{
	// Taking UV coordinates [i-(i+2)] as our triangle and
	// the unitX (1,0) as point to get barycentric coordinates for
	// we can get the U direction in barycentric using the function
	// from this site:
	// http://www.blackpawn.com/texts/pointinpoly/
	double u02 = (u[i + 2] - u[i]);
	double v02 = (v[i + 2] - v[i]);
	double u01 = (u[i + 1] - u[i]);
	double v01 = (v[i + 1] - v[i]);
	double dot00 = u02 * u02 + v02 * v02;
	double dot01 = u02 * u01 + v02 * v01;
	double dot11 = u01 * u01 + v01 * v01;
	double d = dot00 * dot11 - dot01 * dot01;
	double u = 1.0;
	double v = 1.0;
	if(d != 0.0)
	{
		u = (dot11 * u02 - dot01 * u01) / d;
		v = (dot00 * u01 - dot01 * u02) / d;
	}

Now to get the 3D tangent all we need to do is apply the barycentric coordinates to the 3D points:


MVector tangent = points[i + 2] * u + points[i + 1] * v - points[i] * (u + v);

Next we iterate over the three vertices individually.


for(unsigned int j = i; j < i + 3 ; ++j)

Here we use MFnMesh::getVertexNormal for the average normal (whether you want angle-weighted depends on what you’re doing, I often don’t use them).
Having the average normal and triangle tangent we can use the cross product for the binormal, cross the normal & binormal again to get a proper
perpendicular tangent, because the normal is average and the tangent is not the initial tangent was wrong.


inMesh.getVertexNormal(vertices[j], false, normal);
binormal = tangent ^ normal;
binormal.normalize();
tangent = binormal ^ normal;
tangent.normalize();

Last we can store the vertex index in ‘parsed’ and skip future faces that use the same vertex

parsed.append(vertices[j]);

A check for that list then needs to be at the start of the per-vertex loop.
The matrix data is now simply:


{{tangent[0], tangent[1], tangent[2], 0},
{binormal[0], binormal[1], binormal[2], 0},
{normal[0], normal[1], normal[2], 0},
{point[0], point[1], point[2], 0}}

Hope that helps…

Hey all,

That’s some nice bits of knowledge you’re all dropping there.

NOTE: I am assuming you want per vertex tangent matrices ignoring UV seams.
This is about getting an arbitrary average normal for a vertex and a relevant tangent matrix based on that normal.
If uvs are important you want to use face-vertices and maya’s MFnMesh::getNormals / MFnMesh::getTangents functions.

What I’m after is the tangent space that one would also use for vector displacement mapping. As far as I know such a vertex tangent space should also move along (mostly) consistent with deforming geometry.

@Trevor: looking at your code it does seem to make sense that it would create a stable frame since it’ll be able to always pick the same barycentric coordinate as long as the UVs don’t change.
Though it will result (again, looking at the code at first glance) in a different tangent space than provided by: http://www.terathon.com/code/tangent.html
I’ve seen the terathon page linked on multiple websites when discussing tangent space and I assume that’s the industry method for vector displacement. It’s actually close to the code used in eg. Unity and Fabric Engine for calculating tangents! Note that the article explains that for vertices with discontinuous UVs averaging should not be used for the tangents:

To find the tangent vectors for a single vertex, we average the tangents for all triangles sharing that vertex in a manner similar to the way in which vertex normals are commonly calculated. In the case that neighboring triangles have discontinuous texture mapping, vertices along the border are generally already duplicated since they have different mapping coordinates anyway. We do not average tangents from such triangles because the result would not accurately represent the orientation of the bump map for either triangle.

Since the ‘source code’ example they share on that page always averages the tangents I believe it will have the issue that I quoted here.
Even though not industry standard I think your method will likely give a more stable result (up to the point where one does the Gram-Schmidt orthogonalization).

I’ll have a closer look at both cases and I’ll see if I can get some time (hopefully soon!) to do some implementing and experimenting.

I still think it’s odd there’s no built-in implementation that’s giving expected results. Or one might argue that it’s just my expectations of it that are skewed. :smiley:
Anyway, thanks!