// Curvilinear coordinates: polar, cylindrical, spherical.
//
// All three are expressed in the library's world frame (right-handed, +Y up, -Z forward) with
// ONE azimuth convention so they compose with rotations:
// azimuth = yaw angle about +Y by the right-hand rule, measured from the -Z (forward)
// axis: azimuth 0 -> -Z, +PI/2 -> -X, PI -> +Z, -PI/2 -> +X.
// (i.e. direction = Quat::from_y(azimuth).rotate(-Z))
// elevation = angle above the XZ ground plane, in [-PI/2, PI/2] (+PI/2 = +Y).
///|
/// 2D polar coordinates: `angle` counter-clockwise from +X (y-up frame).
#valtype
pub(all) struct Polar {
radius : Double
angle : Double
} derive(Eq, Debug)
///|
pub fn Polar::new(radius : Double, angle : Double) -> Polar {
{ radius, angle, }
}
///|
pub fn Polar::to_cartesian(self : Polar) -> Vec2 {
{
x: self.radius * @math.cos(self.angle),
y: self.radius * @math.sin(self.angle),
}
}
///|
/// Radius >= 0, angle in (-PI, PI]; the origin maps to angle 0.
pub fn Polar::from_cartesian(p : Vec2) -> Polar {
{
radius: p.length(),
angle: if p.x == 0.0 && p.y == 0.0 {
0.0
} else {
@math.atan2(p.y, p.x)
},
}
}
///|
/// Cylindrical coordinates about the +Y axis.
#valtype
pub(all) struct Cylindrical {
radius : Double
azimuth : Double
height : Double
} derive(Eq, Debug)
///|
pub fn Cylindrical::new(
radius : Double,
azimuth : Double,
height : Double,
) -> Cylindrical {
{ radius, azimuth, height, }
}
///|
pub fn Cylindrical::to_cartesian(self : Cylindrical) -> Vec3 {
{
x: -self.radius * @math.sin(self.azimuth),
y: self.height,
z: -self.radius * @math.cos(self.azimuth),
}
}
///|
pub fn Cylindrical::from_cartesian(p : Vec3) -> Cylindrical {
let r = @math.hypot(p.x, p.z)
{
radius: r,
azimuth: if r == 0.0 {
0.0
} else {
@math.atan2(-p.x, -p.z)
},
height: p.y,
}
}
///|
/// Spherical coordinates (radius, azimuth, elevation) -- see the module comment.
#valtype
pub(all) struct Spherical {
radius : Double
azimuth : Double
elevation : Double
} derive(Eq, Debug)
///|
pub fn Spherical::new(
radius : Double,
azimuth : Double,
elevation : Double,
) -> Spherical {
{ radius, azimuth, elevation, }
}
///|
pub fn Spherical::to_cartesian(self : Spherical) -> Vec3 {
let ce = @math.cos(self.elevation)
{
x: -self.radius * @math.sin(self.azimuth) * ce,
y: self.radius * @math.sin(self.elevation),
z: -self.radius * @math.cos(self.azimuth) * ce,
}
}
///|
/// Radius >= 0, azimuth in (-PI, PI], elevation in [-PI/2, PI/2]. At the poles (and the
/// origin) azimuth is reported as 0; at the origin elevation is also 0.
pub fn Spherical::from_cartesian(p : Vec3) -> Spherical {
let r = p.length()
let h = @math.hypot(p.x, p.z)
{
radius: r,
azimuth: if h == 0.0 {
0.0
} else {
@math.atan2(-p.x, -p.z)
},
elevation: if r == 0.0 {
0.0
} else {
@math.atan2(p.y, h)
},
}
}
///|
/// Unit direction for yaw (azimuth) and pitch (elevation).
pub fn Vec3::from_yaw_pitch(yaw : Double, pitch : Double) -> Vec3 {
Spherical::new(1.0, yaw, pitch).to_cartesian()
}
///|
/// (yaw, pitch) of a direction (need not be unit).
pub fn Vec3::to_yaw_pitch(self : Vec3) -> (Double, Double) {
let s = Spherical::from_cartesian(self)
(s.azimuth, s.elevation)
}