use bevy_ecs::prelude::*;
use glam::{Mat4, Vec3};
use crate::renderer::assets::{MeshHandle, MaterialHandle};
#[derive(Debug, Clone, Copy, Component)]
pub struct Aabb {
pub min: Vec3,
pub max: Vec3,
}
impl Aabb {
pub fn from_min_max(min: Vec3, max: Vec3) -> Self {
Self { min, max }
}
pub fn from_points(points: &[Vec3]) -> Self {
let mut min = Vec3::splat(f32::MAX);
let mut max = Vec3::splat(f32::MIN);
for &p in points {
min = min.min(p);
max = max.max(p);
}
Self { min, max }
}
pub fn center(&self) -> Vec3 {
(self.min + self.max) * 0.5
}
pub fn half_extents(&self) -> Vec3 {
(self.max - self.min) * 0.5
}
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 translated(&self, offset: Vec3) -> Aabb {
Aabb {
min: self.min + offset,
max: self.max + offset,
}
}
}
impl Default for Aabb {
fn default() -> Self {
Self {
min: Vec3::splat(-0.5),
max: Vec3::splat(0.5),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Frustum {
pub planes: [glam::Vec4; 6],
}
impl Frustum {
pub fn from_view_proj(vp: &Mat4) -> Self {
let m = vp.to_cols_array_2d();
let row = |r: usize| -> glam::Vec4 {
glam::Vec4::new(m[0][r], m[1][r], m[2][r], m[3][r])
};
let r0 = row(0);
let r1 = row(1);
let r2 = row(2);
let r3 = row(3);
let mut planes = [
r3 + r0, r3 - r0, r3 + r1, r3 - r1, r2, r3 - r2, ];
for p in &mut planes {
let len = glam::Vec3::new(p.x, p.y, p.z).length();
if len > 0.0 {
*p /= len;
}
}
Self { planes }
}
pub fn intersects_aabb(&self, center: Vec3, half_extents: Vec3) -> bool {
for plane in &self.planes {
let normal = glam::Vec3::new(plane.x, plane.y, plane.z);
let d = plane.w;
let r = half_extents.x * normal.x.abs()
+ half_extents.y * normal.y.abs()
+ half_extents.z * normal.z.abs();
let dist = normal.dot(center) + d;
if dist < -r {
return false; }
}
true
}
}
#[derive(Resource)]
pub struct ActiveCamera {
pub view_proj: Mat4,
pub camera_pos: Vec3,
}
impl Default for ActiveCamera {
fn default() -> Self {
Self {
view_proj: Mat4::IDENTITY,
camera_pos: Vec3::ZERO,
}
}
}
#[derive(Debug, Clone)]
pub struct DirectionalLight {
pub direction: Vec3,
pub color: Vec3,
pub intensity: f32,
}
impl Default for DirectionalLight {
fn default() -> Self {
Self {
direction: Vec3::new(-0.5, -0.8, 0.3).normalize(),
color: Vec3::new(1.0, 0.95, 0.9),
intensity: 5.0,
}
}
}
#[derive(Debug, Clone)]
pub struct PointLight {
pub position: Vec3,
pub color: Vec3,
pub intensity: f32,
pub range: f32,
}
impl Default for PointLight {
fn default() -> Self {
Self {
position: Vec3::new(0.0, 3.0, 0.0),
color: Vec3::ONE,
intensity: 5.0,
range: 10.0,
}
}
}
#[derive(Debug, Clone)]
pub struct SpotLight {
pub position: Vec3,
pub direction: Vec3,
pub color: Vec3,
pub intensity: f32,
pub range: f32,
pub inner_cone_angle: f32,
pub outer_cone_angle: f32,
}
impl Default for SpotLight {
fn default() -> Self {
Self {
position: Vec3::new(0.0, 3.0, 0.0),
direction: Vec3::new(0.0, -1.0, 0.0),
color: Vec3::ONE,
intensity: 10.0,
range: 15.0,
inner_cone_angle: 0.35, outer_cone_angle: 0.52, }
}
}
#[derive(Resource)]
pub struct SceneLights {
pub directional: DirectionalLight,
pub point_lights: Vec<PointLight>,
pub spot_lights: Vec<SpotLight>,
}
impl Default for SceneLights {
fn default() -> Self {
Self {
directional: DirectionalLight::default(),
point_lights: Vec::new(),
spot_lights: Vec::new(),
}
}
}
#[derive(Debug, Clone, Component)]
pub struct MaterialParams {
pub metallic: f32,
pub roughness: f32,
pub normal_scale: f32,
pub emissive_factor: [f32; 3],
}
impl Default for MaterialParams {
fn default() -> Self {
Self {
metallic: 0.0,
roughness: 0.5,
normal_scale: 1.0,
emissive_factor: [0.0; 3],
}
}
}
pub struct DrawCommand {
pub mesh: MeshHandle,
pub material: MaterialHandle,
pub model_matrix: Mat4,
pub metallic: f32,
pub roughness: f32,
pub normal_scale: f32,
pub emissive_factor: [f32; 3],
}
#[derive(Resource, Default)]
pub struct DrawCommandList {
pub commands: Vec<DrawCommand>,
}
impl DrawCommandList {
pub fn clear(&mut self) {
self.commands.clear();
}
pub fn push(&mut self, cmd: DrawCommand) {
self.commands.push(cmd);
}
pub fn sort_for_batching(&mut self) {
self.commands.sort_by(|a, b| {
a.material.index().cmp(&b.material.index())
.then(a.mesh.index().cmp(&b.mesh.index()))
});
}
}
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct InstanceData {
pub model: [[f32; 4]; 4], pub normal_matrix: [[f32; 4]; 4], }
impl Default for InstanceData {
fn default() -> Self {
Self {
model: glam::Mat4::IDENTITY.to_cols_array_2d(),
normal_matrix: glam::Mat4::IDENTITY.to_cols_array_2d(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_directional_light_default() {
let light = DirectionalLight::default();
assert!(light.direction.length() > 0.99);
assert!(light.intensity > 0.0);
}
#[test]
fn test_scene_lights_default() {
let lights = SceneLights::default();
assert!(lights.directional.intensity > 0.0);
}
#[test]
fn test_material_params_default() {
let params = MaterialParams::default();
assert_eq!(params.metallic, 0.0);
assert_eq!(params.roughness, 0.5);
assert_eq!(params.normal_scale, 1.0);
}
#[test]
fn test_aabb_from_points() {
let aabb = Aabb::from_points(&[
Vec3::new(-1.0, -2.0, -3.0),
Vec3::new(4.0, 5.0, 6.0),
]);
assert_eq!(aabb.min, Vec3::new(-1.0, -2.0, -3.0));
assert_eq!(aabb.max, Vec3::new(4.0, 5.0, 6.0));
assert_eq!(aabb.center(), Vec3::new(1.5, 1.5, 1.5));
assert_eq!(aabb.half_extents(), Vec3::new(2.5, 3.5, 4.5));
}
#[test]
fn test_frustum_contains_origin() {
let view = Mat4::look_at_lh(Vec3::new(0.0, 0.0, -5.0), Vec3::ZERO, Vec3::Y);
let proj = Mat4::perspective_lh(60.0_f32.to_radians(), 1.0, 0.1, 100.0);
let frustum = Frustum::from_view_proj(&(proj * view));
assert!(frustum.intersects_aabb(Vec3::ZERO, Vec3::splat(0.5)));
assert!(!frustum.intersects_aabb(Vec3::new(0.0, 0.0, -100.0), Vec3::splat(0.5)));
}
}