transforming normals polydata

I’m trying to transform the points and normals of a polydata but part of the normals are negated after transformation. I transform the normals by multiplying them with the transposed and inverted transformation matrix

const transposedMatrix = mat4.create();
mat4.transpose(transposedMatrix, transformMatrix);
const normalsMatrix = mat4.create();
mat4.invert(normalsMatrix, transposedMatrix);
const pointData = polydata.getPoints().getData();
const normalsData = polydata.getCellData().getNormals().getData();
// modify point data
vtkMatrixBuilder
  .buildFromDegree()
  .setMatrix(transformMatrix)
  .apply(pointData);
// modify normals
vtkMatrixBuilder
  .buildFromDegree()
  .setMatrix(normalsMatrix)
  .apply(normalsData);
// explicitly set the point data again. This is needed because otherwise the bounds don't get updated
polydata.getPoints().setData(pointData);

This updates the points but about half of the normals are negated. I know this because when I manually negate all normals:

for (let offset = 0; offset < normalsData.length; offset += 3) {
      const tmp: vec3 = [
        normalsData[offset],
        normalsData[offset + 1],
        normalsData[offset + 2],
      ];
      const result = vec3.create();
      vec3.negate(result, tmp);
      normalsData[offset] = result[0];
      normalsData[offset + 1] = result[1];
      normalsData[offset + 2] = result[2];
    }

the spots that were black are now visible and vice versa. Here is a screenshot of the result:


Any idea why this would be the case? Thanks!

How did you generate the normals? Did you use vtkPolyDataNormals?

1 Like

Thanks for the quick reply. I read them from stl files and use the STLReader:

const reader = vtkSTLReader.newInstance();
reader.parseAsArrayBuffer(arrayBuffer);
const polydata = reader.getOutputData();

I don’t use vtkPolyDataNormals

So you have inconsistency on the normal orientations in your input data.

Could you try using vtkPolyDataNormals on the output of the reader? This would compute the normals from scratch, and it should give orientation consistency. You can play with AutoOrientNormals and / or FlipNormals to get the result you want.

1 Like

I’m using vtk-js and I believe that the vtkPolyDataNormals hasn’t been implemented yet (related issue). I maybe should have mentioned this already but without the transformation everything displays correctly. It’s only after the transformation that the normals are negated. Could it still be an issue with the input data?

If the normals are correct before you transform them, it doesn’t seem to be an issue with your input data… In what you are displaying, it almost looks like normals pointing in one direction (is it x, y or z?) get flipped. Can you share your transform matrix? (before transposition and inversion)

1 Like

I use vtkLandmarkTransform to calculate the transformation matrix. So I have a bunch of points on the ios (structure you see in the picture and that should be transformed) and a structure I want to align it with.

const transform = vtkLandmarkTransform.newInstance();
transform.setMode(vtkLandmarkTransform.Mode.RIGID_BODY);
const sourcePoints = vtkPoints.newInstance();
sourcePoints.setNumberOfPoints(this.iosPoints.length);
this.iosPoints.forEach(({ position }, index) =>
    sourcePoints.setPoint(index, position[0], position[1], position[2])
);
transform.setSourceLandmark(sourcePoints);
const targetPoints = vtkPoints.newInstance();
targetPoints.setNumberOfPoints(this.otherPoints.length);
this.otherPoints.forEach(({ position }, index) =>
    targetPoints.setPoint(index, position[0], position[1], position[2])
);
transform.setTargetLandmark(targetPoints);
transform.update();
const matrix = transform.getMatrix();

Here is an example of such a matrix:

[0.9932939893441913, 0.020253008632796998, -0.1138282318848029, 0, 0.11323989925952363, 0.028150729586301423, 0.993168798160439, 0, 0.02331900401804391, -0.9993984951284837, 0.025668501836446714, 0, -2.0631915446226796, -39.99665204527662, -47.887313583952945, 1]

Be careful, this rule is for normals that are expressed in homogeneous coordinates, but VTK stores its normals as (x,y,z). The math requires computing the w component prior to the matrix multiplication. For example, see VTK’s normal transformation code here.

In your case, it will work, but only because you are working with a rigid-body transformation (and note that for rigid-body transformations you can use the transformation matrix directly, you don’t have to take the transposed inverse).

My recommendation would be to use vtkTransformPolyDataFilter to transform the data.

1 Like

Correction to my post above: even with a rigid-body transformation, it’s incorrect to multiply the (x,y,z) normal by the transposed inverse. The reason is that the normals are only to be transformed by the rotation part of the transform, while the translation part of the transform must have no effect.

However, for rigid-body transforms it is okay to simply set the ‘w’ component of the normal to zero before the multiplication: (x,y,z,0) as opposed to the (x,y,z,1) that is used for homogeneous point coordinates.

1 Like

Thanks for the reply. Setting the ‘w’ component seems to fix the issue. I’ve implemented this by setting these values of the matrix to zero:

normalsMatrix[3] = 0;
normalsMatrix[7] = 0;
normalsMatrix[11] = 0;

So I’m guessing the translation part caused some directions to be negated. Thanks a lot for all the help!