Giant refactor for a better event-driven architecture

This commit is contained in:
reo 2025-07-21 23:52:32 +03:00
parent 341d531db3
commit 88a21040cd
22 changed files with 936 additions and 67 deletions

View file

@ -11,5 +11,6 @@ gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transf
glutin = { version = "0.32.3", default-features = false }
hecs = "0.10.5"
image = "0.25.6"
raidillon_core = { path = "../raidillon_core" }
raidillon_ecs = { path = "../raidillon_ecs" }
winit = "0.30"

View file

@ -3,9 +3,12 @@ pub mod model;
pub mod gltf_loader;
pub mod render;
pub mod ecs_renderer;
pub mod render_system;
pub mod window;
pub use camera::Camera;
pub use render::GliumRenderer;
pub use ecs_renderer::ECSRenderer;
pub use render_system::RenderSystem;
pub use raidillon_core::ModelId;
pub use window::{DisplayHandle, init_window as init_render_window};

View file

@ -51,7 +51,13 @@ impl Default for Material {
}
}
// Implement the Material trait from raidillon_core
impl raidillon_core::Material for Material {}
pub struct Model {
pub mesh: Mesh,
pub material: Material,
}
// Implement the Model trait from raidillon_core
impl raidillon_core::Model for Model {}

View file

@ -1,6 +1,7 @@
use crate::camera::Camera;
use raidillon_ecs::{ModelHandle, Transform};
use crate::model::{Model, Mesh};
use crate::model::{Model, Material, Mesh};
use raidillon_core::AssetManager;
use glium::texture::{RawImage2d, SrgbTexture2d};
use glium::{uniform, Program, Surface};
use glium::uniforms::{MinifySamplerFilter, MagnifySamplerFilter, SamplerWrapFunction};
@ -164,4 +165,94 @@ impl GliumRenderer {
pub fn display(&self) -> &glium::Display<WindowSurface> {
&self.display
}
pub fn render_into_with_assets<S: Surface>(&mut self, world: &World, assets: &AssetManager<Model, Material>, target: &mut S) {
target.clear_color_and_depth((0.1, 0.1, 0.15, 1.0), 1.0);
self.draw_scene_with_assets(world, assets, target);
}
fn draw_scene_with_assets<S: Surface>(&mut self, world: &World, assets: &AssetManager<Model, Material>, target: &mut S) {
let cam = match world.query::<&Camera>().iter().next() {
Some((_, cam)) => *cam,
None => {
eprintln!("[renderer] No camera component found. Skipping frame");
return;
}
};
// Direction from the light source (0,+Y) towards the scene.
let light_dir: Vec3 = Vec3::new(0.0, -1.0, 0.0).normalize();
for (_, (tr, mh)) in world.query::<(&Transform, &ModelHandle)>().iter() {
let model = match assets.get_model(raidillon_core::ModelId(mh.0)) {
Some(model) => model,
None => {
eprintln!("[renderer] Model with ID {} not found in assets", mh.0);
continue;
}
};
let mesh = &model.mesh;
let mat = &model.material;
let tex_ref: &SrgbTexture2d = mat.base_color.as_ref().unwrap_or(&self.white_tex);
let mut sampler = tex_ref.sampled();
sampler = sampler.wrap_function(SamplerWrapFunction::Repeat);
sampler = sampler.minify_filter(MinifySamplerFilter::Linear);
sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear);
let c = mat.base_color_factor;
let uniforms = uniform! {
model: tr.matrix().to_cols_array_2d(),
view: cam.view().to_cols_array_2d(),
projection: cam.projection().to_cols_array_2d(),
u_light: [light_dir.x, light_dir.y, light_dir.z],
tex: sampler,
color: [c[0], c[1], c[2]],
uv_offset: [mat.uv_offset.x, mat.uv_offset.y],
uv_scale: [mat.uv_scale.x, mat.uv_scale.y],
};
target.draw(
&mesh.vbuf,
&mesh.ibuf,
&self.program,
&uniforms,
&self.params,
).unwrap();
}
// Render skybox
let mut sky_view = cam.view();
sky_view.w_axis = Vec4::new(0.0, 0.0, 0.0, 1.0);
let mut sampler = self.skybox_texture.sampled();
sampler = sampler.wrap_function(SamplerWrapFunction::Clamp);
sampler = sampler.minify_filter(MinifySamplerFilter::Linear);
sampler = sampler.magnify_filter(MagnifySamplerFilter::Linear);
let uniforms = uniform! {
view: sky_view.to_cols_array_2d(),
projection: cam.projection().to_cols_array_2d(),
equirect: sampler,
};
let sky_params = glium::DrawParameters {
depth: glium::Depth {
test: DepthTest::IfLessOrEqual,
write: false,
.. Default::default()
},
.. Default::default()
};
target.draw(
&self.skybox_mesh.vbuf,
&self.skybox_mesh.ibuf,
&self.skybox_program,
&uniforms,
&sky_params,
).unwrap();
}
}

View file

@ -0,0 +1,50 @@
use hecs::World;
use raidillon_core::{AssetManager, ModelId};
use crate::render::GliumRenderer;
use crate::model::{Model, Material};
use crate::window::DisplayHandle;
use glium::Surface;
/// A pure render system that doesn't own the ECS world.
/// This decouples rendering from ECS world ownership.
pub struct RenderSystem {
renderer: GliumRenderer,
assets: AssetManager<Model, Material>,
}
impl RenderSystem {
pub fn new(display: DisplayHandle) -> anyhow::Result<Self> {
Ok(Self {
renderer: GliumRenderer::new(display.as_inner().clone())?,
assets: AssetManager::new(),
})
}
pub fn render(&mut self, world: &World, target: &mut impl Surface) {
// Pass the asset manager to the renderer for accessing models
self.renderer.render_into_with_assets(world, &self.assets, target);
}
pub fn load_model(&mut self, path: &str) -> anyhow::Result<ModelId> {
// Check cache first
let model = crate::gltf_loader::load_gltf(path, self.renderer.display())?;
let model_id = self.assets.cache_model(path.to_string(), Box::new(model));
Ok(model_id)
}
pub fn display(&self) -> &glium::Display<glium::glutin::surface::WindowSurface> {
self.renderer.display()
}
pub fn get_model(&self, id: ModelId) -> Option<&Model> {
self.assets.get_model(id)
}
pub fn assets(&self) -> &AssetManager<Model, Material> {
&self.assets
}
pub fn assets_mut(&mut self) -> &mut AssetManager<Model, Material> {
&mut self.assets
}
}