# Copyright (c) 2024 The Foundry Visionmongers Ltd. All Rights Reserved.
import math
import _nukemath
"""
Mathematical functions for dealing with 3D geometry
"""
[docs]def transpose_matrix3(m):
'''
Transpose Matrix3 in place
'''
t = m[0+3*1]
m[0+3*1] = m[1+3*0]
m[1+3*0] = t
t = m[0+3*2]
m[0+3*2] = m[2+3*0]
m[2+3*0] = t
t = m[1+3*2]
m[1+3*2] = m[2+3*1]
m[2+3*1] = t
[docs]def plane_rotation(tri, norm=None) -> _nukemath.Vector3:
'''
Calculate the rotations around the X, Y and Z axes in radians that will align a plane
perpendicular to the Z axis with the given triangle.
@param tri: A list or tuple of 3 _nukemath.Vector3 objects. The 3 points must
describe the plane (i.e. they must not be collinear).
@return: A _nukemath.Vector3 object where the x coordinate is the angle of
rotation around the x axis and so on in radians.
@raise ValueError: if the three points are collinear.
'''
# Get the vectors along two edges of the triangle described by the three points.
a = tri[1] - tri[0]
b = tri[2] - tri[0]
# Calculate the basis vectors for an orthonormal basis, where:
# - u is parallel to a
# - v is perpendicular to u and lies in the plane defined by a and b
# - w is perpendicular to both u and v
u = _nukemath.Vector3(a)
u.normalize()
w = a.cross(b)
w.normalize()
v = w.cross(u)
# If a normal was passed in, check to make sure that the one we're generating
# is aligned close to the same way
if norm is not None:
if w.dot(norm) < 0.0:
w.x = -w.x
w.y = -w.y
w.z = -w.z
# Don't mirror!
v.x = -v.x
v.y = -v.y
v.z = -v.z
# Now find the rotation angles necessary to align a card to the uv plane.
m = ((u[0], v[0], w[0]),
(u[1], v[1], w[1]),
(u[2], v[2], w[2]))
if abs(m[1][2]) == 1.0:
ry = 0.0
rx = (math.pi / 2.0) * -m[1][2]
rz = math.atan2(-m[0][1], m[0][0])
else:
cosx = math.sqrt(m[0][2] ** 2 + m[2][2] ** 2)
if cosx == 0:
cosx = 1.0
rx = math.atan2(-m[1][2], cosx)
rz = math.atan2(m[1][0] / cosx, m[1][1] / cosx)
ry = math.atan2(m[0][2] / cosx, m[2][2] / cosx)
return _nukemath.Vector3(rx, ry, rz)
[docs]def translate_matrix(translations) -> _nukemath.Matrix4:
'''
Generates a translation matrix from the input vector.
@type translations: _nukemath.Vector3
@param translations: Vector that will be used generate the translation
matrix.
@return: The translate matrix.
'''
m = _nukemath.Matrix4()
m.makeIdentity()
m.translation(translations[0], translations[1], translations[2])
return m
[docs]def rotate_matrix_xyz(rotations) -> _nukemath.Matrix4:
'''
Generates a rotation XYZ matrix from the input vector.
@type rotations: _nukemath.Vector3
@param rotations: Vector that will be used generate the rotate matrix.
@return: The rotate matrix.
'''
m = _nukemath.Matrix4()
m.makeIdentity()
m.rotateZ(rotations[2])
m.rotateY(rotations[1])
m.rotateX(rotations[0])
return m
[docs]def scaling_matrix(scalings) -> _nukemath.Matrix4:
'''
Generates a scaling matrix from the input vector.
@type scalings: _nukemath.Vector3
@param scalings: Vector that will be used generate the scaling matrix.
@return: The scaling matrix.
'''
m = _nukemath.Matrix4()
m.makeIdentity()
m.scaling(scalings[0], scalings[1], scalings[2])
return m
[docs]def rotate_matrix_zxy(rotations) -> _nukemath.Matrix4:
'''
Generates a rotation ZXY matrix from the input vector.
@type rotations: _nukemath.Vector3
@param rotations: Vector that will be used generate the rotate matrix.
@return: The rotate matrix.
'''
m = _nukemath.Matrix4()
m.makeIdentity()
m.rotateY(rotations[1])
m.rotateX(rotations[0])
m.rotateZ(rotations[2])
return m
[docs]def get_world_point_on_bbox(prim, position: str, time) -> _nukemath.Vector3:
'''
Return points inside or on the bounding box of a prim in world space
'''
from pxr import UsdGeom, Gf
target_imageable = UsdGeom.Imageable(prim)
local_bounds_box = target_imageable.ComputeLocalBound(time,
UsdGeom.Tokens.default_,
UsdGeom.Tokens.render,
UsdGeom.Tokens.proxy)
bounds_range = local_bounds_box.GetBox()
bounds_range_min = bounds_range.GetMin()
bounds_range_size = bounds_range.GetSize()
point = None
if position == "Center":
point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2,
bounds_range_size[1] / 2,
bounds_range_size[2] / 2)
elif position == "Top":
point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2,
bounds_range_size[1],
bounds_range_size[2] / 2)
elif position == "Bottom":
point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2,
0,
bounds_range_size[2] / 2)
elif position == "Left":
point = bounds_range_min + Gf.Vec3d(0,
bounds_range_size[1] / 2,
bounds_range_size[2] / 2)
elif position == "Right":
point = bounds_range_min + Gf.Vec3d(bounds_range_size[0],
bounds_range_size[1] / 2,
bounds_range_size[2] / 2)
elif position == "Front":
point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2,
bounds_range_size[1] / 2,
bounds_range_size[2])
elif position == "Back":
point = bounds_range_min + Gf.Vec3d(bounds_range_size[0] / 2,
bounds_range_size[1] / 2,
0)
else:
raise ValueError("Invalid snapping position")
local_to_world_transform = target_imageable.ComputeLocalToWorldTransform(time)
return _nukemath.Vector3(*local_to_world_transform.Transform(point))
[docs]def get_prim_world_rotation_zxy(prim, time) -> list:
'''
Get xyz rotation angles in radians for the prim in world space
assuming zxy rotation order
'''
from pxr import UsdGeom, Gf
xform = UsdGeom.Xformable(prim)
mat: Gf.Matrix4d = xform.ComputeLocalToWorldTransform(time)
mat.Orthonormalize()
rotation_deg = mat.ExtractRotation().Decompose(Gf.Vec3d(0, 1, 0),
Gf.Vec3d(1, 0, 0),
Gf.Vec3d(0, 0, 1))
return [math.radians(rotation_deg[i]) for i in (1, 0, 2)]