use anvilkit_ecs::prelude::*;
use anvilkit_ecs::physics::DeltaTime;
use anvilkit_input::prelude::InputState;
use log::info;
use crate::window::WindowConfig;
use crate::renderer::assets::{MeshHandle, MaterialHandle, RenderAssets};
use crate::renderer::draw::{ActiveCamera, Aabb, DrawCommand, DrawCommandList, Frustum, SceneLights, MaterialParams};
use crate::renderer::state::RenderState;
#[derive(Debug, Clone)]
pub struct RenderPlugin {
window_config: WindowConfig,
}
impl Default for RenderPlugin {
fn default() -> Self {
Self {
window_config: WindowConfig::default(),
}
}
}
impl RenderPlugin {
pub fn new() -> Self {
Self::default()
}
pub fn with_window_config(mut self, config: WindowConfig) -> Self {
self.window_config = config;
self
}
pub fn window_config(&self) -> &WindowConfig {
&self.window_config
}
}
impl Plugin for RenderPlugin {
fn build(&self, app: &mut App) {
info!("构建渲染插件");
app.insert_resource(RenderConfig {
window_config: self.window_config.clone(),
});
app.init_resource::<ActiveCamera>();
app.init_resource::<DrawCommandList>();
app.init_resource::<RenderAssets>();
app.init_resource::<SceneLights>();
app.insert_resource(InputState::new());
app.init_resource::<DeltaTime>();
app.add_systems(
AnvilKitSchedule::PostUpdate,
(
camera_system,
render_extract_system.after(camera_system),
),
);
info!("渲染插件构建完成");
}
}
#[derive(Debug, Clone, Resource)]
pub struct RenderConfig {
pub window_config: WindowConfig,
}
#[derive(Debug, Clone, Component)]
pub struct CameraComponent {
pub fov: f32,
pub near: f32,
pub far: f32,
pub is_active: bool,
pub aspect_ratio: f32,
}
impl Default for CameraComponent {
fn default() -> Self {
Self {
fov: 60.0,
near: 0.1,
far: 1000.0,
is_active: true,
aspect_ratio: 16.0 / 9.0,
}
}
}
fn camera_system(
camera_query: Query<(&CameraComponent, &Transform)>,
render_state: Option<Res<RenderState>>,
mut active_camera: ResMut<ActiveCamera>,
) {
let Some((camera, transform)) = camera_query.iter().find(|(c, _)| c.is_active) else {
return;
};
let aspect = if let Some(ref rs) = render_state {
let (w, h) = rs.surface_size;
w as f32 / h.max(1) as f32
} else {
camera.aspect_ratio
};
let eye = transform.translation;
let forward = transform.rotation * glam::Vec3::Z;
let target = eye + forward;
let view = glam::Mat4::look_at_lh(eye, target, glam::Vec3::Y);
let proj = glam::Mat4::perspective_lh(camera.fov.to_radians(), aspect, camera.near, camera.far);
active_camera.view_proj = proj * view;
active_camera.camera_pos = eye;
}
fn render_extract_system(
query: Query<(&MeshHandle, &MaterialHandle, &Transform, Option<&MaterialParams>, Option<&Aabb>)>,
active_camera: Res<ActiveCamera>,
mut draw_list: ResMut<DrawCommandList>,
) {
draw_list.clear();
let frustum = Frustum::from_view_proj(&active_camera.view_proj);
for (mesh, material, transform, mat_params, aabb) in query.iter() {
let model = transform.compute_matrix();
if let Some(aabb) = aabb {
let local_center = aabb.center();
let world_center = model.transform_point3(local_center);
let scale = transform.scale;
let world_half = aabb.half_extents() * scale;
if !frustum.intersects_aabb(world_center, world_half) {
continue; }
}
let default_params = MaterialParams::default();
let p = mat_params.unwrap_or(&default_params);
draw_list.push(DrawCommand {
mesh: *mesh,
material: *material,
model_matrix: model,
metallic: p.metallic,
roughness: p.roughness,
normal_scale: p.normal_scale,
emissive_factor: p.emissive_factor,
});
}
draw_list.sort_for_batching();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_plugin_creation() {
let plugin = RenderPlugin::new();
assert_eq!(plugin.window_config().title, "AnvilKit Application");
}
#[test]
fn test_render_plugin_with_config() {
let config = WindowConfig::new()
.with_title("Test Game")
.with_size(800, 600);
let plugin = RenderPlugin::new().with_window_config(config);
assert_eq!(plugin.window_config().title, "Test Game");
assert_eq!(plugin.window_config().width, 800);
assert_eq!(plugin.window_config().height, 600);
}
#[test]
fn test_camera_component_default() {
let camera = CameraComponent::default();
assert_eq!(camera.fov, 60.0);
assert_eq!(camera.near, 0.1);
assert_eq!(camera.far, 1000.0);
assert!(camera.is_active);
}
#[test]
fn test_render_plugin_default_config() {
let plugin = RenderPlugin::new();
assert_eq!(plugin.window_config().title, "AnvilKit Application");
assert_eq!(plugin.window_config().width, 1280);
}
#[test]
fn test_render_plugin_custom_window() {
let config = WindowConfig::new()
.with_title("Custom Window")
.with_size(800, 600);
let plugin = RenderPlugin::new().with_window_config(config);
assert_eq!(plugin.window_config().title, "Custom Window");
assert_eq!(plugin.window_config().width, 800);
assert_eq!(plugin.window_config().height, 600);
}
#[test]
fn test_render_config_default() {
let config = RenderConfig {
window_config: WindowConfig::default(),
};
assert!(config.window_config.vsync);
}
#[test]
fn test_camera_component_fields() {
let camera = CameraComponent::default();
assert!(camera.fov > 0.0);
assert!(camera.near > 0.0);
assert!(camera.far > camera.near);
}
}