use crate::*;
impl Numeric {
pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
value.max(min).min(max)
}
pub fn lerp(start: f64, end: f64, t: f64) -> f64 {
start + (end - start) * t
}
pub fn deg_to_rad(degrees: f64) -> f64 {
degrees * DEG_TO_RAD
}
pub fn rad_to_deg(radians: f64) -> f64 {
radians * RAD_TO_DEG
}
pub fn normalize_angle(radians: f64) -> f64 {
let mut angle: f64 = radians % TWO_PI;
if angle < -PI {
angle += TWO_PI;
}
if angle > PI {
angle -= TWO_PI;
}
angle
}
pub fn angle_delta(from: f64, to: f64) -> f64 {
Self::normalize_angle(to - from)
}
pub fn lerp_angle(from: f64, to: f64, t: f64) -> f64 {
from + Self::angle_delta(from, to) * t
}
pub fn distance(a: Vector2D, b: Vector2D) -> f64 {
(b - a).magnitude()
}
pub fn distance_squared(a: Vector2D, b: Vector2D) -> f64 {
(b - a).magnitude_squared()
}
pub fn smoothstep(edge_min: f64, edge_max: f64, value: f64) -> f64 {
let clamped: f64 = Self::clamp((value - edge_min) / (edge_max - edge_min), 0.0, 1.0);
clamped * clamped * (3.0 - 2.0 * clamped)
}
pub fn approach(current: f64, target: f64, max_delta: f64) -> f64 {
if (target - current).abs() <= max_delta {
return target;
}
current + max_delta.signum() * max_delta
}
pub fn sign(value: f64) -> f64 {
if value > 0.0 {
1.0
} else if value < 0.0 {
-1.0
} else {
0.0
}
}
pub fn wrap(value: f64, max: f64) -> f64 {
let result: f64 = value % max;
if result < 0.0 { result + max } else { result }
}
pub fn sign_or_positive(value: f64) -> f64 {
if value < 0.0 { -1.0 } else { 1.0 }
}
pub fn distance_3d(a: Vector3D, b: Vector3D) -> f64 {
(b - a).magnitude()
}
pub fn distance_squared_3d(a: Vector3D, b: Vector3D) -> f64 {
(b - a).magnitude_squared()
}
}
impl Interpolable for f64 {
fn lerp(&self, other: f64, t: f64) -> f64 {
*self + (other - *self) * t
}
}
impl Vector2D {
pub fn zero() -> Vector2D {
Vector2D::new(0.0, 0.0)
}
pub fn right() -> Vector2D {
Vector2D::new(1.0, 0.0)
}
pub fn up() -> Vector2D {
Vector2D::new(0.0, -1.0)
}
pub fn from_angle(radians: f64) -> Vector2D {
Vector2D::new(radians.cos(), radians.sin())
}
pub fn magnitude(&self) -> f64 {
(self.get_x() * self.get_x() + self.get_y() * self.get_y()).sqrt()
}
pub fn magnitude_squared(&self) -> f64 {
self.get_x() * self.get_x() + self.get_y() * self.get_y()
}
pub fn normalized(&self) -> Vector2D {
let mag: f64 = self.magnitude();
if mag < EPSILON {
return Vector2D::zero();
}
Vector2D::new(self.get_x() / mag, self.get_y() / mag)
}
pub fn normalize(&mut self) {
let mag: f64 = self.magnitude();
if mag < EPSILON {
self.set_x(0.0);
self.set_y(0.0);
return;
}
self.set_x(self.get_x() / mag);
self.set_y(self.get_y() / mag);
}
pub fn dot(&self, other: Vector2D) -> f64 {
self.get_x() * other.get_x() + self.get_y() * other.get_y()
}
pub fn cross(&self, other: Vector2D) -> f64 {
self.get_x() * other.get_y() - self.get_y() * other.get_x()
}
pub fn perp(&self) -> Vector2D {
Vector2D::new(-self.get_y(), self.get_x())
}
pub fn angle(&self) -> f64 {
self.get_y().atan2(self.get_x())
}
pub fn angle_to(&self, other: Vector2D) -> f64 {
(other - *self).angle()
}
pub fn rotated(&self, radians: f64) -> Vector2D {
let cos: f64 = radians.cos();
let sin: f64 = radians.sin();
Vector2D::new(
self.get_x() * cos - self.get_y() * sin,
self.get_x() * sin + self.get_y() * cos,
)
}
pub fn rotate(&mut self, radians: f64) {
let cos: f64 = radians.cos();
let sin: f64 = radians.sin();
let new_x: f64 = self.get_x() * cos - self.get_y() * sin;
let new_y: f64 = self.get_x() * sin + self.get_y() * cos;
self.set_x(new_x);
self.set_y(new_y);
}
pub fn distance_to(&self, other: Vector2D) -> f64 {
(other - *self).magnitude()
}
pub fn distance_squared_to(&self, other: Vector2D) -> f64 {
(other - *self).magnitude_squared()
}
pub fn direction_to(&self, other: Vector2D) -> Vector2D {
(other - *self).normalized()
}
pub fn lerp(&self, other: Vector2D, t: f64) -> Vector2D {
Vector2D::new(
self.get_x() + (other.get_x() - self.get_x()) * t,
self.get_y() + (other.get_y() - self.get_y()) * t,
)
}
pub fn scale(&mut self, scalar: f64) {
self.set_x(self.get_x() * scalar);
self.set_y(self.get_y() * scalar);
}
pub fn scaled(&self, scalar: f64) -> Vector2D {
Vector2D::new(self.get_x() * scalar, self.get_y() * scalar)
}
}
impl Interpolable for Vector2D {
fn lerp(&self, other: Vector2D, t: f64) -> Vector2D {
Vector2D::lerp(self, other, t)
}
}
impl Add for Vector2D {
type Output = Vector2D;
fn add(self, other: Vector2D) -> Vector2D {
Vector2D::new(self.get_x() + other.get_x(), self.get_y() + other.get_y())
}
}
impl Sub for Vector2D {
type Output = Vector2D;
fn sub(self, other: Vector2D) -> Vector2D {
Vector2D::new(self.get_x() - other.get_x(), self.get_y() - other.get_y())
}
}
impl Mul<f64> for Vector2D {
type Output = Vector2D;
fn mul(self, scalar: f64) -> Vector2D {
Vector2D::new(self.get_x() * scalar, self.get_y() * scalar)
}
}
impl Neg for Vector2D {
type Output = Vector2D;
fn neg(self) -> Vector2D {
Vector2D::new(-self.get_x(), -self.get_y())
}
}
impl AddAssign for Vector2D {
fn add_assign(&mut self, other: Vector2D) {
self.set_x(self.get_x() + other.get_x());
self.set_y(self.get_y() + other.get_y());
}
}
impl SubAssign for Vector2D {
fn sub_assign(&mut self, other: Vector2D) {
self.set_x(self.get_x() - other.get_x());
self.set_y(self.get_y() - other.get_y());
}
}
impl MulAssign<f64> for Vector2D {
fn mul_assign(&mut self, scalar: f64) {
self.set_x(self.get_x() * scalar);
self.set_y(self.get_y() * scalar);
}
}
impl Rect {
pub fn from_center(center: Vector2D, width: f64, height: f64) -> Rect {
Rect::new(
center.get_x() - width * 0.5,
center.get_y() - height * 0.5,
width,
height,
)
}
pub fn center(&self) -> Vector2D {
Vector2D::new(
self.get_x() + self.get_width() * 0.5,
self.get_y() + self.get_height() * 0.5,
)
}
pub fn min(&self) -> Vector2D {
Vector2D::new(self.get_x(), self.get_y())
}
pub fn max(&self) -> Vector2D {
Vector2D::new(
self.get_x() + self.get_width(),
self.get_y() + self.get_height(),
)
}
pub fn size(&self) -> Vector2D {
Vector2D::new(self.get_width(), self.get_height())
}
pub fn contains(&self, point: Vector2D) -> bool {
point.get_x() >= self.get_x()
&& point.get_x() <= self.get_x() + self.get_width()
&& point.get_y() >= self.get_y()
&& point.get_y() <= self.get_y() + self.get_height()
}
pub fn intersects(&self, other: Rect) -> bool {
self.get_x() < other.get_x() + other.get_width()
&& self.get_x() + self.get_width() > other.get_x()
&& self.get_y() < other.get_y() + other.get_height()
&& self.get_y() + self.get_height() > other.get_y()
}
pub fn intersection(&self, other: Rect) -> Option<Rect> {
if !self.intersects(other) {
return None;
}
let max_x: f64 = self.get_x().max(other.get_x());
let max_y: f64 = self.get_y().max(other.get_y());
let min_right: f64 =
(self.get_x() + self.get_width()).min(other.get_x() + other.get_width());
let min_bottom: f64 =
(self.get_y() + self.get_height()).min(other.get_y() + other.get_height());
Some(Rect::new(
max_x,
max_y,
min_right - max_x,
min_bottom - max_y,
))
}
}
impl Circle {
pub fn contains(&self, point: Vector2D) -> bool {
self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
}
pub fn intersects(&self, other: Circle) -> bool {
let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
let radius_sum: f64 = self.get_radius() + other.get_radius();
distance_sq <= radius_sum * radius_sum
}
pub fn circumference(&self) -> f64 {
TWO_PI * self.get_radius()
}
pub fn area(&self) -> f64 {
PI * self.get_radius() * self.get_radius()
}
}
impl Transform2D {
pub fn identity() -> Transform2D {
Transform2D::new(Vector2D::zero(), 0.0, Vector2D::new(1.0, 1.0))
}
pub fn translate(&mut self, offset: Vector2D) {
self.set_position(self.get_position() + offset);
}
pub fn rotate(&mut self, radians: f64) {
self.set_rotation(self.get_rotation() + radians);
}
pub fn scale_by(&mut self, factors: Vector2D) {
let mut scale: Vector2D = self.get_scale();
scale.set_x(scale.get_x() * factors.get_x());
scale.set_y(scale.get_y() * factors.get_y());
self.set_scale(scale);
}
pub fn apply_to_point(&self, point: Vector2D) -> Vector2D {
let scaled: Vector2D = Vector2D::new(
point.get_x() * self.get_scale().get_x(),
point.get_y() * self.get_scale().get_y(),
);
scaled.rotated(self.get_rotation()) + self.get_position()
}
}
impl Default for Transform2D {
fn default() -> Transform2D {
Transform2D::identity()
}
}
impl Color {
pub fn from_rgb(red: u8, green: u8, blue: u8) -> Color {
Color::new(
red as f64 / 255.0,
green as f64 / 255.0,
blue as f64 / 255.0,
1.0,
)
}
pub fn to_css_rgba(&self) -> String {
format!(
"rgba({}, {}, {}, {})",
(self.get_red() * 255.0).round() as i32,
(self.get_green() * 255.0).round() as i32,
(self.get_blue() * 255.0).round() as i32,
self.get_alpha()
)
}
pub fn black() -> Color {
Color::new(0.0, 0.0, 0.0, 1.0)
}
pub fn white() -> Color {
Color::new(1.0, 1.0, 1.0, 1.0)
}
pub fn transparent() -> Color {
Color::new(0.0, 0.0, 0.0, 0.0)
}
}
impl Default for Color {
fn default() -> Color {
Color::black()
}
}
impl Vector3D {
pub fn zero() -> Vector3D {
Vector3D::new(0.0, 0.0, 0.0)
}
pub fn right() -> Vector3D {
Vector3D::new(1.0, 0.0, 0.0)
}
pub fn up() -> Vector3D {
Vector3D::new(0.0, 1.0, 0.0)
}
pub fn forward() -> Vector3D {
Vector3D::new(0.0, 0.0, -1.0)
}
pub fn magnitude(&self) -> f64 {
(self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z())
.sqrt()
}
pub fn magnitude_squared(&self) -> f64 {
self.get_x() * self.get_x() + self.get_y() * self.get_y() + self.get_z() * self.get_z()
}
pub fn normalized(&self) -> Vector3D {
let mag: f64 = self.magnitude();
if mag < EPSILON {
return Vector3D::zero();
}
Vector3D::new(self.get_x() / mag, self.get_y() / mag, self.get_z() / mag)
}
pub fn normalize(&mut self) {
let mag: f64 = self.magnitude();
if mag < EPSILON {
self.set_x(0.0);
self.set_y(0.0);
self.set_z(0.0);
return;
}
self.set_x(self.get_x() / mag);
self.set_y(self.get_y() / mag);
self.set_z(self.get_z() / mag);
}
pub fn dot(&self, other: Vector3D) -> f64 {
self.get_x() * other.get_x() + self.get_y() * other.get_y() + self.get_z() * other.get_z()
}
pub fn cross(&self, other: Vector3D) -> Vector3D {
Vector3D::new(
self.get_y() * other.get_z() - self.get_z() * other.get_y(),
self.get_z() * other.get_x() - self.get_x() * other.get_z(),
self.get_x() * other.get_y() - self.get_y() * other.get_x(),
)
}
pub fn distance_to(&self, other: Vector3D) -> f64 {
(other - *self).magnitude()
}
pub fn distance_squared_to(&self, other: Vector3D) -> f64 {
(other - *self).magnitude_squared()
}
pub fn direction_to(&self, other: Vector3D) -> Vector3D {
(other - *self).normalized()
}
pub fn lerp(&self, other: Vector3D, t: f64) -> Vector3D {
Vector3D::new(
self.get_x() + (other.get_x() - self.get_x()) * t,
self.get_y() + (other.get_y() - self.get_y()) * t,
self.get_z() + (other.get_z() - self.get_z()) * t,
)
}
pub fn scale(&mut self, scalar: f64) {
self.set_x(self.get_x() * scalar);
self.set_y(self.get_y() * scalar);
self.set_z(self.get_z() * scalar);
}
pub fn scaled(&self, scalar: f64) -> Vector3D {
Vector3D::new(
self.get_x() * scalar,
self.get_y() * scalar,
self.get_z() * scalar,
)
}
pub fn rotated_by(&self, quaternion: Quaternion) -> Vector3D {
let pure: Quaternion = Quaternion::new(self.get_x(), self.get_y(), self.get_z(), 0.0);
let result: Quaternion = quaternion * pure * quaternion.conjugate();
Vector3D::new(result.get_x(), result.get_y(), result.get_z())
}
}
impl Interpolable for Vector3D {
fn lerp(&self, other: Vector3D, t: f64) -> Vector3D {
Vector3D::lerp(self, other, t)
}
}
impl Add for Vector3D {
type Output = Vector3D;
fn add(self, other: Vector3D) -> Vector3D {
Vector3D::new(
self.get_x() + other.get_x(),
self.get_y() + other.get_y(),
self.get_z() + other.get_z(),
)
}
}
impl Sub for Vector3D {
type Output = Vector3D;
fn sub(self, other: Vector3D) -> Vector3D {
Vector3D::new(
self.get_x() - other.get_x(),
self.get_y() - other.get_y(),
self.get_z() - other.get_z(),
)
}
}
impl Mul<f64> for Vector3D {
type Output = Vector3D;
fn mul(self, scalar: f64) -> Vector3D {
Vector3D::new(
self.get_x() * scalar,
self.get_y() * scalar,
self.get_z() * scalar,
)
}
}
impl Neg for Vector3D {
type Output = Vector3D;
fn neg(self) -> Vector3D {
Vector3D::new(-self.get_x(), -self.get_y(), -self.get_z())
}
}
impl AddAssign for Vector3D {
fn add_assign(&mut self, other: Vector3D) {
self.set_x(self.get_x() + other.get_x());
self.set_y(self.get_y() + other.get_y());
self.set_z(self.get_z() + other.get_z());
}
}
impl SubAssign for Vector3D {
fn sub_assign(&mut self, other: Vector3D) {
self.set_x(self.get_x() - other.get_x());
self.set_y(self.get_y() - other.get_y());
self.set_z(self.get_z() - other.get_z());
}
}
impl MulAssign<f64> for Vector3D {
fn mul_assign(&mut self, scalar: f64) {
self.set_x(self.get_x() * scalar);
self.set_y(self.get_y() * scalar);
self.set_z(self.get_z() * scalar);
}
}
impl Quaternion {
pub fn identity() -> Quaternion {
Quaternion::new(0.0, 0.0, 0.0, 1.0)
}
pub fn from_axis_angle(axis: Vector3D, angle: f64) -> Quaternion {
let half: f64 = angle * 0.5;
let sin_half: f64 = half.sin();
let cos_half: f64 = half.cos();
let normalized_axis: Vector3D = axis.normalized();
Quaternion::new(
normalized_axis.get_x() * sin_half,
normalized_axis.get_y() * sin_half,
normalized_axis.get_z() * sin_half,
cos_half,
)
}
pub fn from_euler(yaw: f64, pitch: f64, roll: f64) -> Quaternion {
let half_yaw: f64 = yaw * 0.5;
let half_pitch: f64 = pitch * 0.5;
let half_roll: f64 = roll * 0.5;
let cy: f64 = half_yaw.cos();
let sy: f64 = half_yaw.sin();
let cp: f64 = half_pitch.cos();
let sp: f64 = half_pitch.sin();
let cr: f64 = half_roll.cos();
let sr: f64 = half_roll.sin();
Quaternion::new(
sp * cy * cr + cp * sy * sr,
cp * sy * cr - sp * cy * sr,
cp * cy * sr - sp * sy * cr,
sp * sy * sr + cp * cy * cr,
)
}
pub fn magnitude(&self) -> f64 {
(self.get_x() * self.get_x()
+ self.get_y() * self.get_y()
+ self.get_z() * self.get_z()
+ self.get_w() * self.get_w())
.sqrt()
}
pub fn normalized(&self) -> Quaternion {
let mag: f64 = self.magnitude();
if mag < EPSILON {
return Quaternion::identity();
}
let inv: f64 = 1.0 / mag;
Quaternion::new(
self.get_x() * inv,
self.get_y() * inv,
self.get_z() * inv,
self.get_w() * inv,
)
}
pub fn conjugate(&self) -> Quaternion {
Quaternion::new(-self.get_x(), -self.get_y(), -self.get_z(), self.get_w())
}
pub fn dot(&self, other: Quaternion) -> f64 {
self.get_x() * other.get_x()
+ self.get_y() * other.get_y()
+ self.get_z() * other.get_z()
+ self.get_w() * other.get_w()
}
pub fn slerp(&self, other: Quaternion, t: f64) -> Quaternion {
let mut cos_theta: f64 = self.dot(other);
let target: Quaternion = if cos_theta < 0.0 {
cos_theta = -cos_theta;
Quaternion::new(
-other.get_x(),
-other.get_y(),
-other.get_z(),
-other.get_w(),
)
} else {
other
};
if cos_theta > 1.0 - EPSILON {
return Quaternion::new(
self.get_x() + (target.get_x() - self.get_x()) * t,
self.get_y() + (target.get_y() - self.get_y()) * t,
self.get_z() + (target.get_z() - self.get_z()) * t,
self.get_w() + (target.get_w() - self.get_w()) * t,
)
.normalized();
}
let theta: f64 = cos_theta.acos();
let sin_theta: f64 = theta.sin();
let factor_a: f64 = ((1.0 - t) * theta).sin() / sin_theta;
let factor_b: f64 = (t * theta).sin() / sin_theta;
Quaternion::new(
self.get_x() * factor_a + target.get_x() * factor_b,
self.get_y() * factor_a + target.get_y() * factor_b,
self.get_z() * factor_a + target.get_z() * factor_b,
self.get_w() * factor_a + target.get_w() * factor_b,
)
}
}
impl Mul for Quaternion {
type Output = Quaternion;
fn mul(self, other: Quaternion) -> Quaternion {
Quaternion::new(
self.get_w() * other.get_x()
+ self.get_x() * other.get_w()
+ self.get_y() * other.get_z()
- self.get_z() * other.get_y(),
self.get_w() * other.get_y() - self.get_x() * other.get_z()
+ self.get_y() * other.get_w()
+ self.get_z() * other.get_x(),
self.get_w() * other.get_z() + self.get_x() * other.get_y()
- self.get_y() * other.get_x()
+ self.get_z() * other.get_w(),
self.get_w() * other.get_w()
- self.get_x() * other.get_x()
- self.get_y() * other.get_y()
- self.get_z() * other.get_z(),
)
}
}
impl Matrix4x4 {
pub fn identity() -> Matrix4x4 {
Matrix4x4::new([
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
])
}
pub fn translation(translation: Vector3D) -> Matrix4x4 {
let mut elements: [f64; 16] = Self::identity().get_elements();
elements[12] = translation.get_x();
elements[13] = translation.get_y();
elements[14] = translation.get_z();
Matrix4x4::new(elements)
}
pub fn scaling(scale: Vector3D) -> Matrix4x4 {
Matrix4x4::new([
scale.get_x(),
0.0,
0.0,
0.0,
0.0,
scale.get_y(),
0.0,
0.0,
0.0,
0.0,
scale.get_z(),
0.0,
0.0,
0.0,
0.0,
1.0,
])
}
pub fn rotation(quaternion: Quaternion) -> Matrix4x4 {
let xx: f64 = quaternion.get_x() * quaternion.get_x();
let yy: f64 = quaternion.get_y() * quaternion.get_y();
let zz: f64 = quaternion.get_z() * quaternion.get_z();
let xy: f64 = quaternion.get_x() * quaternion.get_y();
let xz: f64 = quaternion.get_x() * quaternion.get_z();
let yz: f64 = quaternion.get_y() * quaternion.get_z();
let wx: f64 = quaternion.get_w() * quaternion.get_x();
let wy: f64 = quaternion.get_w() * quaternion.get_y();
let wz: f64 = quaternion.get_w() * quaternion.get_z();
Matrix4x4::new([
1.0 - 2.0 * (yy + zz),
2.0 * (xy + wz),
2.0 * (xz - wy),
0.0,
2.0 * (xy - wz),
1.0 - 2.0 * (xx + zz),
2.0 * (yz + wx),
0.0,
2.0 * (xz + wy),
2.0 * (yz - wx),
1.0 - 2.0 * (xx + yy),
0.0,
0.0,
0.0,
0.0,
1.0,
])
}
pub fn perspective(fov: f64, aspect: f64, near: f64, far: f64) -> Matrix4x4 {
let f: f64 = 1.0 / (fov * 0.5).tan();
let range: f64 = far - near;
Matrix4x4::new([
f / aspect,
0.0,
0.0,
0.0,
0.0,
f,
0.0,
0.0,
0.0,
0.0,
-(far + near) / range,
-1.0,
0.0,
0.0,
-(2.0 * far * near) / range,
0.0,
])
}
pub fn orthographic(
left: f64,
right: f64,
bottom: f64,
top: f64,
near: f64,
far: f64,
) -> Matrix4x4 {
let rml: f64 = right - left;
let tmb: f64 = top - bottom;
let fmn: f64 = far - near;
Matrix4x4::new([
2.0 / rml,
0.0,
0.0,
0.0,
0.0,
2.0 / tmb,
0.0,
0.0,
0.0,
0.0,
-2.0 / fmn,
0.0,
-(right + left) / rml,
-(top + bottom) / tmb,
-(far + near) / fmn,
1.0,
])
}
pub fn look_at(eye: Vector3D, target: Vector3D, up: Vector3D) -> Matrix4x4 {
let forward: Vector3D = (target - eye).normalized();
let right: Vector3D = forward.cross(up).normalized();
let up_orthogonal: Vector3D = right.cross(forward);
Matrix4x4::new([
right.get_x(),
up_orthogonal.get_x(),
-forward.get_x(),
0.0,
right.get_y(),
up_orthogonal.get_y(),
-forward.get_y(),
0.0,
right.get_z(),
up_orthogonal.get_z(),
-forward.get_z(),
0.0,
-right.dot(eye),
-up_orthogonal.dot(eye),
forward.dot(eye),
1.0,
])
}
pub fn multiply(&self, other: Matrix4x4) -> Matrix4x4 {
let a: [f64; 16] = self.get_elements();
let b: [f64; 16] = other.get_elements();
Matrix4x4::new([
a[0] * b[0] + a[4] * b[1] + a[8] * b[2] + a[12] * b[3],
a[1] * b[0] + a[5] * b[1] + a[9] * b[2] + a[13] * b[3],
a[2] * b[0] + a[6] * b[1] + a[10] * b[2] + a[14] * b[3],
a[3] * b[0] + a[7] * b[1] + a[11] * b[2] + a[15] * b[3],
a[0] * b[4] + a[4] * b[5] + a[8] * b[6] + a[12] * b[7],
a[1] * b[4] + a[5] * b[5] + a[9] * b[6] + a[13] * b[7],
a[2] * b[4] + a[6] * b[5] + a[10] * b[6] + a[14] * b[7],
a[3] * b[4] + a[7] * b[5] + a[11] * b[6] + a[15] * b[7],
a[0] * b[8] + a[4] * b[9] + a[8] * b[10] + a[12] * b[11],
a[1] * b[8] + a[5] * b[9] + a[9] * b[10] + a[13] * b[11],
a[2] * b[8] + a[6] * b[9] + a[10] * b[10] + a[14] * b[11],
a[3] * b[8] + a[7] * b[9] + a[11] * b[10] + a[15] * b[11],
a[0] * b[12] + a[4] * b[13] + a[8] * b[14] + a[12] * b[15],
a[1] * b[12] + a[5] * b[13] + a[9] * b[14] + a[13] * b[15],
a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15],
])
}
pub fn transform_point(&self, point: Vector3D) -> Vector3D {
let elements: [f64; 16] = self.get_elements();
let x: f64 = elements[0] * point.get_x()
+ elements[4] * point.get_y()
+ elements[8] * point.get_z()
+ elements[12];
let y: f64 = elements[1] * point.get_x()
+ elements[5] * point.get_y()
+ elements[9] * point.get_z()
+ elements[13];
let z: f64 = elements[2] * point.get_x()
+ elements[6] * point.get_y()
+ elements[10] * point.get_z()
+ elements[14];
let w: f64 = elements[3] * point.get_x()
+ elements[7] * point.get_y()
+ elements[11] * point.get_z()
+ elements[15];
if w.abs() < EPSILON {
return Vector3D::new(x, y, z);
}
Vector3D::new(x / w, y / w, z / w)
}
}
impl Default for Quaternion {
fn default() -> Quaternion {
Quaternion::identity()
}
}
impl Default for Matrix4x4 {
fn default() -> Matrix4x4 {
Matrix4x4::identity()
}
}
impl Transform3D {
pub fn identity() -> Transform3D {
Transform3D::new(
Vector3D::zero(),
Quaternion::identity(),
Vector3D::new(1.0, 1.0, 1.0),
)
}
pub fn translate(&mut self, offset: Vector3D) {
self.set_position(self.get_position() + offset);
}
pub fn rotate(&mut self, rotation: Quaternion) {
self.set_rotation(rotation * self.get_rotation());
}
pub fn scale_by(&mut self, factors: Vector3D) {
let mut scale: Vector3D = self.get_scale();
scale.set_x(scale.get_x() * factors.get_x());
scale.set_y(scale.get_y() * factors.get_y());
scale.set_z(scale.get_z() * factors.get_z());
self.set_scale(scale);
}
pub fn apply_to_point(&self, point: Vector3D) -> Vector3D {
let scale: Vector3D = self.get_scale();
let scaled: Vector3D = Vector3D::new(
point.get_x() * scale.get_x(),
point.get_y() * scale.get_y(),
point.get_z() * scale.get_z(),
);
scaled.rotated_by(self.get_rotation()) + self.get_position()
}
pub fn to_matrix(&self) -> Matrix4x4 {
let translation: Matrix4x4 = Matrix4x4::translation(self.get_position());
let rotation: Matrix4x4 = Matrix4x4::rotation(self.get_rotation());
let scaling: Matrix4x4 = Matrix4x4::scaling(self.get_scale());
translation.multiply(rotation).multiply(scaling)
}
}
impl Default for Transform3D {
fn default() -> Transform3D {
Transform3D::identity()
}
}
impl AABB3D {
pub fn from_center(center: Vector3D, width: f64, height: f64, depth: f64) -> AABB3D {
AABB3D::new(
Vector3D::new(
center.get_x() - width * 0.5,
center.get_y() - height * 0.5,
center.get_z() - depth * 0.5,
),
Vector3D::new(
center.get_x() + width * 0.5,
center.get_y() + height * 0.5,
center.get_z() + depth * 0.5,
),
)
}
pub fn center(&self) -> Vector3D {
Vector3D::new(
(self.get_min().get_x() + self.get_max().get_x()) * 0.5,
(self.get_min().get_y() + self.get_max().get_y()) * 0.5,
(self.get_min().get_z() + self.get_max().get_z()) * 0.5,
)
}
pub fn size(&self) -> Vector3D {
Vector3D::new(
self.get_max().get_x() - self.get_min().get_x(),
self.get_max().get_y() - self.get_min().get_y(),
self.get_max().get_z() - self.get_min().get_z(),
)
}
pub fn contains(&self, point: Vector3D) -> bool {
point.get_x() >= self.get_min().get_x()
&& point.get_x() <= self.get_max().get_x()
&& point.get_y() >= self.get_min().get_y()
&& point.get_y() <= self.get_max().get_y()
&& point.get_z() >= self.get_min().get_z()
&& point.get_z() <= self.get_max().get_z()
}
pub fn intersects(&self, other: AABB3D) -> bool {
self.get_min().get_x() <= other.get_max().get_x()
&& self.get_max().get_x() >= other.get_min().get_x()
&& self.get_min().get_y() <= other.get_max().get_y()
&& self.get_max().get_y() >= other.get_min().get_y()
&& self.get_min().get_z() <= other.get_max().get_z()
&& self.get_max().get_z() >= other.get_min().get_z()
}
}
impl Sphere {
pub fn contains(&self, point: Vector3D) -> bool {
self.get_center().distance_squared_to(point) <= self.get_radius() * self.get_radius()
}
pub fn intersects(&self, other: Sphere) -> bool {
let distance_sq: f64 = self.get_center().distance_squared_to(other.get_center());
let radius_sum: f64 = self.get_radius() + other.get_radius();
distance_sq <= radius_sum * radius_sum
}
pub fn volume(&self) -> f64 {
(4.0 / 3.0) * PI * self.get_radius() * self.get_radius() * self.get_radius()
}
pub fn surface_area(&self) -> f64 {
4.0 * PI * self.get_radius() * self.get_radius()
}
}
impl Plane {
pub fn from_normal_and_point(normal: Vector3D, point: Vector3D) -> Plane {
let normalized_normal: Vector3D = normal.normalized();
Plane::new(normalized_normal, -normalized_normal.dot(point))
}
pub fn distance_to_point(&self, point: Vector3D) -> f64 {
self.get_normal().dot(point) + self.get_distance()
}
pub fn normalize(&mut self) {
let mut normal: Vector3D = self.get_normal();
let mag: f64 = normal.magnitude();
if mag < EPSILON {
return;
}
normal.set_x(normal.get_x() / mag);
normal.set_y(normal.get_y() / mag);
normal.set_z(normal.get_z() / mag);
self.set_normal(normal);
self.set_distance(self.get_distance() / mag);
}
}
impl Ray3D {
pub fn point_at(&self, t: f64) -> Vector3D {
self.get_origin() + self.get_direction().scaled(t)
}
pub fn intersect_sphere(&self, sphere: Sphere) -> Option<f64> {
let oc: Vector3D = self.get_origin() - sphere.get_center();
let direction: Vector3D = self.get_direction();
let a: f64 = direction.dot(direction);
let b: f64 = 2.0 * oc.dot(direction);
let c: f64 = oc.dot(oc) - sphere.get_radius() * sphere.get_radius();
let discriminant: f64 = b * b - 4.0 * a * c;
if discriminant < 0.0 {
return None;
}
let sqrt_d: f64 = discriminant.sqrt();
let t1: f64 = (-b - sqrt_d) / (2.0 * a);
if t1 >= 0.0 {
return Some(t1);
}
let t2: f64 = (-b + sqrt_d) / (2.0 * a);
if t2 >= 0.0 {
return Some(t2);
}
None
}
pub fn intersect_plane(&self, plane: Plane) -> Option<f64> {
let direction: Vector3D = self.get_direction();
let normal: Vector3D = plane.get_normal();
let denom: f64 = direction.dot(normal);
if denom.abs() < EPSILON {
return None;
}
let t: f64 = -(normal.dot(self.get_origin()) + plane.get_distance()) / denom;
if t >= 0.0 { Some(t) } else { None }
}
pub fn intersect_aabb(&self, aabb: AABB3D) -> Option<f64> {
let mut t_min: f64 = f64::MIN;
let mut t_max: f64 = f64::MAX;
let direction: Vector3D = self.get_direction();
let origin: Vector3D = self.get_origin();
let aabb_min: Vector3D = aabb.get_min();
let aabb_max: Vector3D = aabb.get_max();
for axis in 0..3usize {
let (dir_component, origin_component, min_component, max_component) = match axis {
0 => (
direction.get_x(),
origin.get_x(),
aabb_min.get_x(),
aabb_max.get_x(),
),
1 => (
direction.get_y(),
origin.get_y(),
aabb_min.get_y(),
aabb_max.get_y(),
),
_ => (
direction.get_z(),
origin.get_z(),
aabb_min.get_z(),
aabb_max.get_z(),
),
};
if dir_component.abs() < EPSILON {
if origin_component < min_component || origin_component > max_component {
return None;
}
} else {
let inv_dir: f64 = 1.0 / dir_component;
let t1: f64 = (min_component - origin_component) * inv_dir;
let t2: f64 = (max_component - origin_component) * inv_dir;
let t_near: f64 = t1.min(t2);
let t_far: f64 = t1.max(t2);
t_min = t_min.max(t_near);
t_max = t_max.min(t_far);
if t_min > t_max {
return None;
}
}
}
if t_min >= 0.0 {
Some(t_min)
} else if t_max >= 0.0 {
Some(t_max)
} else {
None
}
}
}