Skip to main content

anvilkit_render/
plugin.rs

1//! # 渲染插件系统
2//!
3//! 提供与 AnvilKit ECS 系统的集成,实现渲染功能的插件化。
4
5use anvilkit_ecs::prelude::*;
6use anvilkit_ecs::physics::DeltaTime;
7use anvilkit_input::prelude::InputState;
8use log::info;
9
10use crate::window::WindowConfig;
11use crate::renderer::assets::{MeshHandle, MaterialHandle, RenderAssets};
12use crate::renderer::draw::{ActiveCamera, Aabb, DrawCommand, DrawCommandList, Frustum, SceneLights, MaterialParams};
13use crate::renderer::state::RenderState;
14
15/// 渲染插件
16///
17/// 将渲染系统集成到 AnvilKit ECS 应用中的插件。
18///
19/// # 示例
20///
21/// ```rust,no_run
22/// use anvilkit_render::prelude::*;
23/// use anvilkit_ecs::prelude::*;
24///
25/// // 创建应用并添加渲染插件
26/// let mut app = App::new();
27/// app.add_plugins(RenderPlugin::default())
28///    .run();
29/// ```
30#[derive(Debug, Clone)]
31pub struct RenderPlugin {
32    /// 窗口配置
33    window_config: WindowConfig,
34}
35
36impl Default for RenderPlugin {
37    fn default() -> Self {
38        Self {
39            window_config: WindowConfig::default(),
40        }
41    }
42}
43
44impl RenderPlugin {
45    /// 创建新的渲染插件
46    ///
47    /// # 示例
48    ///
49    /// ```rust
50    /// use anvilkit_render::plugin::RenderPlugin;
51    ///
52    /// let plugin = RenderPlugin::new();
53    /// ```
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// 设置窗口配置
59    ///
60    /// # 示例
61    ///
62    /// ```rust
63    /// use anvilkit_render::prelude::*;
64    ///
65    /// let config = WindowConfig::new()
66    ///     .with_title("我的游戏")
67    ///     .with_size(1920, 1080);
68    ///
69    /// let plugin = RenderPlugin::new().with_window_config(config);
70    /// ```
71    pub fn with_window_config(mut self, config: WindowConfig) -> Self {
72        self.window_config = config;
73        self
74    }
75
76    /// 获取窗口配置
77    ///
78    /// # 示例
79    ///
80    /// ```rust
81    /// use anvilkit_render::plugin::RenderPlugin;
82    ///
83    /// let plugin = RenderPlugin::new();
84    /// let config = plugin.window_config();
85    /// assert_eq!(config.title, "AnvilKit Application");
86    /// ```
87    pub fn window_config(&self) -> &WindowConfig {
88        &self.window_config
89    }
90}
91
92impl Plugin for RenderPlugin {
93    fn build(&self, app: &mut App) {
94        info!("构建渲染插件");
95
96        // 添加渲染配置资源
97        app.insert_resource(RenderConfig {
98            window_config: self.window_config.clone(),
99        });
100
101        // 注册 ECS 资源
102        app.init_resource::<ActiveCamera>();
103        app.init_resource::<DrawCommandList>();
104        app.init_resource::<RenderAssets>();
105        app.init_resource::<SceneLights>();
106        app.insert_resource(InputState::new());
107        app.init_resource::<DeltaTime>();
108
109        // 添加真实 ECS 渲染系统到 PostUpdate 阶段
110        app.add_systems(
111            AnvilKitSchedule::PostUpdate,
112            (
113                camera_system,
114                render_extract_system.after(camera_system),
115            ),
116        );
117
118        info!("渲染插件构建完成");
119    }
120}
121
122/// 渲染配置资源
123///
124/// 存储渲染系统的全局配置参数。
125///
126/// # 示例
127///
128/// ```rust
129/// use anvilkit_render::plugin::RenderConfig;
130/// use anvilkit_render::window::WindowConfig;
131///
132/// let config = RenderConfig {
133///     window_config: WindowConfig::default(),
134/// };
135/// ```
136#[derive(Debug, Clone, Resource)]
137pub struct RenderConfig {
138    /// 窗口配置
139    pub window_config: WindowConfig,
140}
141
142/// 相机组件
143///
144/// 定义渲染视角和投影参数的组件。
145///
146/// # 示例
147///
148/// ```rust
149/// use anvilkit_render::plugin::CameraComponent;
150/// use anvilkit_core::math::Transform;
151/// use glam::Vec3;
152///
153/// let camera = CameraComponent {
154///     fov: 60.0,
155///     near: 0.1,
156///     far: 1000.0,
157///     is_active: true,
158///     aspect_ratio: 16.0 / 9.0,
159/// };
160/// ```
161#[derive(Debug, Clone, Component)]
162pub struct CameraComponent {
163    /// 视野角度(度)
164    pub fov: f32,
165    /// 近裁剪面
166    pub near: f32,
167    /// 远裁剪面
168    pub far: f32,
169    /// 是否激活
170    pub is_active: bool,
171    /// 宽高比(由 RenderApp 在 resize 时更新,或用户手动设置)
172    pub aspect_ratio: f32,
173}
174
175impl Default for CameraComponent {
176    fn default() -> Self {
177        Self {
178            fov: 60.0,
179            near: 0.1,
180            far: 1000.0,
181            is_active: true,
182            aspect_ratio: 16.0 / 9.0,
183        }
184    }
185}
186
187// ---------------------------------------------------------------------------
188//  ECS 系统
189// ---------------------------------------------------------------------------
190
191/// 相机系统 (PostUpdate)
192///
193/// 查询 (CameraComponent, Transform) → 计算 view_proj → 写入 ActiveCamera
194fn camera_system(
195    camera_query: Query<(&CameraComponent, &Transform)>,
196    render_state: Option<Res<RenderState>>,
197    mut active_camera: ResMut<ActiveCamera>,
198) {
199    let Some((camera, transform)) = camera_query.iter().find(|(c, _)| c.is_active) else {
200        return;
201    };
202
203    // 如果 RenderState 存在,用实际 surface size 计算 aspect ratio
204    let aspect = if let Some(ref rs) = render_state {
205        let (w, h) = rs.surface_size;
206        w as f32 / h.max(1) as f32
207    } else {
208        camera.aspect_ratio
209    };
210
211    let eye = transform.translation;
212    // LH 坐标系中,前方是 +Z
213    let forward = transform.rotation * glam::Vec3::Z;
214    let target = eye + forward;
215
216    let view = glam::Mat4::look_at_lh(eye, target, glam::Vec3::Y);
217    let proj = glam::Mat4::perspective_lh(camera.fov.to_radians(), aspect, camera.near, camera.far);
218
219    active_camera.view_proj = proj * view;
220    active_camera.camera_pos = eye;
221}
222
223/// 渲染提取系统 (PostUpdate, after camera_system)
224///
225/// 查询 (MeshHandle, MaterialHandle, Transform, Option<MaterialParams>, Option<Aabb>)
226/// → 视锥体剔除 → 填充 DrawCommandList
227fn render_extract_system(
228    query: Query<(&MeshHandle, &MaterialHandle, &Transform, Option<&MaterialParams>, Option<&Aabb>)>,
229    active_camera: Res<ActiveCamera>,
230    mut draw_list: ResMut<DrawCommandList>,
231) {
232    draw_list.clear();
233
234    let frustum = Frustum::from_view_proj(&active_camera.view_proj);
235
236    for (mesh, material, transform, mat_params, aabb) in query.iter() {
237        let model = transform.compute_matrix();
238
239        // Frustum culling: if entity has an Aabb, test visibility
240        if let Some(aabb) = aabb {
241            // Transform AABB center to world space
242            let local_center = aabb.center();
243            let world_center = model.transform_point3(local_center);
244            // Scale half_extents by model matrix (approximate for uniform scale)
245            let scale = transform.scale;
246            let world_half = aabb.half_extents() * scale;
247
248            if !frustum.intersects_aabb(world_center, world_half) {
249                continue; // 不可见,跳过
250            }
251        }
252
253        let default_params = MaterialParams::default();
254        let p = mat_params.unwrap_or(&default_params);
255
256        draw_list.push(DrawCommand {
257            mesh: *mesh,
258            material: *material,
259            model_matrix: model,
260            metallic: p.metallic,
261            roughness: p.roughness,
262            normal_scale: p.normal_scale,
263            emissive_factor: p.emissive_factor,
264        });
265    }
266
267    // Sort for batching: group by material → mesh to minimize state changes
268    draw_list.sort_for_batching();
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274
275    #[test]
276    fn test_render_plugin_creation() {
277        let plugin = RenderPlugin::new();
278        assert_eq!(plugin.window_config().title, "AnvilKit Application");
279    }
280
281    #[test]
282    fn test_render_plugin_with_config() {
283        let config = WindowConfig::new()
284            .with_title("Test Game")
285            .with_size(800, 600);
286
287        let plugin = RenderPlugin::new().with_window_config(config);
288        assert_eq!(plugin.window_config().title, "Test Game");
289        assert_eq!(plugin.window_config().width, 800);
290        assert_eq!(plugin.window_config().height, 600);
291    }
292
293    #[test]
294    fn test_camera_component_default() {
295        let camera = CameraComponent::default();
296        assert_eq!(camera.fov, 60.0);
297        assert_eq!(camera.near, 0.1);
298        assert_eq!(camera.far, 1000.0);
299        assert!(camera.is_active);
300    }
301
302    #[test]
303    fn test_render_plugin_default_config() {
304        let plugin = RenderPlugin::new();
305        assert_eq!(plugin.window_config().title, "AnvilKit Application");
306        assert_eq!(plugin.window_config().width, 1280);
307    }
308
309    #[test]
310    fn test_render_plugin_custom_window() {
311        let config = WindowConfig::new()
312            .with_title("Custom Window")
313            .with_size(800, 600);
314        let plugin = RenderPlugin::new().with_window_config(config);
315
316        assert_eq!(plugin.window_config().title, "Custom Window");
317        assert_eq!(plugin.window_config().width, 800);
318        assert_eq!(plugin.window_config().height, 600);
319    }
320
321    #[test]
322    fn test_render_config_default() {
323        let config = RenderConfig {
324            window_config: WindowConfig::default(),
325        };
326        assert!(config.window_config.vsync);
327    }
328
329    #[test]
330    fn test_camera_component_fields() {
331        let camera = CameraComponent::default();
332        assert!(camera.fov > 0.0);
333        assert!(camera.near > 0.0);
334        assert!(camera.far > camera.near);
335    }
336}