anvilkit_render/renderer/
draw.rs1use bevy_ecs::prelude::*;
6use glam::{Mat4, Vec3};
7
8use crate::renderer::assets::{MeshHandle, MaterialHandle};
9
10#[derive(Debug, Clone, Copy, Component)]
25pub struct Aabb {
26 pub min: Vec3,
27 pub max: Vec3,
28}
29
30impl Aabb {
31 pub fn from_min_max(min: Vec3, max: Vec3) -> Self {
33 Self { min, max }
34 }
35
36 pub fn from_points(points: &[Vec3]) -> Self {
38 let mut min = Vec3::splat(f32::MAX);
39 let mut max = Vec3::splat(f32::MIN);
40 for &p in points {
41 min = min.min(p);
42 max = max.max(p);
43 }
44 Self { min, max }
45 }
46
47 pub fn center(&self) -> Vec3 {
49 (self.min + self.max) * 0.5
50 }
51
52 pub fn half_extents(&self) -> Vec3 {
54 (self.max - self.min) * 0.5
55 }
56
57 pub fn intersects(&self, other: &Aabb) -> bool {
59 self.min.x <= other.max.x && self.max.x >= other.min.x
60 && self.min.y <= other.max.y && self.max.y >= other.min.y
61 && self.min.z <= other.max.z && self.max.z >= other.min.z
62 }
63
64 pub fn translated(&self, offset: Vec3) -> Aabb {
66 Aabb {
67 min: self.min + offset,
68 max: self.max + offset,
69 }
70 }
71}
72
73impl Default for Aabb {
74 fn default() -> Self {
75 Self {
76 min: Vec3::splat(-0.5),
77 max: Vec3::splat(0.5),
78 }
79 }
80}
81
82#[derive(Debug, Clone, Copy)]
87pub struct Frustum {
88 pub planes: [glam::Vec4; 6],
90}
91
92impl Frustum {
93 pub fn from_view_proj(vp: &Mat4) -> Self {
97 let m = vp.to_cols_array_2d();
98 let row = |r: usize| -> glam::Vec4 {
99 glam::Vec4::new(m[0][r], m[1][r], m[2][r], m[3][r])
100 };
101 let r0 = row(0);
102 let r1 = row(1);
103 let r2 = row(2);
104 let r3 = row(3);
105
106 let mut planes = [
107 r3 + r0, r3 - r0, r3 + r1, r3 - r1, r2, r3 - r2, ];
114
115 for p in &mut planes {
117 let len = glam::Vec3::new(p.x, p.y, p.z).length();
118 if len > 0.0 {
119 *p /= len;
120 }
121 }
122
123 Self { planes }
124 }
125
126 pub fn intersects_aabb(&self, center: Vec3, half_extents: Vec3) -> bool {
131 for plane in &self.planes {
132 let normal = glam::Vec3::new(plane.x, plane.y, plane.z);
133 let d = plane.w;
134 let r = half_extents.x * normal.x.abs()
136 + half_extents.y * normal.y.abs()
137 + half_extents.z * normal.z.abs();
138 let dist = normal.dot(center) + d;
140 if dist < -r {
141 return false; }
143 }
144 true
145 }
146}
147
148#[derive(Resource)]
152pub struct ActiveCamera {
153 pub view_proj: Mat4,
154 pub camera_pos: Vec3,
155}
156
157impl Default for ActiveCamera {
158 fn default() -> Self {
159 Self {
160 view_proj: Mat4::IDENTITY,
161 camera_pos: Vec3::ZERO,
162 }
163 }
164}
165
166#[derive(Debug, Clone)]
168pub struct DirectionalLight {
169 pub direction: Vec3,
171 pub color: Vec3,
173 pub intensity: f32,
175}
176
177impl Default for DirectionalLight {
178 fn default() -> Self {
179 Self {
180 direction: Vec3::new(-0.5, -0.8, 0.3).normalize(),
181 color: Vec3::new(1.0, 0.95, 0.9),
182 intensity: 5.0,
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
189pub struct PointLight {
190 pub position: Vec3,
192 pub color: Vec3,
194 pub intensity: f32,
196 pub range: f32,
198}
199
200impl Default for PointLight {
201 fn default() -> Self {
202 Self {
203 position: Vec3::new(0.0, 3.0, 0.0),
204 color: Vec3::ONE,
205 intensity: 5.0,
206 range: 10.0,
207 }
208 }
209}
210
211#[derive(Debug, Clone)]
213pub struct SpotLight {
214 pub position: Vec3,
216 pub direction: Vec3,
218 pub color: Vec3,
220 pub intensity: f32,
222 pub range: f32,
224 pub inner_cone_angle: f32,
226 pub outer_cone_angle: f32,
228}
229
230impl Default for SpotLight {
231 fn default() -> Self {
232 Self {
233 position: Vec3::new(0.0, 3.0, 0.0),
234 direction: Vec3::new(0.0, -1.0, 0.0),
235 color: Vec3::ONE,
236 intensity: 10.0,
237 range: 15.0,
238 inner_cone_angle: 0.35, outer_cone_angle: 0.52, }
241 }
242}
243
244#[derive(Resource)]
248pub struct SceneLights {
249 pub directional: DirectionalLight,
250 pub point_lights: Vec<PointLight>,
251 pub spot_lights: Vec<SpotLight>,
252}
253
254impl Default for SceneLights {
255 fn default() -> Self {
256 Self {
257 directional: DirectionalLight::default(),
258 point_lights: Vec::new(),
259 spot_lights: Vec::new(),
260 }
261 }
262}
263
264#[derive(Debug, Clone, Component)]
269pub struct MaterialParams {
270 pub metallic: f32,
271 pub roughness: f32,
272 pub normal_scale: f32,
273 pub emissive_factor: [f32; 3],
274}
275
276impl Default for MaterialParams {
277 fn default() -> Self {
278 Self {
279 metallic: 0.0,
280 roughness: 0.5,
281 normal_scale: 1.0,
282 emissive_factor: [0.0; 3],
283 }
284 }
285}
286
287pub struct DrawCommand {
289 pub mesh: MeshHandle,
290 pub material: MaterialHandle,
291 pub model_matrix: Mat4,
292 pub metallic: f32,
293 pub roughness: f32,
294 pub normal_scale: f32,
295 pub emissive_factor: [f32; 3],
296}
297
298#[derive(Resource, Default)]
303pub struct DrawCommandList {
304 pub commands: Vec<DrawCommand>,
305}
306
307impl DrawCommandList {
308 pub fn clear(&mut self) {
309 self.commands.clear();
310 }
311
312 pub fn push(&mut self, cmd: DrawCommand) {
313 self.commands.push(cmd);
314 }
315
316 pub fn sort_for_batching(&mut self) {
321 self.commands.sort_by(|a, b| {
322 a.material.index().cmp(&b.material.index())
323 .then(a.mesh.index().cmp(&b.mesh.index()))
324 });
325 }
326}
327
328#[repr(C)]
333#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
334pub struct InstanceData {
335 pub model: [[f32; 4]; 4], pub normal_matrix: [[f32; 4]; 4], }
338
339impl Default for InstanceData {
340 fn default() -> Self {
341 Self {
342 model: glam::Mat4::IDENTITY.to_cols_array_2d(),
343 normal_matrix: glam::Mat4::IDENTITY.to_cols_array_2d(),
344 }
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn test_directional_light_default() {
354 let light = DirectionalLight::default();
355 assert!(light.direction.length() > 0.99);
356 assert!(light.intensity > 0.0);
357 }
358
359 #[test]
360 fn test_scene_lights_default() {
361 let lights = SceneLights::default();
362 assert!(lights.directional.intensity > 0.0);
363 }
364
365 #[test]
366 fn test_material_params_default() {
367 let params = MaterialParams::default();
368 assert_eq!(params.metallic, 0.0);
369 assert_eq!(params.roughness, 0.5);
370 assert_eq!(params.normal_scale, 1.0);
371 }
372
373 #[test]
374 fn test_aabb_from_points() {
375 let aabb = Aabb::from_points(&[
376 Vec3::new(-1.0, -2.0, -3.0),
377 Vec3::new(4.0, 5.0, 6.0),
378 ]);
379 assert_eq!(aabb.min, Vec3::new(-1.0, -2.0, -3.0));
380 assert_eq!(aabb.max, Vec3::new(4.0, 5.0, 6.0));
381 assert_eq!(aabb.center(), Vec3::new(1.5, 1.5, 1.5));
382 assert_eq!(aabb.half_extents(), Vec3::new(2.5, 3.5, 4.5));
383 }
384
385 #[test]
386 fn test_frustum_contains_origin() {
387 let view = Mat4::look_at_lh(Vec3::new(0.0, 0.0, -5.0), Vec3::ZERO, Vec3::Y);
389 let proj = Mat4::perspective_lh(60.0_f32.to_radians(), 1.0, 0.1, 100.0);
390 let frustum = Frustum::from_view_proj(&(proj * view));
391
392 assert!(frustum.intersects_aabb(Vec3::ZERO, Vec3::splat(0.5)));
394
395 assert!(!frustum.intersects_aabb(Vec3::new(0.0, 0.0, -100.0), Vec3::splat(0.5)));
397 }
398}