Matrix Transforms and changing up

Hello all,

I asked yesterday about some transform issues and @deadpin showed me I was using the wrong order for my matrices.

Below is my transform converter that takes Rhino’s column-major to Pixar’s row-major


const pxr::GfMatrix4d ON_Helpers::Convert(const ON_Xform& xForm)
{
  pxr::GfMatrix4d matrix;
  for (int r = 0; r < 4; r++)
    for (int c = 0; c < 4; c++)
      matrix[r][c] = xForm.m_xform[c][r];
  

  return matrix;
}

The problem is the Z/Y of the transform are swapped. Rhino uses Z as up, Pixar uses Y (as far as I can tell, so I’m setting the .usd to Y-up)

testfile27.usda (18.6 KB)

Block-01.usda (65.8 KB)

If I change the code to look like below, the translations work correctly, but rotations are still a bit off.


const pxr::GfMatrix4d ON_Helpers::Convert(const ON_Xform& xForm)
{
  pxr::GfMatrix4d matrix;
  for (int r = 0; r < 4; r++)
    for (int c = 0; c < 4; c++)
      matrix[r][c] = xForm.m_xform[c][r];
  
  // Z/Y swap
  double zValue = matrix[3][2];
  double yValue = matrix[3][1];
  if (yValue > 0)
    yValue = -yValue;
  
  matrix[3][2] = yValue;
  matrix[3][1] = zValue;

  return matrix;
}

(Sorry for the pics in comments, I can’t post more than 1 picture per post as I’m new!)

Geometry in Rhino

Geometry as USD

Geometry with manual transform swap, looks great! But any rotations are wrong, see further below.

Non matching transforms underlined with matching colours.

I’m starting to feel a bit out of my depth and I’m sure there’s something obvious I’m missing about Y/Z up and these transforms :slight_smile:

My guess is that you forgot to also transform the mesh points from a Z up scene to a Y up stage.

What you are doing is a change of base, and you’ll need a “sandwich product” (formulas seem inverted in those two links, but that is because the first one is row-major, while the second is column-major) to achieve it.

Given C the change-of-base transform (in your case, “the transform that make a Z-Up scene becomes a Y-Up scene”) and Cinv the inverse of this transform, for a given transform M, the M’ in the new base is

M' = Cinv * M * C

So given the global position Pg of a vertex of your mesh, it’s position Pg’ in Y Up is

Pg' = Cinv * Pg * C

But that’s not the whole story: the vertex global position is made of two transforms:

  • its local position P defined in the pointf3f[] points attribute of your mesh
  • the local transform T of its prim
Pg = M * P

So, going back to the change of base:

Pg' = Cinv * Pg * C
Pg' = Cinv * M * P * C

We now need to extract from this equation two transforms:

  • one for the Prim transform in Y up
  • another one for point transform in Y up

Let’s insert an identity matrix I in the equation (a matrix that don’t have an effect, like multiplying a number by 1 or adding 0 to a sum).

Pg' = Cinv * M * I * P * C

A property of an indentity matrix is that if you multiply a matrix by its inverse, you get an identity:

I = M * Minv = Minv * M

With this property in mind, let’s decompose the I matrix:

Pg' = Cinv * M * (C * Cinv) * P * C

Or, if we drop the parenthesis:

Pg' = Cinv * M * C * Cinv * P * C
Pg' = M' * P'

M’ being the prim transform in Y-Up and P’ the point transform in Y up. In another term: "you need to change the base of all intermediate transforms, being prim transforms or point locations”.

Here a sample below that transform an Z Up USD layer into a Y Up USD layer:

Z-Up stage-z-up.usda:

#usda 1.0
(
    defaultPrim = "Tri"
    metersPerUnit = 1
    upAxis = "Z"
)

