MEL How-To #05

Back · Previous · Next Maya

How do I get the normal vector for a polygon face or vertex?

Querying Per-Vertex Per-Face Normals

There is a "polyNormalPerVertex" command which allows you to query the vertex normal for each associated face:

polyNormalPerVertex -q -xyz;
// Result: 0 1 0 0 0 1 -1 0 0 //

These results indicate that the vertex is associated with three faces. The normals are:

{ 0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { -1.0, 0.0, 0.0 }

You could average these three to obtain the shared vertex normal, if desired.

Unfortunately, this doesn't tell you which normal correlates to which associated face. To really know you must work with .vtxFace components.

// Convert a .vtx component to .vtxFace components.
string $plcc[] = `polyListComponentConversion -fv -tvf pCube1.vtx[2]`;

// Expand the list to avoid Maya's component compression
$plcc = `filterExpand -sm 70 -ex true $plcc`;

for ( $vtxFace in $plcc )
{
  // Get the per-vertex per-face normal for this component
  float $normal[3] = `polyNormalPerVertex -q -xyz $vtxFace`;
  print(  $vtxFace + ": { " + $normal[0] + ", " + $normal[1] + ", " + $normal[2] + " }\n" );
}

Example run:

pCube1.vtxFace[2][0]: { 0, 0, 1 }
pCube1.vtxFace[2][1]: { 0, 1, 0 }
pCube1.vtxFace[2][5]: { -1, 0, 0 }

Using 'polyInfo'

The polyInfo command has a '-faceNormal' flag which can be used to obtain the vector for a face. Two quirks with this method:

  1. The result is returned as an annotated string (actually a string array, just to be difficult); and,

  2. The normal isn't normalized.

We can work around both of these with a little helper procedure.

polyInfo -fn pSphere1.f[7];
// Result: FACE_NORMAL      7: -0.090404 -0.135299 0.017982 //

polyInfo -fn pCube1.f[0];
// Result: FACE_NORMAL      0: 0.000000 0.000000 2.000000 //

Here's our helper script:

proc vector translatePolyInfoNormal( string $pin )
{
  vector $normal;
  float $x;
  float $y;
  float $z;

  string $tokens[];
  int $numTokens = `tokenize $pin " " $tokens`;

  // Make sure we're looking at polyInfo data:
  if ( ( $numTokens > 3 ) && ( $tokens[0] == "FACE_NORMAL" ) )
  {
    // Maya performs data-type conversion here.
    $x = ($tokens[$numTokens-3]);
    $y = ($tokens[$numTokens-2]);
    $z = ($tokens[$numTokens-1]);

    $normal = << $x, $y, $z >>;

    // Normalize it.
    $normal = `unit $normal`;
  }

  // Return it.
  return $normal;
}

Now let's put it to use:

string $sphereInfo[] = `polyInfo -fn pSphere1.f[7]`;
// Result: FACE_NORMAL      7: -0.090404 -0.135299 0.017982

vector $sphereNormal = translatePolyInfoNormal( $sphereInfo[0] );
// Result: <<-0.552209, -0.826438, 0.109838>>  //

string $cubeInfo[] = `polyInfo -fn pCube1.f[7]`;
// Result: FACE_NORMAL      5: -2.000000 0.000000 0.000000

vector $cubeNormal = translatePolyInfoNormal( $cubeInfo[0] );
// Result: <<-1, 0, 0>>  //

Using The Cross Product

// Pick a face, any face
string $node = "polySurface1";
int $face = 0;

// Get vertex list for face
int $vertices[] = facetVertices( $node + ".f[" + $face + "]" );

// Get coordinates for each of the three vertices
vector $vector[3];
for ( $p = 0; $p < 3; $p++ )
{
  float $coord[3];

  $command = "xform -q -ws -t " + $node + ".vtx[" + $vertices[$p] + "]";
  $coord = `eval $command`;
  $vector[$p] = << $coord[0], $coord[1], $coord[2] >>;
}

vector $v0, $v1, $v2;
// Reassign to non-array variables to allow access
// to vector components (e.g. vector.x)
$v0 = $vector[0];
$v1 = $vector[1];
$v2 = $vector[2];

// Get two vectors for surface definition, from the 3 verts.
vector $def[2];
$def[0] =
  <<
    $v0.x - $v2.x,
    $v0.y - $v2.y,
    $v0.z - $v2.z
  >>;

$def[1] =
  <<
    $v1.x - $v2.x,
    $v1.y - $v2.y,
    $v1.z - $v2.z
  >>;

// Get normal
vector $normal;

// The cross product provides the normal for the surface defined from these two vectors
$normal = `cross $def[0] $def[1]`;

This script only considers the first three vertices for the given face. If your face is planar, this is fine. If your face is comprised of more than three vertices and it is not planar then the results will be inaccurate.

Be aware that the direction of the surface normal is dependent on the direction of your edge vectors. To ensure accurate results you'll want to make sure that the vertices are queried in render order.

Querying The 'mesh' Node

On a whim, I tried to see if it was possible to query one of the 'vertexNormal' attributes of the 'mesh' DG node to obtain this information directly:

  getAttr pSphere1.vn[0].vfnl[0].fnxy;
  // Result: 100000002004087730000 100000002004087730000 100000002004087730000 //

Given the results, I think not.


References for Cross Product


Acknowledgement

Joseph A. Hansen, Tools Manager, Beyond Games


Related How-To's

22 February 2003