use crate::engine::Engine;
use super::mesh::Mesh3D;
use super::vertex::Mesh3DVertex;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DrawMode {
#[default]
Solid,
Wireframe,
Points,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BillboardMode {
#[default]
Spherical,
Cylindrical,
}
#[derive(Debug, Clone, Copy)]
pub struct Billboard {
pub position: glam::Vec3,
pub size: glam::Vec2,
pub mode: BillboardMode,
}
impl Billboard {
pub fn new(position: glam::Vec3, size: glam::Vec2) -> Self {
Self {
position,
size,
mode: BillboardMode::Spherical,
}
}
pub fn cylindrical(position: glam::Vec3, size: glam::Vec2) -> Self {
Self {
position,
size,
mode: BillboardMode::Cylindrical,
}
}
pub fn model_matrix(&self, camera_pos: glam::Vec3, camera_up: glam::Vec3) -> glam::Mat4 {
match self.mode {
BillboardMode::Spherical => self.spherical_matrix(camera_pos, camera_up),
BillboardMode::Cylindrical => self.cylindrical_matrix(camera_pos),
}
}
fn spherical_matrix(&self, camera_pos: glam::Vec3, camera_up: glam::Vec3) -> glam::Mat4 {
let look = (camera_pos - self.position).normalize_or_zero();
if look == glam::Vec3::ZERO {
return glam::Mat4::from_scale_rotation_translation(
glam::Vec3::new(self.size.x, self.size.y, 1.0),
glam::Quat::IDENTITY,
self.position,
);
}
let right = camera_up.cross(look).normalize_or_zero();
let right = if right == glam::Vec3::ZERO {
glam::Vec3::X
} else {
right
};
let up = look.cross(right);
let rotation = glam::Mat3::from_cols(right, up, look);
glam::Mat4::from_scale_rotation_translation(
glam::Vec3::new(self.size.x, self.size.y, 1.0),
glam::Quat::from_mat3(&rotation),
self.position,
)
}
fn cylindrical_matrix(&self, camera_pos: glam::Vec3) -> glam::Mat4 {
let mut look = camera_pos - self.position;
look.y = 0.0;
let look = look.normalize_or_zero();
if look == glam::Vec3::ZERO {
return glam::Mat4::from_scale_rotation_translation(
glam::Vec3::new(self.size.x, self.size.y, 1.0),
glam::Quat::IDENTITY,
self.position,
);
}
let angle = look.z.atan2(look.x) - std::f32::consts::FRAC_PI_2;
glam::Mat4::from_scale_rotation_translation(
glam::Vec3::new(self.size.x, self.size.y, 1.0),
glam::Quat::from_rotation_y(angle),
self.position,
)
}
pub fn create_quad(g: &Engine) -> Mesh3D {
let vertices = vec![
Mesh3DVertex::new(
glam::Vec3::new(-0.5, -0.5, 0.0),
glam::Vec3::Z,
glam::Vec2::new(0.0, 1.0),
),
Mesh3DVertex::new(
glam::Vec3::new(0.5, -0.5, 0.0),
glam::Vec3::Z,
glam::Vec2::new(1.0, 1.0),
),
Mesh3DVertex::new(
glam::Vec3::new(0.5, 0.5, 0.0),
glam::Vec3::Z,
glam::Vec2::new(1.0, 0.0),
),
Mesh3DVertex::new(
glam::Vec3::new(-0.5, 0.5, 0.0),
glam::Vec3::Z,
glam::Vec2::new(0.0, 0.0),
),
];
let indices = vec![0, 1, 2, 2, 3, 0];
Mesh3D::new(g, &vertices, &indices)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AABB {
pub min: glam::Vec3,
pub max: glam::Vec3,
}
impl AABB {
pub fn new(min: glam::Vec3, max: glam::Vec3) -> Self {
Self { min, max }
}
pub fn from_center_half_extents(center: glam::Vec3, half_extents: glam::Vec3) -> Self {
Self {
min: center - half_extents,
max: center + half_extents,
}
}
pub fn from_points(points: &[glam::Vec3]) -> Option<Self> {
if points.is_empty() {
return None;
}
let mut min = points[0];
let mut max = points[0];
for &point in &points[1..] {
min = min.min(point);
max = max.max(point);
}
Some(Self { min, max })
}
pub fn center(&self) -> glam::Vec3 {
(self.min + self.max) * 0.5
}
pub fn half_extents(&self) -> glam::Vec3 {
(self.max - self.min) * 0.5
}
pub fn size(&self) -> glam::Vec3 {
self.max - self.min
}
pub fn contains_point(&self, point: glam::Vec3) -> bool {
point.x >= self.min.x
&& point.x <= self.max.x
&& point.y >= self.min.y
&& point.y <= self.max.y
&& point.z >= self.min.z
&& point.z <= self.max.z
}
pub fn intersects(&self, other: &AABB) -> bool {
self.min.x <= other.max.x
&& self.max.x >= other.min.x
&& self.min.y <= other.max.y
&& self.max.y >= other.min.y
&& self.min.z <= other.max.z
&& self.max.z >= other.min.z
}
pub fn intersection(&self, other: &AABB) -> Option<AABB> {
if !self.intersects(other) {
return None;
}
Some(AABB {
min: self.min.max(other.min),
max: self.max.min(other.max),
})
}
pub fn merge(&self, other: &AABB) -> AABB {
AABB {
min: self.min.min(other.min),
max: self.max.max(other.max),
}
}
pub fn expand_to_include(&mut self, point: glam::Vec3) {
self.min = self.min.min(point);
self.max = self.max.max(point);
}
pub fn corners(&self) -> [glam::Vec3; 8] {
[
glam::Vec3::new(self.min.x, self.min.y, self.min.z),
glam::Vec3::new(self.max.x, self.min.y, self.min.z),
glam::Vec3::new(self.min.x, self.max.y, self.min.z),
glam::Vec3::new(self.max.x, self.max.y, self.min.z),
glam::Vec3::new(self.min.x, self.min.y, self.max.z),
glam::Vec3::new(self.max.x, self.min.y, self.max.z),
glam::Vec3::new(self.min.x, self.max.y, self.max.z),
glam::Vec3::new(self.max.x, self.max.y, self.max.z),
]
}
pub fn ray_intersection(&self, ray: &Ray3D) -> Option<f32> {
ray.intersect_aabb(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Ray3D {
pub origin: glam::Vec3,
pub direction: glam::Vec3,
}
impl Ray3D {
pub fn new(origin: glam::Vec3, direction: glam::Vec3) -> Self {
Self {
origin,
direction: direction.normalize_or_zero(),
}
}
pub fn from_points(origin: glam::Vec3, target: glam::Vec3) -> Self {
Self::new(origin, target - origin)
}
pub fn point_at(&self, t: f32) -> glam::Vec3 {
self.origin + self.direction * t
}
pub fn intersect_aabb(&self, aabb: &AABB) -> Option<f32> {
let inv_dir = glam::Vec3::new(
if self.direction.x != 0.0 {
1.0 / self.direction.x
} else {
f32::INFINITY
},
if self.direction.y != 0.0 {
1.0 / self.direction.y
} else {
f32::INFINITY
},
if self.direction.z != 0.0 {
1.0 / self.direction.z
} else {
f32::INFINITY
},
);
let t1 = (aabb.min.x - self.origin.x) * inv_dir.x;
let t2 = (aabb.max.x - self.origin.x) * inv_dir.x;
let t3 = (aabb.min.y - self.origin.y) * inv_dir.y;
let t4 = (aabb.max.y - self.origin.y) * inv_dir.y;
let t5 = (aabb.min.z - self.origin.z) * inv_dir.z;
let t6 = (aabb.max.z - self.origin.z) * inv_dir.z;
let tmin = t1.min(t2).max(t3.min(t4)).max(t5.min(t6));
let tmax = t1.max(t2).min(t3.max(t4)).min(t5.max(t6));
if tmax < 0.0 || tmin > tmax {
return None;
}
Some(if tmin < 0.0 { 0.0 } else { tmin })
}
pub fn intersect_sphere(&self, center: glam::Vec3, radius: f32) -> Option<f32> {
let oc = self.origin - center;
let a = self.direction.dot(self.direction);
let b = 2.0 * oc.dot(self.direction);
let c = oc.dot(oc) - radius * radius;
let discriminant = b * b - 4.0 * a * c;
if discriminant < 0.0 {
return None;
}
let sqrt_discriminant = discriminant.sqrt();
let t1 = (-b - sqrt_discriminant) / (2.0 * a);
let t2 = (-b + sqrt_discriminant) / (2.0 * a);
if t1 >= 0.0 {
Some(t1)
} else if t2 >= 0.0 {
Some(t2)
} else {
None
}
}
pub fn intersect_plane(
&self,
plane_point: glam::Vec3,
plane_normal: glam::Vec3,
) -> Option<f32> {
let denom = plane_normal.dot(self.direction);
if denom.abs() < 1e-6 {
return None;
}
let t = (plane_point - self.origin).dot(plane_normal) / denom;
if t >= 0.0 { Some(t) } else { None }
}
pub fn from_screen(
screen_pos: glam::Vec2,
screen_size: glam::Vec2,
view_proj_inverse: glam::Mat4,
) -> Self {
let ndc_x = (screen_pos.x / screen_size.x) * 2.0 - 1.0;
let ndc_y = 1.0 - (screen_pos.y / screen_size.y) * 2.0;
let near_ndc = glam::Vec4::new(ndc_x, ndc_y, 0.0, 1.0);
let far_ndc = glam::Vec4::new(ndc_x, ndc_y, 1.0, 1.0);
let near_world = view_proj_inverse * near_ndc;
let far_world = view_proj_inverse * far_ndc;
let near_world = near_world.truncate() / near_world.w;
let far_world = far_world.truncate() / far_world.w;
Self::new(near_world, far_world - near_world)
}
}
#[cfg(test)]
mod tests {
use super::*;
use glam::Vec3;
#[test]
fn test_aabb_new() {
let aabb = AABB::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(10.0, 10.0, 10.0));
assert_eq!(aabb.min, Vec3::ZERO);
assert_eq!(aabb.max, Vec3::splat(10.0));
}
#[test]
fn test_aabb_from_center_half_extents() {
let aabb =
AABB::from_center_half_extents(Vec3::new(5.0, 5.0, 5.0), Vec3::new(5.0, 5.0, 5.0));
assert_eq!(aabb.min, Vec3::ZERO);
assert_eq!(aabb.max, Vec3::splat(10.0));
}
#[test]
fn test_aabb_from_points() {
let points = vec![
Vec3::new(1.0, 2.0, 3.0),
Vec3::new(5.0, 1.0, 2.0),
Vec3::new(2.0, 6.0, 1.0),
];
let aabb = AABB::from_points(&points).unwrap();
assert_eq!(aabb.min, Vec3::new(1.0, 1.0, 1.0));
assert_eq!(aabb.max, Vec3::new(5.0, 6.0, 3.0));
}
#[test]
fn test_aabb_from_points_empty() {
let aabb = AABB::from_points(&[]);
assert!(aabb.is_none());
}
#[test]
fn test_aabb_from_points_single() {
let aabb = AABB::from_points(&[Vec3::new(3.0, 4.0, 5.0)]).unwrap();
assert_eq!(aabb.min, Vec3::new(3.0, 4.0, 5.0));
assert_eq!(aabb.max, Vec3::new(3.0, 4.0, 5.0));
}
#[test]
fn test_aabb_center() {
let aabb = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
assert_eq!(aabb.center(), Vec3::splat(5.0));
}
#[test]
fn test_aabb_half_extents() {
let aabb = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
assert_eq!(aabb.half_extents(), Vec3::splat(5.0));
}
#[test]
fn test_aabb_size() {
let aabb = AABB::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(4.0, 6.0, 9.0));
assert_eq!(aabb.size(), Vec3::new(3.0, 4.0, 6.0));
}
#[test]
fn test_aabb_contains_point_inside() {
let aabb = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
assert!(aabb.contains_point(Vec3::splat(5.0)));
}
#[test]
fn test_aabb_contains_point_outside() {
let aabb = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
assert!(!aabb.contains_point(Vec3::splat(11.0)));
assert!(!aabb.contains_point(Vec3::new(-1.0, 5.0, 5.0)));
}
#[test]
fn test_aabb_contains_point_boundary() {
let aabb = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
assert!(aabb.contains_point(Vec3::ZERO)); assert!(aabb.contains_point(Vec3::splat(10.0))); assert!(aabb.contains_point(Vec3::new(10.0, 0.0, 0.0))); }
#[test]
fn test_aabb_intersects_overlapping() {
let a = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
let b = AABB::new(Vec3::splat(5.0), Vec3::splat(15.0));
assert!(a.intersects(&b));
assert!(b.intersects(&a));
}
#[test]
fn test_aabb_intersects_separate() {
let a = AABB::new(Vec3::ZERO, Vec3::splat(5.0));
let b = AABB::new(Vec3::splat(10.0), Vec3::splat(15.0));
assert!(!a.intersects(&b));
assert!(!b.intersects(&a));
}
#[test]
fn test_aabb_intersects_touching() {
let a = AABB::new(Vec3::ZERO, Vec3::splat(5.0));
let b = AABB::new(Vec3::new(5.0, 0.0, 0.0), Vec3::new(10.0, 5.0, 5.0));
assert!(a.intersects(&b)); }
#[test]
fn test_aabb_intersects_contained() {
let outer = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
let inner = AABB::new(Vec3::splat(2.0), Vec3::splat(8.0));
assert!(outer.intersects(&inner));
assert!(inner.intersects(&outer));
}
#[test]
fn test_aabb_intersection() {
let a = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
let b = AABB::new(Vec3::splat(5.0), Vec3::splat(15.0));
let intersection = a.intersection(&b).unwrap();
assert_eq!(intersection.min, Vec3::splat(5.0));
assert_eq!(intersection.max, Vec3::splat(10.0));
}
#[test]
fn test_aabb_intersection_none() {
let a = AABB::new(Vec3::ZERO, Vec3::splat(5.0));
let b = AABB::new(Vec3::splat(10.0), Vec3::splat(15.0));
assert!(a.intersection(&b).is_none());
}
#[test]
fn test_aabb_merge() {
let a = AABB::new(Vec3::ZERO, Vec3::splat(5.0));
let b = AABB::new(Vec3::splat(3.0), Vec3::splat(10.0));
let merged = a.merge(&b);
assert_eq!(merged.min, Vec3::ZERO);
assert_eq!(merged.max, Vec3::splat(10.0));
}
#[test]
fn test_aabb_expand_to_include() {
let mut aabb = AABB::new(Vec3::ZERO, Vec3::splat(5.0));
aabb.expand_to_include(Vec3::new(10.0, -2.0, 3.0));
assert_eq!(aabb.min, Vec3::new(0.0, -2.0, 0.0));
assert_eq!(aabb.max, Vec3::new(10.0, 5.0, 5.0));
}
#[test]
fn test_aabb_corners() {
let aabb = AABB::new(Vec3::ZERO, Vec3::ONE);
let corners = aabb.corners();
assert_eq!(corners.len(), 8);
assert!(corners.contains(&Vec3::ZERO));
assert!(corners.contains(&Vec3::ONE));
assert!(corners.contains(&Vec3::new(1.0, 0.0, 0.0)));
assert!(corners.contains(&Vec3::new(0.0, 1.0, 1.0)));
}
#[test]
fn test_ray_new_normalizes_direction() {
let ray = Ray3D::new(Vec3::ZERO, Vec3::new(10.0, 0.0, 0.0));
assert!((ray.direction.length() - 1.0).abs() < 1e-6);
assert_eq!(ray.direction, Vec3::X);
}
#[test]
fn test_ray_from_points() {
let ray = Ray3D::from_points(Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0));
assert_eq!(ray.origin, Vec3::ZERO);
assert!((ray.direction - Vec3::X).length() < 1e-6);
}
#[test]
fn test_ray_point_at() {
let ray = Ray3D::new(Vec3::ZERO, Vec3::X);
assert_eq!(ray.point_at(0.0), Vec3::ZERO);
assert_eq!(ray.point_at(5.0), Vec3::new(5.0, 0.0, 0.0));
assert_eq!(ray.point_at(-2.0), Vec3::new(-2.0, 0.0, 0.0));
}
#[test]
fn test_ray_intersect_aabb_hit() {
let aabb = AABB::new(Vec3::splat(5.0), Vec3::splat(10.0));
let ray = Ray3D::new(Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0));
let t = ray.intersect_aabb(&aabb);
assert!(t.is_some());
let hit_point = ray.point_at(t.unwrap());
assert!(hit_point.x >= 4.9 && hit_point.x <= 5.1);
}
#[test]
fn test_ray_intersect_aabb_miss() {
let aabb = AABB::new(Vec3::splat(5.0), Vec3::splat(10.0));
let ray = Ray3D::new(Vec3::ZERO, Vec3::new(-1.0, 0.0, 0.0)); assert!(ray.intersect_aabb(&aabb).is_none());
}
#[test]
fn test_ray_intersect_aabb_inside() {
let aabb = AABB::new(Vec3::ZERO, Vec3::splat(10.0));
let ray = Ray3D::new(Vec3::splat(5.0), Vec3::X); let t = ray.intersect_aabb(&aabb);
assert!(t.is_some());
assert_eq!(t.unwrap(), 0.0); }
#[test]
fn test_ray_intersect_sphere_hit() {
let ray = Ray3D::new(Vec3::ZERO, Vec3::Z);
let t = ray.intersect_sphere(Vec3::new(0.0, 0.0, 5.0), 1.0);
assert!(t.is_some());
assert!((t.unwrap() - 4.0).abs() < 0.01);
}
#[test]
fn test_ray_intersect_sphere_miss() {
let ray = Ray3D::new(Vec3::ZERO, Vec3::X);
let t = ray.intersect_sphere(Vec3::new(0.0, 10.0, 0.0), 1.0); assert!(t.is_none());
}
#[test]
fn test_ray_intersect_sphere_inside() {
let ray = Ray3D::new(Vec3::ZERO, Vec3::X);
let t = ray.intersect_sphere(Vec3::ZERO, 5.0); assert!(t.is_some());
assert!((t.unwrap() - 5.0).abs() < 0.01); }
#[test]
fn test_ray_intersect_sphere_behind() {
let ray = Ray3D::new(Vec3::new(0.0, 0.0, 10.0), Vec3::Z);
let t = ray.intersect_sphere(Vec3::ZERO, 1.0); assert!(t.is_none());
}
#[test]
fn test_ray_intersect_plane_hit() {
let ray = Ray3D::new(Vec3::new(0.0, 5.0, 0.0), Vec3::new(0.0, -1.0, 0.0));
let t = ray.intersect_plane(Vec3::ZERO, Vec3::Y);
assert!(t.is_some());
assert!((t.unwrap() - 5.0).abs() < 0.01);
}
#[test]
fn test_ray_intersect_plane_parallel() {
let ray = Ray3D::new(Vec3::new(0.0, 5.0, 0.0), Vec3::X); let t = ray.intersect_plane(Vec3::ZERO, Vec3::Y);
assert!(t.is_none());
}
#[test]
fn test_ray_intersect_plane_behind() {
let ray = Ray3D::new(Vec3::new(0.0, 5.0, 0.0), Vec3::Y); let t = ray.intersect_plane(Vec3::ZERO, Vec3::Y);
assert!(t.is_none());
}
}