255 lines
7.7 KiB
Rust
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]))
|
|
}
|
|
}
|