Files
endorphin-tessera-gui/src/render/scene3d/math.rs
T

255 lines
7.7 KiB
Rust

use serde::{Deserialize, Serialize};
use std::ops::{Add, Mul, Sub};
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3 {
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn dot(self, b: Self) -> f32 {
self.x * b.x + self.y * b.y + self.z * b.z
}
pub fn cross(self, b: Self) -> Self {
Self::new(
self.y * b.z - self.z * b.y,
self.z * b.x - self.x * b.z,
self.x * b.y - self.y * b.x,
)
}
pub fn length(self) -> f32 {
self.dot(self).sqrt()
}
pub fn normalized(self) -> Option<Self> {
let length = self.length();
(length.is_finite() && length > 1e-6).then(|| self * (1.0 / length))
}
pub fn valid(self) -> bool {
[self.x, self.y, self.z]
.iter()
.all(|v| v.is_finite() && v.abs() <= 1_000_000.0)
}
}
impl Add for Vec3 {
type Output = Self;
fn add(self, b: Self) -> Self {
Self::new(self.x + b.x, self.y + b.y, self.z + b.z)
}
}
impl Sub for Vec3 {
type Output = Self;
fn sub(self, b: Self) -> Self {
Self::new(self.x - b.x, self.y - b.y, self.z - b.z)
}
}
impl Mul<f32> for Vec3 {
type Output = Self;
fn mul(self, b: f32) -> Self {
Self::new(self.x * b, self.y * b, self.z * b)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Matrix(pub [f32; 16]);
impl Default for Matrix {
fn default() -> Self {
Self([
1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
])
}
}
impl Matrix {
pub fn inverse(self) -> Option<Self> {
let m = self.0;
let determinant = m[0] * (m[5] * m[10] - m[6] * m[9]) - m[1] * (m[4] * m[10] - m[6] * m[8])
+ m[2] * (m[4] * m[9] - m[5] * m[8]);
if !determinant.is_finite() || determinant.abs() < 1e-9 {
return None;
}
let d = 1. / determinant;
let mut out = Self([
(m[5] * m[10] - m[6] * m[9]) * d,
(m[2] * m[9] - m[1] * m[10]) * d,
(m[1] * m[6] - m[2] * m[5]) * d,
0.,
(m[6] * m[8] - m[4] * m[10]) * d,
(m[0] * m[10] - m[2] * m[8]) * d,
(m[2] * m[4] - m[0] * m[6]) * d,
0.,
(m[4] * m[9] - m[5] * m[8]) * d,
(m[1] * m[8] - m[0] * m[9]) * d,
(m[0] * m[5] - m[1] * m[4]) * d,
0.,
0.,
0.,
0.,
1.,
]);
let translation = out.point(Vec3::new(m[3], m[7], m[11])) * -1.;
out.0[3] = translation.x;
out.0[7] = translation.y;
out.0[11] = translation.z;
Some(out)
}
pub fn compose(self, local: Self) -> Self {
let mut out = [0.; 16];
for row in 0..4 {
for col in 0..4 {
out[row * 4 + col] = (0..4)
.map(|k| self.0[row * 4 + k] * local.0[k * 4 + col])
.sum();
}
}
Self(out)
}
pub fn point(self, p: Vec3) -> Vec3 {
Vec3::new(
self.0[0] * p.x + self.0[1] * p.y + self.0[2] * p.z + self.0[3],
self.0[4] * p.x + self.0[5] * p.y + self.0[6] * p.z + self.0[7],
self.0[8] * p.x + self.0[9] * p.y + self.0[10] * p.z + self.0[11],
)
}
pub fn trs(position: Vec3, rotation: Vec3, scale: Vec3) -> Self {
let (sx, cx) = rotation.x.to_radians().sin_cos();
let (sy, cy) = rotation.y.to_radians().sin_cos();
let (sz, cz) = rotation.z.to_radians().sin_cos();
// Local X, then Y, then Z rotation; scale first, translation last.
Self([
cz * cy * scale.x,
(cz * sy * sx - sz * cx) * scale.y,
(cz * sy * cx + sz * sx) * scale.z,
position.x,
sz * cy * scale.x,
(sz * sy * sx + cz * cx) * scale.y,
(sz * sy * cx - cz * sx) * scale.z,
position.y,
-sy * scale.x,
cy * sx * scale.y,
cy * cx * scale.z,
position.z,
0.,
0.,
0.,
1.,
])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum Projection {
Perspective { fov: f32 },
Orthographic { height: f32 },
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Camera {
pub position: Vec3,
pub target: Vec3,
pub up: Vec3,
pub near: f32,
pub far: f32,
pub projection: Projection,
}
impl Default for Camera {
fn default() -> Self {
Self {
position: Vec3::new(0., 0., 5.),
target: Vec3::default(),
up: Vec3::new(0., 1., 0.),
near: 0.1,
far: 1000.,
projection: Projection::Perspective { fov: 50. },
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Basis {
pub right: Vec3,
pub up: Vec3,
pub forward: Vec3,
}
impl Camera {
pub fn basis(self) -> Option<Basis> {
let forward = (self.target - self.position).normalized()?;
let right = forward.cross(self.up).normalized()?;
let up = right.cross(forward).normalized()?;
Some(Basis { right, up, forward })
}
pub fn valid(self) -> bool {
self.position.valid()
&& self.target.valid()
&& self.up.valid()
&& self.basis().is_some()
&& self.near.is_finite()
&& self.far.is_finite()
&& self.near > 0.
&& self.far > self.near
&& self.far <= 1_000_000.
&& match self.projection {
Projection::Perspective { fov } => fov.is_finite() && (1.0..=175.0).contains(&fov),
Projection::Orthographic { height } => {
height.is_finite() && height > 0. && height <= 1_000_000.
}
}
}
pub fn view(self, basis: Basis, p: Vec3) -> Vec3 {
let p = p - self.position;
Vec3::new(p.dot(basis.right), p.dot(basis.up), p.dot(basis.forward))
}
pub fn ray(self, u: f32, v: f32, aspect: f32) -> Option<Ray> {
let basis = self.basis()?;
let (x, y) = (u * 2. - 1., 1. - v * 2.);
match self.projection {
Projection::Perspective { fov } => {
let half = (fov.to_radians() / 2.).tan();
Some(Ray {
origin: self.position,
direction: (basis.forward
+ basis.right * (x * half * aspect)
+ basis.up * (y * half))
.normalized()?,
})
}
Projection::Orthographic { height } => Some(Ray {
origin: self.position
+ basis.right * (x * height * aspect / 2.)
+ basis.up * (y * height / 2.),
direction: basis.forward,
}),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Ray {
pub origin: Vec3,
pub direction: Vec3,
}
impl Ray {
pub fn triangle(self, a: Vec3, b: Vec3, c: Vec3) -> Option<(f32, [f32; 3])> {
let ab = b - a;
let ac = c - a;
let p = self.direction.cross(ac);
let determinant = ab.dot(p);
if determinant.abs() < 1e-7 {
return None;
}
let inverse = 1.0 / determinant;
let t = self.origin - a;
let u = t.dot(p) * inverse;
if !(0.0..=1.0).contains(&u) {
return None;
}
let q = t.cross(ab);
let v = self.direction.dot(q) * inverse;
if v < 0. || u + v > 1. {
return None;
}
let distance = ac.dot(q) * inverse;
(distance >= 0.).then_some((distance, [1. - u - v, u, v]))
}
}