diff --git a/Cargo.lock b/Cargo.lock index 9cd14d4..308e9da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arrayref" @@ -1258,10 +1258,19 @@ dependencies = [ "winit", ] +[[package]] +name = "raidillon_ecs" +version = "0.1.0" +dependencies = [ + "glam", + "raidillon_assets", +] + [[package]] name = "raidillon_game" version = "0.1.0" dependencies = [ + "glam", "raidillon_core", "raidillon_glium", "raidillon_platform", @@ -1278,6 +1287,7 @@ dependencies = [ "indexmap", "raidillon_assets", "raidillon_core", + "raidillon_ecs", "raidillon_platform", "winit", ] @@ -1286,6 +1296,7 @@ dependencies = [ name = "raidillon_platform" version = "0.1.0" dependencies = [ + "glam", "raidillon_assets", "raidillon_core", "winit", diff --git a/Cargo.toml b/Cargo.toml index 7b2793f..aedf63e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "glium_platform", "platform", "asset", - "game" + "game", + "ecs" ] diff --git a/asset/Cargo.toml b/asset/Cargo.toml index 6abd397..7b35051 100644 --- a/asset/Cargo.toml +++ b/asset/Cargo.toml @@ -2,5 +2,3 @@ name = "raidillon_assets" version = "0.1.0" edition = "2024" - -[dependencies] diff --git a/asset/src/lib.rs b/asset/src/lib.rs index 71eb407..f0349ac 100644 --- a/asset/src/lib.rs +++ b/asset/src/lib.rs @@ -1,3 +1,10 @@ pub mod model_manager; - pub use crate::model_manager::{ModelManager, ModelManagerRef}; + +pub use crate::model_manager::ModelID; +#[macro_export] +macro_rules! include_shader { + ($path:expr) => { + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/shaders/", $path)) + }; +} \ No newline at end of file diff --git a/asset/src/model_manager.rs b/asset/src/model_manager.rs index de7c254..4f1a9bb 100644 --- a/asset/src/model_manager.rs +++ b/asset/src/model_manager.rs @@ -1,14 +1,17 @@ +use std::any::Any; use std::cell::RefCell; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::rc::Rc; pub type ModelManagerRef = Rc>>; - +pub type ModelID = PathBuf; /// The asset manager trait of Raidillon. -pub trait ModelManager { +pub trait ModelManager: Any { /// Loads a gltf model to VRAM. fn load_gltf(&mut self, path: &Path); /// Unloads the loaded model from VRAM. fn unload_model(&mut self, path: &Path); + + fn get_model(&self, id: &ModelID) -> Option<&dyn Any>; } diff --git a/assets/shaders/gl_textured.frag b/assets/shaders/gl_textured.frag new file mode 100644 index 0000000..b264f77 --- /dev/null +++ b/assets/shaders/gl_textured.frag @@ -0,0 +1,34 @@ +#version 330 core + +in vec3 v_normal; +in vec2 v_tex; +in vec3 v_position; + +out vec4 frag_color; + +uniform vec3 u_light; +uniform sampler2D tex; +uniform vec3 color; // base colour factor (acts as solid colour when no texture) + +void main() { + // Combine base texture (or constant white) with colour factor supplied by CPU. + vec3 base_col = texture(tex, v_tex).rgb * color; + + vec3 ambient_color = base_col * 0.2; + vec3 diffuse_color = base_col * 0.6; + vec3 specular_color = vec3(1.0); + + // u_light is the direction **from the light towards the fragment**. + float diffuse = max(dot(normalize(v_normal), normalize(u_light)), 0.0); + + vec3 camera_dir = normalize(-v_position); + vec3 half_dir = normalize(normalize(u_light) + camera_dir); + float specular = pow(max(dot(half_dir, normalize(v_normal)), 0.0), 16.0); + + vec3 result = ambient_color + diffuse * diffuse_color + specular * specular_color; + + // Convert from linear to sRGB for display (approximate γ-correction) + result = pow(result, vec3(1.0 / 2.2)); + + frag_color = vec4(result, 1.0); +} diff --git a/assets/shaders/gl_textured.vert b/assets/shaders/gl_textured.vert new file mode 100644 index 0000000..df2c27d --- /dev/null +++ b/assets/shaders/gl_textured.vert @@ -0,0 +1,23 @@ +#version 330 core + +in vec3 position; +in vec3 normal; +in vec2 tex_coords; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; +uniform vec2 uv_offset; +uniform vec2 uv_scale; + +out vec3 v_normal; +out vec2 v_tex; +out vec3 v_position; + +void main() { + mat4 modelview = view * model; + v_normal = transpose(inverse(mat3(modelview))) * normal; + v_tex = tex_coords * uv_scale + uv_offset; + v_position = (modelview * vec4(position, 1.0)).xyz; + gl_Position = projection * modelview * vec4(position, 1.0); +} diff --git a/assets/shaders/skybox.frag b/assets/shaders/skybox.frag new file mode 100644 index 0000000..8418e54 --- /dev/null +++ b/assets/shaders/skybox.frag @@ -0,0 +1,22 @@ +#version 330 core + +in vec3 direction; + +out vec4 frag_color; + +uniform sampler2D equirect; + +const vec2 inv_atan = vec2(0.15915494309, 0.31830988618); + +vec2 sample_spherical_map(vec3 v) { + vec2 uv = vec2(atan(v.z, v.x), asin(v.y)); + uv *= inv_atan; + uv += 0.5; + return uv; +} + +void main() { + vec2 uv = sample_spherical_map(normalize(direction)); + vec3 color = texture(equirect, uv).rgb; + frag_color = vec4(color, 1.0); +} diff --git a/assets/shaders/skybox.vert b/assets/shaders/skybox.vert new file mode 100644 index 0000000..2db3b53 --- /dev/null +++ b/assets/shaders/skybox.vert @@ -0,0 +1,15 @@ +#version 330 core + +in vec3 position; + +uniform mat4 view; + +uniform mat4 projection; + +out vec3 direction; + +void main() { + direction = position; + vec4 pos = projection * view * vec4(position, 1.0); + gl_Position = pos.xyww; +} diff --git a/core/src/scene.rs b/core/src/scene.rs index be47abd..5014d63 100644 --- a/core/src/scene.rs +++ b/core/src/scene.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; pub struct Scene { - title: String, - world: hecs::World, - skybox_texture_path: Option, + pub title: String, + pub world: hecs::World, + pub skybox_texture_path: Option, } impl Scene { diff --git a/core/src/system.rs b/core/src/system.rs index 3480901..7bc5a94 100644 --- a/core/src/system.rs +++ b/core/src/system.rs @@ -16,7 +16,7 @@ pub trait System { fn update(&mut self, ctx: &mut SystemContext); } -pub type SystemID = String; +pub type SystemID = &'static str; pub struct SystemManager { pub systems: IndexMap>, } diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml new file mode 100644 index 0000000..0e1efb5 --- /dev/null +++ b/ecs/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "raidillon_ecs" +version = "0.1.0" +edition = "2024" + +[dependencies] +glam = "0.30.5" +raidillon_assets = { path = "../asset" } diff --git a/ecs/src/components.rs b/ecs/src/components.rs new file mode 100644 index 0000000..83bc7df --- /dev/null +++ b/ecs/src/components.rs @@ -0,0 +1,15 @@ +use glam::{Vec3, Quat, Mat4}; +pub use raidillon_assets::ModelID; + +#[derive(Copy, Clone)] +pub struct Transform { + pub translation: Vec3, + pub rotation: Quat, + pub scale: Vec3, +} + +impl Transform { + pub fn matrix(&self) -> Mat4 { + Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) + } +} diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs new file mode 100644 index 0000000..d08fe80 --- /dev/null +++ b/ecs/src/lib.rs @@ -0,0 +1,3 @@ +pub mod components; + +pub use components::{Transform, ModelID}; diff --git a/game/Cargo.toml b/game/Cargo.toml index 0c76ce4..835bf39 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -11,3 +11,4 @@ glium = ["raidillon_glium"] raidillon_core = { path = "../core" } raidillon_platform = { path = "../platform" } raidillon_glium = { path = "../glium_platform", optional = true } +glam = "0.30.5" \ No newline at end of file diff --git a/game/src/main.rs b/game/src/main.rs index 3b201a7..e0403c7 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,14 +1,39 @@ -use raidillon_core::{Engine, Scene}; -use raidillon_platform::Platform; +use glam::Vec3; +use raidillon_core::{Engine, Scene, System}; +use raidillon_core::system::SystemContext; +use raidillon_platform::{Platform, Camera}; #[cfg(feature = "glium")] use raidillon_glium::GliumPlatform; +const RENDERING_TEST_SYSTEM: &str = "rendering_test_system"; +struct RenderingTestSystem; + +impl System for RenderingTestSystem { + fn initialize(&mut self) {} + fn load_world(&mut self, ctx: &mut SystemContext) { + ctx.scene.world.spawn((Camera { + eye: Vec3::new(0.0, 0.0, 2.0), + center: Vec3::ZERO, + up: Vec3::Y, + fovy: 60_f32.to_radians(), + aspect: 1920 as f32 / 1080 as f32, // FIXME + znear: 0.1, + zfar: 100.0, + },)); + + // TODO: Load a sample glTF file + } + + fn update(&mut self, ctx: &mut SystemContext) {} +} + fn main() { let mut engine = Engine::new(); // Define systems // engine.system_manager.add_system("spawn_chunks".to_string(), ChunkSystem); // engine.system_manager.add_system("movement".to_string(), MovementSystem); + engine.system_manager.add_system(RENDERING_TEST_SYSTEM, Box::new(RenderingTestSystem)); // Set up the scene let main_scene_id = "Main".to_owned(); diff --git a/glium_platform/Cargo.toml b/glium_platform/Cargo.toml index 941ce0e..c206b4e 100644 --- a/glium_platform/Cargo.toml +++ b/glium_platform/Cargo.toml @@ -11,5 +11,6 @@ gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transfor raidillon_platform = { path = "../platform" } raidillon_core = { path = "../core" } raidillon_assets = { path = "../asset" } +raidillon_ecs = { path = "../ecs" } winit = "0.30.12" indexmap = "2.10.0" \ No newline at end of file diff --git a/glium_platform/src/assets.rs b/glium_platform/src/assets.rs index 5786059..6d33887 100644 --- a/glium_platform/src/assets.rs +++ b/glium_platform/src/assets.rs @@ -1,3 +1,5 @@ +use std::any::Any; +use std::cell::RefCell; use raidillon_assets::{ModelManagerRef, ModelManager}; use crate::model::Model; use std::path::{Path, PathBuf}; @@ -5,10 +7,12 @@ use crate::gltf_loader::load_gltf; use glium::backend::Facade; use std::collections::HashMap; use std::collections::hash_map::Entry; +use std::rc::Rc; +use raidillon_assets::model_manager::ModelID; /// Glium platform asset manager implementation. pub struct GliumAssetManager { - models: HashMap, + pub models: HashMap, facade: Box, } @@ -42,4 +46,8 @@ impl ModelManager for GliumAssetManager { // } // } // } + + fn get_model(&self, id: &ModelID) -> Option<&dyn Any> { + self.models.get(id).map(|model| model as &dyn Any) + } } diff --git a/glium_platform/src/lib.rs b/glium_platform/src/lib.rs index 6fd924f..7d91b4d 100644 --- a/glium_platform/src/lib.rs +++ b/glium_platform/src/lib.rs @@ -5,6 +5,6 @@ pub mod gltf_loader; pub mod system; mod render; -pub use assets::GliumAssetManager; +pub use assets::{GliumAssetManager}; pub use platform::GliumPlatform; pub use system::RenderingSystem; diff --git a/glium_platform/src/model.rs b/glium_platform/src/model.rs index 815cd96..1d18dd8 100644 --- a/glium_platform/src/model.rs +++ b/glium_platform/src/model.rs @@ -1,3 +1,4 @@ +use std::any::{Any, TypeId}; use glium::{IndexBuffer, VertexBuffer, implement_vertex}; use glium::texture::{SrgbTexture2d, Texture2d}; use glium::uniforms::SamplerBehavior; diff --git a/glium_platform/src/platform.rs b/glium_platform/src/platform.rs index f9a6eb9..69b0735 100644 --- a/glium_platform/src/platform.rs +++ b/glium_platform/src/platform.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; +use std::str::FromStr; use std::sync::{Arc, RwLock}; use raidillon_platform::Platform; use glium::winit::event_loop::EventLoop; @@ -8,12 +9,14 @@ use glium::backend::glutin::Display; use glium::glutin::surface::WindowSurface; use glium::backend::glutin::SimpleWindowBuilder; use glium::Surface; -use crate::system::{RenderingSystemManager, RenderingSystem, RenderingContext}; +use crate::system::{RenderingSystemManager, RenderingSystem, RenderingContext, SystemID}; use winit::event::{Event, WindowEvent}; use raidillon_assets::{ModelManager, ModelManagerRef}; use raidillon_core::Engine; -use crate::GliumAssetManager; -use crate::render::BasicRenderingSystem; +use crate::{GliumAssetManager}; +use crate::render::BasicMeshRenderingSystem; + +pub const MESH_RENDERER: &str = "mesh_renderer"; pub struct GliumPlatform { event_loop: EventLoop<()>, @@ -36,8 +39,15 @@ impl Platform for GliumPlatform { .build(&event_loop); let asset_manager: ModelManagerRef = Rc::new(RefCell::new(Box::new(GliumAssetManager::new(Box::new(display.clone()))))); - let rendering_system_manager = RenderingSystemManager::new(); + let mut rendering_system_manager = RenderingSystemManager::new(); engine.set_model_manager(asset_manager.clone()); + + // Install rendering systems + rendering_system_manager.add_system( + MESH_RENDERER, + Box::new(BasicMeshRenderingSystem::initialize(&display)) + ); + Self { event_loop, window, @@ -49,6 +59,7 @@ impl Platform for GliumPlatform { } fn run(mut self) { + self.engine.initialize(); let _ = &self.event_loop.run(move |event, el| { match event { Event::WindowEvent { event, .. } => match event { diff --git a/glium_platform/src/render/basic.rs b/glium_platform/src/render/basic.rs index 32d2a5c..66ffadf 100644 --- a/glium_platform/src/render/basic.rs +++ b/glium_platform/src/render/basic.rs @@ -1,11 +1,111 @@ -use crate::RenderingSystem; +use std::any::Any; +use glium::{uniform, Display, Program, Surface}; +use glium::glutin::surface::WindowSurface; +use glium::texture::{RawImage2d, SrgbTexture2d}; +use crate::{GliumAssetManager, RenderingSystem}; use crate::system::RenderingContext; +use raidillon_assets::include_shader; +pub use raidillon_platform::Camera; +use glam::Vec3; +use glium::uniforms::{MagnifySamplerFilter, MinifySamplerFilter, SamplerWrapFunction}; +use raidillon_ecs::{Transform, ModelID}; +use crate::model::Model; /// A basic renderer pipeline step. -pub struct BasicRenderingSystem; +pub struct BasicMeshRenderingSystem { + program: Program, + white_tex: SrgbTexture2d, + params: glium::DrawParameters<'static>, +} + +impl RenderingSystem for BasicMeshRenderingSystem { + fn initialize(display: &Display) -> Self { + const VERT_SRC: &str = include_shader!("gl_textured.vert"); + const FRAG_SRC: &str = include_shader!("gl_textured.frag"); + + let program = Program::from_source(display, VERT_SRC, FRAG_SRC, None).unwrap(); + + let white_tex = { + let data = vec![255u8, 255u8, 255u8, 255u8]; + let raw = RawImage2d::from_raw_rgba(data, (1, 1)); + SrgbTexture2d::new(display, raw).unwrap() + }; + + let params = glium::DrawParameters { + depth: glium::Depth { + test: glium::draw_parameters::DepthTest::IfLess, + write: true, + .. Default::default() + }, + .. Default::default() + }; + + Self { + program, white_tex, params + } + } -impl RenderingSystem for BasicRenderingSystem { fn render(&mut self, ctx: &mut RenderingContext) { - todo!() + let cam = match ctx.scene.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(); + + // let asset_manager = ctx.asset_manager.borrow(); + // let any_ref: &dyn Any = &**asset_manager; + // if let Some(glium_manager) = any_ref.downcast_ref::() { + // &glium_manager.models; + // } + + let asset_manager = ctx.asset_manager.borrow(); + + for (_, (tr, mh)) in ctx.scene.world.query::<(&Transform, &ModelID)>().iter() { + let model = match asset_manager.get_model(mh) { + Some(model) => model, + _ => continue, + }; + + let model = match model.downcast_ref::() { + Some(model) => model, + None => 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], + }; + + ctx.target.draw( + &mesh.vbuf, + &mesh.ibuf, + &self.program, + &uniforms, + &self.params, + ).unwrap(); + } } } diff --git a/glium_platform/src/render/mod.rs b/glium_platform/src/render/mod.rs index e2288bc..01028df 100644 --- a/glium_platform/src/render/mod.rs +++ b/glium_platform/src/render/mod.rs @@ -1,2 +1,2 @@ mod basic; -pub use basic::BasicRenderingSystem; \ No newline at end of file +pub use basic::BasicMeshRenderingSystem; \ No newline at end of file diff --git a/glium_platform/src/system.rs b/glium_platform/src/system.rs index 03d228f..63d58b4 100644 --- a/glium_platform/src/system.rs +++ b/glium_platform/src/system.rs @@ -1,8 +1,11 @@ +use std::cell::RefCell; +use std::rc::Rc; use raidillon_core::Scene; -use glium::Frame; -use crate::GliumAssetManager; +use glium::{Display, Frame}; +use glium::glutin::surface::WindowSurface; use indexmap::IndexMap; use raidillon_assets::ModelManagerRef; +use crate::GliumAssetManager; pub struct RenderingContext<'a> { pub scene: &'a Scene, @@ -14,10 +17,10 @@ pub struct RenderingContext<'a> { /// This is unrelated to the main System trait in core. pub trait RenderingSystem { fn render(&mut self, ctx: &mut RenderingContext); - fn compile_shaders(&mut self) {} + fn initialize(display: &Display) -> Self where Self: Sized; } -type SystemID = String; +pub type SystemID = &'static str; pub struct RenderingSystemManager { pub systems: IndexMap>, diff --git a/platform/Cargo.toml b/platform/Cargo.toml index e3369e8..3b42bac 100644 --- a/platform/Cargo.toml +++ b/platform/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" winit = "0.30.12" raidillon_core = { path = "../core" } raidillon_assets = { path = "../asset" } +glam = "0.30.5" diff --git a/platform/src/camera.rs b/platform/src/camera.rs new file mode 100644 index 0000000..53b3599 --- /dev/null +++ b/platform/src/camera.rs @@ -0,0 +1,24 @@ +use glam::{Mat4, Vec3}; + +#[derive(Copy, Clone)] +pub struct Camera { + pub eye: Vec3, + pub center: Vec3, + pub up: Vec3, + pub fovy: f32, + pub aspect: f32, + pub znear: f32, + pub zfar: f32, +} + +impl Camera { + pub fn view(&self) -> Mat4 { + Mat4::look_at_rh(self.eye, self.center, self.up) + } + pub fn projection(&self) -> Mat4 { + Mat4::perspective_rh(self.fovy, self.aspect, self.znear, self.zfar) + } + pub fn view_proj(&self) -> Mat4 { + self.projection() * self.view() + } +} diff --git a/platform/src/lib.rs b/platform/src/lib.rs index 477c025..0750166 100644 --- a/platform/src/lib.rs +++ b/platform/src/lib.rs @@ -1,5 +1,5 @@ pub mod platform; -pub mod context; +mod camera; -pub use context::PlatformContext; pub use platform::Platform; +pub use camera::Camera;