// Axis conventions of other tools / engines / file formats and exact conversions to and from
// this library's world (right-handed, +X right, +Y up, -Z forward -- same as OpenGL, glTF,
// three.js, Maya, WebGPU/Vulkan world space).
///|
pub(all) enum Axis {
PosX
NegX
PosY
NegY
PosZ
NegZ
} derive(Eq, Debug)
///|
pub fn Axis::to_vec3(self : Axis) -> Vec3 {
match self {
PosX => { x: 1.0, y: 0.0, z: 0.0, }
NegX => { x: -1.0, y: 0.0, z: 0.0, }
PosY => { x: 0.0, y: 1.0, z: 0.0, }
NegY => { x: 0.0, y: -1.0, z: 0.0, }
PosZ => { x: 0.0, y: 0.0, z: 1.0, }
NegZ => { x: 0.0, y: 0.0, z: -1.0, }
}
}
///|
fn Axis::index(self : Axis) -> Int {
match self {
PosX | NegX => 0
PosY | NegY => 1
PosZ | NegZ => 2
}
}
///|
/// Which world axes mean "right", "up" and "forward" (the direction a camera / character looks).
#valtype
pub(all) struct Convention {
right : Axis
up : Axis
forward : Axis
} derive(Eq, Debug)
///|
/// This library / OpenGL / glTF / three.js / Maya: right +X, up +Y, forward -Z (right-handed).
pub fn Convention::native() -> Convention {
{ right: PosX, up: PosY, forward: NegZ, }
}
///|
/// Blender, 3ds Max, SketchUp, most CAD: right +X, up +Z, forward +Y (right-handed).
pub fn Convention::z_up_right_handed() -> Convention {
{ right: PosX, up: PosZ, forward: PosY, }
}
///|
/// Unity, Direct3D-style left-handed world: right +X, up +Y, forward +Z.
pub fn Convention::y_up_left_handed() -> Convention {
{ right: PosX, up: PosY, forward: PosZ, }
}
///|
/// Unreal Engine: forward +X, right +Y, up +Z (left-handed).
pub fn Convention::unreal() -> Convention {
{ right: PosY, up: PosZ, forward: PosX, }
}
///|
/// ROS REP-103 / Source engine style: forward +X, left +Y (right -Y), up +Z (right-handed).
pub fn Convention::ros() -> Convention {
{ right: NegY, up: PosZ, forward: PosX, }
}
///|
/// Valid when right/up/forward use three different coordinate axes.
pub fn Convention::is_valid(self : Convention) -> Bool {
let r = self.right.index()
let u = self.up.index()
let f = self.forward.index()
r != u && r != f && u != f
}
///|
/// Handedness of the world: right-handed when `right x up` points BACKWARD (= -forward).
pub fn Convention::is_right_handed(self : Convention) -> Bool {
self.right.to_vec3().cross(self.up.to_vec3()).dot(self.forward.to_vec3()) <
0.0
}
///|
fn Convention::basis(self : Convention) -> Mat3 {
Mat3::from_cols(
self.right.to_vec3(),
self.up.to_vec3(),
self.forward.to_vec3(),
)
}
///|
/// Signed permutation matrix M such that `v_to = M * v_from`: right stays right, up stays up,
/// forward stays forward. det(M) = -1 when the two conventions differ in handedness (a mirror);
/// remember to flip triangle winding in that case.
pub fn Convention::basis_change(self : Convention, to : Convention) -> Mat3 {
if !self.is_valid() || !to.is_valid() {
abort(
"Convention::basis_change: invalid convention (axes must be distinct)",
)
}
to.basis() * self.basis().transpose()
}
///|
/// Re-express a point / direction given in `self` convention in `to` convention.
pub fn Convention::convert_vec3(
self : Convention,
to : Convention,
v : Vec3,
) -> Vec3 {
self.basis_change(to).mul_vec3(v)
}
///|
/// Re-express an orientation: the same physical rotation, described in the other axes.
/// Axis -> M * axis * det(M), angle unchanged (a mirror reverses the sense of rotation).
pub fn Convention::convert_quat(
self : Convention,
to : Convention,
q : Quat,
) -> Quat {
let m = self.basis_change(to)
let det = m.determinant()
let a = m.mul_vec3(Vec3::new(q.x, q.y, q.z)).scale(det)
Quat::new(a.x, a.y, a.z, q.w)
}
///|
/// Re-express a 4x4 transform: M4 * T * M4^-1 (M4 is its own inverse transpose; orthogonal).
pub fn Convention::convert_mat4(
self : Convention,
to : Convention,
t : Mat4,
) -> Mat4 {
let m = Mat4::from_mat3(self.basis_change(to))
m * t * m.transpose()
}