def Mesh "Tri"
{
	# Rotation +90 degrees around X
	# Translation 1 unit toward Y

    matrix4d xformOp:transform = (
		(1,  0, 0, 0),
		(0,  0, 1, 0),
		(0, -1, 0, 0),
		(0,  1, 0, 1)
	)
    uniform token[] xformOpOrder = ["xformOp:transform"]

    int[] faceVertexCounts = [3]
    int[] faceVertexIndices = [0, 1, 2]
	
    point3f[] points = [(0, 0, 0), (0, 1, 0), (1, 0, 0)]
}

Y-Up stage-y-up.usda:

#usda 1.0
(
    metersPerUnit = 1
    subLayers = [
        @stage-z-up.usda@
    ]
    upAxis = "Y"
)

over "Tri"
{
    matrix4d xformOp:transform = (
		(1, 0, 0, 0),
		(0, 0, 1, 0),
		(0, -1, 0, 0),
		(0, 0, -1, 1)
	)
    uniform token[] xformOpOrder = ["xformOp:transform"]

    point3f[] points = [(0, 0, 0), (0, 0, -1), (1, 0, 0)]
}

See the differences ?

  • the Translation part of the Prim transform (the last row) was “a translation 1 unit toward Y (0, 1, 0) in Z-Up” is now “a translation of -1 unit along Z in Y-Up“
  • the points coordinates have also changed

Here is the script I used to do the change of base:

from pxr import Gf, Usd, UsdGeom

# We create a new scene that sublayers the original,
# Z up layer. All edits will be overrides in our Y up scene

stage = Usd.Stage.CreateNew("stage-y-up.usda")
stage.GetRootLayer().subLayerPaths = ["stage-z-up.usda"]

# We set the new Y up layer axis metadata

UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.y)
UsdGeom.SetStageMetersPerUnit(stage, 1)

prim = stage.GetPrimAtPath("/Tri")

# This is the matrix used for the change of Z up to Y up
# A point "up" in Z (0, 0, 1) becomes a point "up" in Y (0, 1, 0)
# In another term, we need to apply a clockwise 90 degrees
# rotation around the X axis

transform_z_up_to_y_up = Gf.Matrix4d()
transform_z_up_to_y_up.SetRotate(Gf.Rotation([1, 0, 0], -90))

# First, we transform the UsdGeom.Xformable prim itself

xformable = UsdGeom.Xformable(prim)
op = xformable.MakeMatrixXform()

prim_transform_z_up = op.Get()
prim_transform_y_up = transform_z_up_to_y_up.GetInverse() * prim_transform_z_up * transform_z_up_to_y_up

op.Set(prim_transform_y_up)

# Then, we transform each point coordinates of the mesh

points_attr = UsdGeom.Mesh(prim).GetPointsAttr()

origin = Gf.Vec3f()

points_y_up = []
for point_z_up in points_attr.Get():
    point_z_up_transform = Gf.Matrix4d()
    point_z_up_transform.SetTranslate(Gf.Vec3d(point_z_up))
    
    point_y_up_transform = transform_z_up_to_y_up.GetInverse() * point_z_up_transform * transform_z_up_to_y_up

    point_y_up = point_y_up_transform.Transform(origin)
    
    points_y_up.append(Gf.Vec3f(point_y_up))

points_attr.Set(points_y_up)

# And we save the scene

stage.Save()

What if you also wanted to change from meters to centimeters ? The code would stay very much the same, you’d only scale transform_z_up_to_y_up by 100 (“1 meter becomes 100 centimeters”) and UsdGeom.SetStageMetersPerUnit(stage, 0.01).

As a note: point positions is not the only attribute you’ll need to take care of, you’ll certainly want to rotate the mesh normals as well.

That should be all that you want to become an ace of base.

1 Like

I cannot thank you enough @charlesfleche!!

I’m going to really enjoy reading, re-reading, and figuring all of this out. It should help me get everything working.

2 posts into this forum and already I feel like there should be a “send a beer” button :clinking_beer_mugs:

1 Like