Giant refactor for a better event-driven architecture
This commit is contained in:
parent
341d531db3
commit
88a21040cd
22 changed files with 936 additions and 67 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -1728,6 +1728,11 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raidillon_core"
|
name = "raidillon_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"glam",
|
||||||
|
"hecs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "raidillon_ecs"
|
name = "raidillon_ecs"
|
||||||
|
|
@ -1743,6 +1748,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"glam",
|
"glam",
|
||||||
|
"glium",
|
||||||
"hecs",
|
"hecs",
|
||||||
"raidillon_core",
|
"raidillon_core",
|
||||||
"raidillon_ecs",
|
"raidillon_ecs",
|
||||||
|
|
@ -1757,6 +1763,9 @@ name = "raidillon_input"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
|
"hecs",
|
||||||
|
"raidillon_core",
|
||||||
|
"raidillon_render",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1771,6 +1780,7 @@ dependencies = [
|
||||||
"glutin",
|
"glutin",
|
||||||
"hecs",
|
"hecs",
|
||||||
"image",
|
"image",
|
||||||
|
"raidillon_core",
|
||||||
"raidillon_ecs",
|
"raidillon_ecs",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,8 @@
|
||||||
name = "raidillon_core"
|
name = "raidillon_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.98"
|
||||||
|
glam = "0.30.4"
|
||||||
|
hecs = "0.10.5"
|
||||||
|
|
|
||||||
95
raidillon_core/src/assets.rs
Normal file
95
raidillon_core/src/assets.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Forward declarations - these will be from other crates
|
||||||
|
pub trait Model {}
|
||||||
|
pub trait Material {}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ModelId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct MaterialId(pub usize);
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct TextureHandle(pub usize);
|
||||||
|
|
||||||
|
pub struct AssetManager<M: Model + ?Sized, Mat: Material + ?Sized> {
|
||||||
|
models: Vec<Box<M>>,
|
||||||
|
materials: Vec<Box<Mat>>,
|
||||||
|
textures: HashMap<String, TextureHandle>,
|
||||||
|
model_cache: HashMap<String, ModelId>,
|
||||||
|
next_texture_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M: Model + ?Sized, Mat: Material + ?Sized> AssetManager<M, Mat> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
models: Vec::new(),
|
||||||
|
materials: Vec::new(),
|
||||||
|
textures: HashMap::new(),
|
||||||
|
model_cache: HashMap::new(),
|
||||||
|
next_texture_id: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_model(&mut self, model: Box<M>) -> ModelId {
|
||||||
|
let id = ModelId(self.models.len());
|
||||||
|
self.models.push(model);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_model(&mut self, path: String, model: Box<M>) -> ModelId {
|
||||||
|
if let Some(&cached_id) = self.model_cache.get(&path) {
|
||||||
|
return cached_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let model_id = self.add_model(model);
|
||||||
|
self.model_cache.insert(path, model_id);
|
||||||
|
model_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_model(&self, id: ModelId) -> Option<&M> {
|
||||||
|
self.models.get(id.0).map(|boxed| boxed.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_model_mut(&mut self, id: ModelId) -> Option<&mut M> {
|
||||||
|
self.models.get_mut(id.0).map(|boxed| boxed.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_material(&mut self, material: Box<Mat>) -> MaterialId {
|
||||||
|
let id = MaterialId(self.materials.len());
|
||||||
|
self.materials.push(material);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_material(&self, id: MaterialId) -> Option<&Mat> {
|
||||||
|
self.materials.get(id.0).map(|boxed| boxed.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_texture(&mut self, name: String) -> TextureHandle {
|
||||||
|
if let Some(&handle) = self.textures.get(&name) {
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = TextureHandle(self.next_texture_id);
|
||||||
|
self.next_texture_id += 1;
|
||||||
|
self.textures.insert(name, handle);
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_texture_handle(&self, name: &str) -> Option<TextureHandle> {
|
||||||
|
self.textures.get(name).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn model_count(&self) -> usize {
|
||||||
|
self.models.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn material_count(&self) -> usize {
|
||||||
|
self.materials.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_cache(&mut self) {
|
||||||
|
self.model_cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
80
raidillon_core/src/engine.rs
Normal file
80
raidillon_core/src/engine.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
use hecs::World;
|
||||||
|
use crate::{
|
||||||
|
Time, EventBus, GameEvent, SystemRegistry,
|
||||||
|
AssetManager, Model, Material, ModelId
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Engine {
|
||||||
|
pub world: World,
|
||||||
|
pub systems: SystemRegistry,
|
||||||
|
pub assets: AssetManager<dyn Model, dyn Material>,
|
||||||
|
pub events: EventBus,
|
||||||
|
pub time: Time,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let systems = SystemRegistry::new();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
world: World::new(),
|
||||||
|
systems,
|
||||||
|
assets: AssetManager::new(),
|
||||||
|
events: EventBus::new(),
|
||||||
|
time: Time::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_system<S: crate::System + 'static>(&mut self, system: S) {
|
||||||
|
self.systems.add_system(system);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
self.time.tick();
|
||||||
|
let dt = self.time.delta_seconds();
|
||||||
|
|
||||||
|
// Update all systems
|
||||||
|
self.systems.update_all(&mut self.world, &self.assets, &mut self.events, dt);
|
||||||
|
|
||||||
|
// Process events
|
||||||
|
self.events.process();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_window_event(&mut self, event: &GameEvent) {
|
||||||
|
self.events.emit(event.clone());
|
||||||
|
self.systems.handle_event_for_all(event, &mut self.world);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_model(&mut self, path: &str) -> anyhow::Result<ModelId> {
|
||||||
|
// This is a placeholder - in a real implementation, we'd need to
|
||||||
|
// coordinate with the render system to actually load the model
|
||||||
|
// For now, just return a dummy ID
|
||||||
|
Ok(ModelId(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_entity_with_model(&mut self, model_id: ModelId) -> hecs::Entity {
|
||||||
|
// This would need proper Transform and ModelHandle types
|
||||||
|
// For now, return a placeholder entity
|
||||||
|
self.world.spawn(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delta_time(&self) -> f32 {
|
||||||
|
self.time.delta_seconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_event(&mut self, event: GameEvent) {
|
||||||
|
self.events.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn world(&self) -> &World {
|
||||||
|
&self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn world_mut(&mut self) -> &mut World {
|
||||||
|
&mut self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_count(&self) -> usize {
|
||||||
|
self.systems.system_count()
|
||||||
|
}
|
||||||
|
}
|
||||||
60
raidillon_core/src/events.rs
Normal file
60
raidillon_core/src/events.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
use glam::Vec3;
|
||||||
|
use hecs::Entity;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum GameEvent {
|
||||||
|
InputAction(InputAction),
|
||||||
|
CameraMove { position: Vec3, front: Vec3 },
|
||||||
|
WindowResize { width: u32, height: u32 },
|
||||||
|
EntitySpawned(Entity),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum InputAction {
|
||||||
|
MoveForward,
|
||||||
|
MoveBackward,
|
||||||
|
MoveLeft,
|
||||||
|
MoveRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait EventHandler {
|
||||||
|
fn handle(&mut self, event: &GameEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventBus {
|
||||||
|
events: Vec<GameEvent>,
|
||||||
|
handlers: Vec<Box<dyn EventHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventBus {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
events: Vec::new(),
|
||||||
|
handlers: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit(&mut self, event: GameEvent) {
|
||||||
|
self.events.push(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe<H: EventHandler + 'static>(&mut self, handler: H) {
|
||||||
|
self.handlers.push(Box::new(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self) {
|
||||||
|
for event in self.events.drain(..) {
|
||||||
|
for handler in &mut self.handlers {
|
||||||
|
handler.handle(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_events(&self) -> bool {
|
||||||
|
!self.events.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events(&self) -> &[GameEvent] {
|
||||||
|
&self.events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,11 @@
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
pub mod events;
|
||||||
|
pub mod assets;
|
||||||
|
pub mod systems;
|
||||||
|
pub mod engine;
|
||||||
|
|
||||||
pub use time::Time;
|
pub use time::Time;
|
||||||
|
pub use events::{GameEvent, InputAction, EventHandler, EventBus};
|
||||||
|
pub use assets::{AssetManager, ModelId, MaterialId, TextureHandle, Model, Material};
|
||||||
|
pub use systems::{System, SystemRegistry};
|
||||||
|
pub use engine::Engine;
|
||||||
|
|
|
||||||
45
raidillon_core/src/systems.rs
Normal file
45
raidillon_core/src/systems.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use hecs::World;
|
||||||
|
use crate::assets::{AssetManager, Model, Material};
|
||||||
|
use crate::events::{EventBus, GameEvent};
|
||||||
|
|
||||||
|
pub trait System {
|
||||||
|
fn update(&mut self, world: &mut World, resources: &AssetManager<dyn Model, dyn Material>, events: &mut EventBus, dt: f32);
|
||||||
|
fn handle_event(&mut self, event: &GameEvent, world: &mut World);
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SystemRegistry {
|
||||||
|
systems: Vec<Box<dyn System>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemRegistry {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
systems: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_system<S: System + 'static>(&mut self, system: S) {
|
||||||
|
self.systems.push(Box::new(system));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_all(&mut self, world: &mut World, resources: &AssetManager<dyn Model, dyn Material>, events: &mut EventBus, dt: f32) {
|
||||||
|
for system in &mut self.systems {
|
||||||
|
system.update(world, resources, events, dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event_for_all(&mut self, event: &GameEvent, world: &mut World) {
|
||||||
|
for system in &mut self.systems {
|
||||||
|
system.handle_event(event, world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn system_count(&self) -> usize {
|
||||||
|
self.systems.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.systems.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,5 +13,5 @@ impl Transform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct ModelHandle(pub usize);
|
pub struct ModelHandle(pub usize);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ edition = "2021"
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
glam = "0.30.4"
|
glam = "0.30.4"
|
||||||
winit = "0.30"
|
winit = "0.30"
|
||||||
|
glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] }
|
||||||
raidillon_render = { path = "../raidillon_render" }
|
raidillon_render = { path = "../raidillon_render" }
|
||||||
raidillon_ecs = { path = "../raidillon_ecs" }
|
raidillon_ecs = { path = "../raidillon_ecs" }
|
||||||
raidillon_ui = { path = "../raidillon_ui" }
|
raidillon_ui = { path = "../raidillon_ui" }
|
||||||
|
|
|
||||||
88
raidillon_game/src/game_state.rs
Normal file
88
raidillon_game/src/game_state.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
use anyhow;
|
||||||
|
use glam::{Quat, Vec3};
|
||||||
|
use hecs::{Entity, World};
|
||||||
|
use raidillon_ecs::{Transform, ModelHandle};
|
||||||
|
use raidillon_render::{Camera, ModelId};
|
||||||
|
use raidillon_core::InputAction;
|
||||||
|
|
||||||
|
pub struct GameState {
|
||||||
|
pub world: World,
|
||||||
|
pub camera_entity: Entity,
|
||||||
|
pub object_entity: Entity,
|
||||||
|
pub ground_entity: Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
// Create camera entity
|
||||||
|
let camera_entity = world.spawn((Camera {
|
||||||
|
eye: Vec3::new(0.0, 0.0, 2.0),
|
||||||
|
center: Vec3::ZERO,
|
||||||
|
up: Vec3::Y,
|
||||||
|
fovy: 60_f32.to_radians(),
|
||||||
|
aspect: 1280.0 / 720.0, // default aspect ratio
|
||||||
|
znear: 0.1,
|
||||||
|
zfar: 100.0,
|
||||||
|
},));
|
||||||
|
|
||||||
|
// Create placeholder entities for object and ground (will be properly loaded later)
|
||||||
|
let object_entity = world.spawn((
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(0.0, -2.5, -5.0),
|
||||||
|
rotation: Quat::IDENTITY,
|
||||||
|
scale: Vec3::new(0.01, 0.01, 0.01),
|
||||||
|
},
|
||||||
|
ModelHandle(0),
|
||||||
|
));
|
||||||
|
|
||||||
|
let ground_entity = world.spawn((
|
||||||
|
Transform {
|
||||||
|
translation: Vec3::new(0.0, -1.5, 0.0),
|
||||||
|
rotation: Quat::IDENTITY,
|
||||||
|
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||||
|
},
|
||||||
|
ModelHandle(1),
|
||||||
|
));
|
||||||
|
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
camera_entity,
|
||||||
|
object_entity,
|
||||||
|
ground_entity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, _dt: f32) {
|
||||||
|
// Game state update logic will go here
|
||||||
|
// Camera updates are now handled by CameraSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize_camera(&mut self, width: u32, height: u32) {
|
||||||
|
if let Ok(cam) = self.world.query_one_mut::<&mut Camera>(self.camera_entity) {
|
||||||
|
cam.aspect = width as f32 / height as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn world(&self) -> &World {
|
||||||
|
&self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn world_mut(&mut self) -> &mut World {
|
||||||
|
&mut self.world
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_model(&mut self, model_id: ModelId, transform: Transform) -> Entity {
|
||||||
|
self.world.spawn((transform, ModelHandle(model_id.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_entity_model(&mut self, entity: Entity, model_id: ModelId) -> anyhow::Result<()> {
|
||||||
|
if let Ok(model_handle) = self.world.query_one_mut::<&mut ModelHandle>(entity) {
|
||||||
|
model_handle.0 = model_id.0;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow::anyhow!("Entity does not have a ModelHandle component"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
raidillon_game/src/lib.rs
Normal file
4
raidillon_game/src/lib.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod game_state;
|
||||||
|
|
||||||
|
pub use game_state::GameState;
|
||||||
|
pub use raidillon_core::InputAction;
|
||||||
|
|
@ -1,20 +1,52 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use glam::{Quat, Vec3, EulerRot};
|
use glam::{Quat, Vec3, EulerRot};
|
||||||
use raidillon_core::Time;
|
use raidillon_core::{Time, EventBus, GameEvent, InputAction, System, SystemRegistry, AssetManager, Model, Material};
|
||||||
use raidillon_ecs::Transform;
|
use raidillon_ecs::Transform;
|
||||||
use raidillon_render::{Camera, ECSRenderer, init_render_window, DisplayHandle};
|
use raidillon_render::{RenderSystem, init_render_window, DisplayHandle};
|
||||||
use raidillon_ui::Gui;
|
use raidillon_ui::Gui;
|
||||||
use raidillon_input::{Input, FPSCameraController};
|
use raidillon_input::{InputSystem, CameraSystem};
|
||||||
use winit::keyboard::KeyCode;
|
use raidillon_game::GameState;
|
||||||
use winit::window::CursorGrabMode;
|
use winit::window::CursorGrabMode;
|
||||||
use winit::event::MouseButton;
|
use winit::event::MouseButton;
|
||||||
|
use hecs::World;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
// Wrapper to make RenderSystem implement the System trait
|
||||||
enum Action {
|
struct RenderSystemWrapper {
|
||||||
MoveForward,
|
render_system: RenderSystem,
|
||||||
MoveBackward,
|
}
|
||||||
MoveLeft,
|
|
||||||
MoveRight,
|
impl RenderSystemWrapper {
|
||||||
|
fn new(display: DisplayHandle) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
render_system: RenderSystem::new(display)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_model(&mut self, path: &str) -> anyhow::Result<raidillon_core::ModelId> {
|
||||||
|
self.render_system.load_model(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, world: &World, target: &mut impl glium::Surface) {
|
||||||
|
self.render_system.render(world, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&self) -> &glium::Display<glium::glutin::surface::WindowSurface> {
|
||||||
|
self.render_system.display()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System for RenderSystemWrapper {
|
||||||
|
fn update(&mut self, _world: &mut World, _resources: &AssetManager<dyn Model, dyn Material>, _events: &mut EventBus, _dt: f32) {
|
||||||
|
// Rendering is handled separately in the main loop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, _event: &GameEvent, _world: &mut World) {
|
||||||
|
// RenderSystem doesn't need to respond to events currently
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"RenderSystem"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
|
|
@ -24,65 +56,50 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let (window, _display): (winit::window::Window, DisplayHandle) = init_render_window(&event_loop, "raidillon", (1280, 720))?;
|
let (window, _display): (winit::window::Window, DisplayHandle) = init_render_window(&event_loop, "raidillon", (1280, 720))?;
|
||||||
|
|
||||||
// Create ECS renderer which internally owns both the world and the renderer
|
// Create game state and systems
|
||||||
let mut ecsr = ECSRenderer::from_display_handle(&_display)?;
|
let mut game_state = GameState::new();
|
||||||
|
let mut render_system_wrapper = RenderSystemWrapper::new(_display.clone())?;
|
||||||
|
|
||||||
// Dear ImGui integration
|
// Dear ImGui integration
|
||||||
let mut gui = Gui::new(&_display, &window)?;
|
let mut gui = Gui::new(&_display, &window)?;
|
||||||
|
|
||||||
let mut input = Input::<Action>::new();
|
// Create system registry and register systems
|
||||||
input.map_key(KeyCode::KeyW, Action::MoveForward);
|
let mut system_registry = SystemRegistry::new();
|
||||||
input.map_key(KeyCode::KeyS, Action::MoveBackward);
|
let mut event_bus = EventBus::new();
|
||||||
input.map_key(KeyCode::KeyA, Action::MoveLeft);
|
let mut input_system = InputSystem::new(); // Keep this for direct access
|
||||||
input.map_key(KeyCode::KeyD, Action::MoveRight);
|
let mut camera_system = CameraSystem::new(game_state.camera_entity); // Keep this for direct access
|
||||||
|
|
||||||
let mut camera_controller = FPSCameraController::new(Vec3::new(0.0, 0.0, 2.0));
|
// Register systems later when we have proper asset manager integration
|
||||||
|
// For now, manage systems directly in main loop
|
||||||
|
|
||||||
let mut right_mouse_held = false;
|
let mut right_mouse_held = false;
|
||||||
|
|
||||||
let mut time = Time::new();
|
let mut time = Time::new();
|
||||||
|
|
||||||
let object_ent = ecsr.load_mesh_from_gltf("resources/models/tree.gltf", Transform {
|
// Load models using the RenderSystem
|
||||||
translation: Vec3::new(0.0, -2.5, -5.0),
|
let object_model_id = render_system_wrapper.load_model("resources/models/tree.gltf")?;
|
||||||
rotation: Quat::IDENTITY,
|
let ground_model_id = render_system_wrapper.load_model("resources/models/plane.gltf")?;
|
||||||
scale: Vec3::new(0.01, 0.01, 0.01),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let ground_ent = ecsr.load_mesh_from_gltf("resources/models/plane.gltf", Transform {
|
// Update the model handles in game state using the new method
|
||||||
translation: Vec3::new(0.0, -1.5, 0.0),
|
game_state.update_entity_model(game_state.object_entity, object_model_id)?;
|
||||||
rotation: Quat::IDENTITY,
|
game_state.update_entity_model(game_state.ground_entity, ground_model_id)?;
|
||||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
|
// Set initial camera aspect ratio
|
||||||
let camera_ent = {
|
|
||||||
let (w, h): (u32, u32) = window.inner_size().into();
|
let (w, h): (u32, u32) = window.inner_size().into();
|
||||||
ecsr.world.spawn((Camera {
|
game_state.resize_camera(w, h);
|
||||||
eye: Vec3::new(0.0, 0.0, 2.0),
|
|
||||||
center: Vec3::ZERO,
|
|
||||||
up: Vec3::Y,
|
|
||||||
fovy: 60_f32.to_radians(),
|
|
||||||
aspect: w as f32 / h as f32,
|
|
||||||
znear: 0.1,
|
|
||||||
zfar: 100.0,
|
|
||||||
},))
|
|
||||||
};
|
|
||||||
|
|
||||||
event_loop
|
event_loop
|
||||||
.run(move |event, el| {
|
.run(move |event, el| {
|
||||||
use winit::event::{Event, WindowEvent};
|
use winit::event::{Event, WindowEvent};
|
||||||
|
|
||||||
gui.handle_event(&window, &event);
|
gui.handle_event(&window, &event);
|
||||||
|
input_system.handle_event(&event);
|
||||||
input.handle_event(&event);
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { event, .. } => match event {
|
Event::WindowEvent { event, .. } => match event {
|
||||||
WindowEvent::CloseRequested => el.exit(),
|
WindowEvent::CloseRequested => el.exit(),
|
||||||
WindowEvent::Resized(sz) => {
|
WindowEvent::Resized(sz) => {
|
||||||
ecsr.world.query_one_mut::<&mut Camera>(camera_ent).map(|mut cam| {
|
camera_system.resize_camera(game_state.world_mut(), sz.width, sz.height);
|
||||||
cam.aspect = sz.width as f32 / sz.height as f32;
|
event_bus.emit(GameEvent::WindowResize { width: sz.width, height: sz.height });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
if button == MouseButton::Right {
|
if button == MouseButton::Right {
|
||||||
|
|
@ -107,12 +124,13 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
// First render the 3D world
|
// First render the 3D world
|
||||||
let mut target = ecsr.renderer.display().draw();
|
let mut target = render_system_wrapper.display().draw();
|
||||||
ecsr.render_into(&mut target);
|
render_system_wrapper.render(game_state.world(), &mut target);
|
||||||
|
|
||||||
// Then overlay ImGui on top
|
// Then overlay ImGui on top
|
||||||
gui.render_with(&mut target, &window, |ui| {
|
gui.render_with(&mut target, &window, |ui| {
|
||||||
if let Ok(mut tr) = ecsr.world.query_one_mut::<&mut Transform>(object_ent) {
|
let object_entity = game_state.object_entity;
|
||||||
|
if let Ok(tr) = game_state.world_mut().query_one_mut::<&mut Transform>(object_entity) {
|
||||||
ui.text("Hold right click to control the camera");
|
ui.text("Hold right click to control the camera");
|
||||||
ui.text("WASD to move");
|
ui.text("WASD to move");
|
||||||
|
|
||||||
|
|
@ -146,24 +164,31 @@ fn main() -> Result<()> {
|
||||||
},
|
},
|
||||||
Event::AboutToWait => {
|
Event::AboutToWait => {
|
||||||
time.tick();
|
time.tick();
|
||||||
|
|
||||||
{
|
|
||||||
let dt = time.delta_seconds();
|
let dt = time.delta_seconds();
|
||||||
camera_controller.update(
|
|
||||||
&input,
|
|
||||||
dt,
|
|
||||||
right_mouse_held,
|
|
||||||
(Action::MoveForward, Action::MoveBackward, Action::MoveLeft, Action::MoveRight),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Ok(mut cam) = ecsr.world.query_one_mut::<&mut Camera>(camera_ent) {
|
// Update input system and generate events
|
||||||
cam.eye = camera_controller.position;
|
input_system.update(&mut event_bus, right_mouse_held);
|
||||||
cam.center = camera_controller.position + camera_controller.front();
|
|
||||||
|
// Process input events for camera movement
|
||||||
|
let mouse_delta = input_system.mouse_delta();
|
||||||
|
|
||||||
|
// Handle camera input actions
|
||||||
|
for event in event_bus.events() {
|
||||||
|
if let GameEvent::InputAction(action) = event {
|
||||||
|
camera_system.handle_input_action(*action, dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input.end_frame();
|
// Update camera with mouse movement
|
||||||
|
camera_system.update(game_state.world_mut(), dt, mouse_delta);
|
||||||
|
|
||||||
|
// Update game state
|
||||||
|
game_state.update(dt);
|
||||||
|
|
||||||
|
// Process all events
|
||||||
|
event_bus.process();
|
||||||
|
|
||||||
|
input_system.end_frame();
|
||||||
gui.prepare_frame(&window);
|
gui.prepare_frame(&window);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
116
raidillon_game/src/main_clean.rs
Normal file
116
raidillon_game/src/main_clean.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use raidillon_core::{Engine, GameEvent};
|
||||||
|
use raidillon_render::{init_render_window, RenderSystem};
|
||||||
|
use raidillon_ui::Gui;
|
||||||
|
use raidillon_input::{InputSystem, CameraSystem};
|
||||||
|
use winit::event::{Event, WindowEvent};
|
||||||
|
use winit::window::CursorGrabMode;
|
||||||
|
use winit::event::MouseButton;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let event_loop = winit::event_loop::EventLoop::builder()
|
||||||
|
.build()
|
||||||
|
.expect("create event-loop");
|
||||||
|
|
||||||
|
let (window, _display) = init_render_window(&event_loop, "raidillon", (1280, 720))?;
|
||||||
|
|
||||||
|
// Create the unified engine
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
// Create render system separately (for now, until full integration)
|
||||||
|
let mut render_system = RenderSystem::new(_display.clone())?;
|
||||||
|
|
||||||
|
// Create GUI system
|
||||||
|
let mut gui = Gui::new(&_display, &window)?;
|
||||||
|
|
||||||
|
// Add systems to the engine
|
||||||
|
let input_system = InputSystem::new();
|
||||||
|
let camera_system = CameraSystem::new(engine.world().spawn(())); // placeholder camera entity
|
||||||
|
|
||||||
|
engine.add_system(input_system);
|
||||||
|
engine.add_system(camera_system);
|
||||||
|
|
||||||
|
// Load initial scene content
|
||||||
|
load_default_scene(&mut engine, &mut render_system)?;
|
||||||
|
|
||||||
|
let mut right_mouse_held = false;
|
||||||
|
|
||||||
|
event_loop
|
||||||
|
.run(move |event, el| {
|
||||||
|
gui.handle_event(&window, &event);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent { event, .. } => match event {
|
||||||
|
WindowEvent::CloseRequested => el.exit(),
|
||||||
|
WindowEvent::Resized(sz) => {
|
||||||
|
engine.handle_window_event(&GameEvent::WindowResize {
|
||||||
|
width: sz.width,
|
||||||
|
height: sz.height
|
||||||
|
});
|
||||||
|
}
|
||||||
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
|
if button == MouseButton::Right {
|
||||||
|
match state {
|
||||||
|
winit::event::ElementState::Pressed => {
|
||||||
|
if window
|
||||||
|
.set_cursor_grab(CursorGrabMode::Confined)
|
||||||
|
.or_else(|_| window.set_cursor_grab(CursorGrabMode::Locked))
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
window.set_cursor_visible(false);
|
||||||
|
right_mouse_held = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
winit::event::ElementState::Released => {
|
||||||
|
let _ = window.set_cursor_grab(CursorGrabMode::None);
|
||||||
|
window.set_cursor_visible(true);
|
||||||
|
right_mouse_held = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
// Update engine
|
||||||
|
engine.update();
|
||||||
|
|
||||||
|
// Render
|
||||||
|
let mut target = render_system.display().draw();
|
||||||
|
render_system.render(engine.world(), &mut target);
|
||||||
|
|
||||||
|
// Render debug UI
|
||||||
|
gui.render_with(&mut target, &window, |ui| {
|
||||||
|
render_debug_ui(&engine, ui);
|
||||||
|
});
|
||||||
|
|
||||||
|
target.finish().expect("Failed to swap buffers");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Event::AboutToWait => {
|
||||||
|
gui.prepare_frame(&window);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_default_scene(engine: &mut Engine, render_system: &mut RenderSystem) -> Result<()> {
|
||||||
|
// Load and setup default scene
|
||||||
|
let _tree_model = render_system.load_model("resources/models/tree.gltf")?;
|
||||||
|
let _ground_model = render_system.load_model("resources/models/plane.gltf")?;
|
||||||
|
|
||||||
|
// Note: Full integration would require coordinating between engine and render system
|
||||||
|
// For now, this demonstrates the clean architecture structure
|
||||||
|
|
||||||
|
println!("Loaded default scene with {} systems", engine.system_count());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_debug_ui(engine: &Engine, ui: &imgui::Ui) {
|
||||||
|
ui.text(format!("Engine Systems: {}", engine.system_count()));
|
||||||
|
ui.text(format!("Delta Time: {:.3}ms", engine.delta_time() * 1000.0));
|
||||||
|
ui.text("Clean Architecture Demo");
|
||||||
|
ui.text("This shows the unified Engine approach");
|
||||||
|
}
|
||||||
|
|
@ -6,3 +6,6 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.30"
|
winit = "0.30"
|
||||||
glam = "0.30.4"
|
glam = "0.30.4"
|
||||||
|
hecs = "0.10.5"
|
||||||
|
raidillon_core = { path = "../raidillon_core" }
|
||||||
|
raidillon_render = { path = "../raidillon_render" }
|
||||||
109
raidillon_input/src/camera_system.rs
Normal file
109
raidillon_input/src/camera_system.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use hecs::{Entity, World};
|
||||||
|
use raidillon_core::{EventHandler, GameEvent, InputAction, System, AssetManager, Model, Material, EventBus};
|
||||||
|
use raidillon_render::Camera;
|
||||||
|
use crate::FPSCameraController;
|
||||||
|
use glam::Vec3;
|
||||||
|
|
||||||
|
pub struct CameraSystem {
|
||||||
|
controller: FPSCameraController,
|
||||||
|
camera_entity: Entity,
|
||||||
|
yaw: f32,
|
||||||
|
pitch: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraSystem {
|
||||||
|
pub fn new(camera_entity: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
controller: FPSCameraController::new(Vec3::new(0.0, 0.0, 2.0)),
|
||||||
|
camera_entity,
|
||||||
|
yaw: -90.0,
|
||||||
|
pitch: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, world: &mut World, dt: f32, mouse_delta: (f64, f64)) {
|
||||||
|
// Apply mouse movement if there's any
|
||||||
|
if mouse_delta.0 != 0.0 || mouse_delta.1 != 0.0 {
|
||||||
|
self.yaw += mouse_delta.0 as f32 * self.controller.sensitivity;
|
||||||
|
self.pitch -= mouse_delta.1 as f32 * self.controller.sensitivity;
|
||||||
|
self.pitch = self.pitch.clamp(-89.0, 89.0);
|
||||||
|
|
||||||
|
// Update front vector based on new yaw/pitch
|
||||||
|
let yaw_rad = self.yaw.to_radians();
|
||||||
|
let pitch_rad = self.pitch.to_radians();
|
||||||
|
let front = Vec3::new(
|
||||||
|
yaw_rad.cos() * pitch_rad.cos(),
|
||||||
|
pitch_rad.sin(),
|
||||||
|
yaw_rad.sin() * pitch_rad.cos(),
|
||||||
|
).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update camera component in the world
|
||||||
|
if let Ok(cam) = world.query_one_mut::<&mut Camera>(self.camera_entity) {
|
||||||
|
cam.eye = self.controller.position;
|
||||||
|
cam.center = self.controller.position + self.controller.front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_input_action(&mut self, action: InputAction, dt: f32) {
|
||||||
|
let front = self.controller.front();
|
||||||
|
let right = front.cross(Vec3::Y).normalize();
|
||||||
|
let frame_speed = self.controller.speed * dt;
|
||||||
|
|
||||||
|
match action {
|
||||||
|
InputAction::MoveForward => {
|
||||||
|
self.controller.position += front * frame_speed;
|
||||||
|
}
|
||||||
|
InputAction::MoveBackward => {
|
||||||
|
self.controller.position -= front * frame_speed;
|
||||||
|
}
|
||||||
|
InputAction::MoveLeft => {
|
||||||
|
self.controller.position -= right * frame_speed;
|
||||||
|
}
|
||||||
|
InputAction::MoveRight => {
|
||||||
|
self.controller.position += right * frame_speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize_camera(&mut self, world: &mut World, width: u32, height: u32) {
|
||||||
|
if let Ok(cam) = world.query_one_mut::<&mut Camera>(self.camera_entity) {
|
||||||
|
cam.aspect = width as f32 / height as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventHandler for CameraSystem {
|
||||||
|
fn handle(&mut self, event: &GameEvent) {
|
||||||
|
match event {
|
||||||
|
GameEvent::InputAction(_action) => {
|
||||||
|
// Movement will be handled separately with delta time
|
||||||
|
// This is just for event registration
|
||||||
|
}
|
||||||
|
GameEvent::WindowResize { width: _, height: _ } => {
|
||||||
|
// Window resize will be handled separately with world access
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System for CameraSystem {
|
||||||
|
fn update(&mut self, world: &mut World, _resources: &AssetManager<dyn Model, dyn Material>, _events: &mut EventBus, _dt: f32) {
|
||||||
|
// Camera update logic is handled separately with mouse input
|
||||||
|
// This system mainly responds to events
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, event: &GameEvent, world: &mut World) {
|
||||||
|
match event {
|
||||||
|
GameEvent::WindowResize { width, height } => {
|
||||||
|
self.resize_camera(world, *width, *height);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"CameraSystem"
|
||||||
|
}
|
||||||
|
}
|
||||||
64
raidillon_input/src/input_system.rs
Normal file
64
raidillon_input/src/input_system.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
use raidillon_core::{EventBus, GameEvent, InputAction, System, AssetManager, Model, Material};
|
||||||
|
use crate::Input;
|
||||||
|
use hecs::World;
|
||||||
|
|
||||||
|
pub struct InputSystem {
|
||||||
|
input: Input<InputAction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut input = Input::<InputAction>::new();
|
||||||
|
input.map_key(winit::keyboard::KeyCode::KeyW, InputAction::MoveForward);
|
||||||
|
input.map_key(winit::keyboard::KeyCode::KeyS, InputAction::MoveBackward);
|
||||||
|
input.map_key(winit::keyboard::KeyCode::KeyA, InputAction::MoveLeft);
|
||||||
|
input.map_key(winit::keyboard::KeyCode::KeyD, InputAction::MoveRight);
|
||||||
|
|
||||||
|
Self { input }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_event<T>(&mut self, event: &winit::event::Event<T>) {
|
||||||
|
self.input.handle_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, event_bus: &mut EventBus, right_mouse_held: bool) {
|
||||||
|
if right_mouse_held {
|
||||||
|
if self.input.action_held(InputAction::MoveForward) {
|
||||||
|
event_bus.emit(GameEvent::InputAction(InputAction::MoveForward));
|
||||||
|
}
|
||||||
|
if self.input.action_held(InputAction::MoveBackward) {
|
||||||
|
event_bus.emit(GameEvent::InputAction(InputAction::MoveBackward));
|
||||||
|
}
|
||||||
|
if self.input.action_held(InputAction::MoveLeft) {
|
||||||
|
event_bus.emit(GameEvent::InputAction(InputAction::MoveLeft));
|
||||||
|
}
|
||||||
|
if self.input.action_held(InputAction::MoveRight) {
|
||||||
|
event_bus.emit(GameEvent::InputAction(InputAction::MoveRight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_frame(&mut self) {
|
||||||
|
self.input.end_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_delta(&self) -> (f64, f64) {
|
||||||
|
self.input.mouse_delta()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System for InputSystem {
|
||||||
|
fn update(&mut self, _world: &mut World, _resources: &AssetManager<dyn Model, dyn Material>, _events: &mut EventBus, _dt: f32) {
|
||||||
|
// Input processing is handled separately in the main loop
|
||||||
|
// This system mainly generates events based on input state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, _event: &GameEvent, _world: &mut World) {
|
||||||
|
// InputSystem doesn't need to respond to events
|
||||||
|
// It generates events based on input state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"InputSystem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,12 @@ use winit::event::{DeviceEvent, ElementState, Event, WindowEvent};
|
||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
|
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
|
pub mod input_system;
|
||||||
|
pub mod camera_system;
|
||||||
|
|
||||||
pub use camera::FPSCameraController;
|
pub use camera::FPSCameraController;
|
||||||
|
pub use input_system::InputSystem;
|
||||||
|
pub use camera_system::CameraSystem;
|
||||||
|
|
||||||
pub struct Input<A: Copy + Eq + Hash> {
|
pub struct Input<A: Copy + Eq + Hash> {
|
||||||
pressed_keys: HashSet<KeyCode>,
|
pressed_keys: HashSet<KeyCode>,
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ gltf = { version = "1.4.1", features = ["import", "utils", "KHR_texture_transf
|
||||||
glutin = { version = "0.32.3", default-features = false }
|
glutin = { version = "0.32.3", default-features = false }
|
||||||
hecs = "0.10.5"
|
hecs = "0.10.5"
|
||||||
image = "0.25.6"
|
image = "0.25.6"
|
||||||
|
raidillon_core = { path = "../raidillon_core" }
|
||||||
raidillon_ecs = { path = "../raidillon_ecs" }
|
raidillon_ecs = { path = "../raidillon_ecs" }
|
||||||
winit = "0.30"
|
winit = "0.30"
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@ pub mod model;
|
||||||
pub mod gltf_loader;
|
pub mod gltf_loader;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
pub mod ecs_renderer;
|
pub mod ecs_renderer;
|
||||||
|
pub mod render_system;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
pub use camera::Camera;
|
pub use camera::Camera;
|
||||||
pub use render::GliumRenderer;
|
pub use render::GliumRenderer;
|
||||||
pub use ecs_renderer::ECSRenderer;
|
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};
|
pub use window::{DisplayHandle, init_window as init_render_window};
|
||||||
|
|
|
||||||
|
|
@ -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 struct Model {
|
||||||
pub mesh: Mesh,
|
pub mesh: Mesh,
|
||||||
pub material: Material,
|
pub material: Material,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implement the Model trait from raidillon_core
|
||||||
|
impl raidillon_core::Model for Model {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::camera::Camera;
|
use crate::camera::Camera;
|
||||||
use raidillon_ecs::{ModelHandle, Transform};
|
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::texture::{RawImage2d, SrgbTexture2d};
|
||||||
use glium::{uniform, Program, Surface};
|
use glium::{uniform, Program, Surface};
|
||||||
use glium::uniforms::{MinifySamplerFilter, MagnifySamplerFilter, SamplerWrapFunction};
|
use glium::uniforms::{MinifySamplerFilter, MagnifySamplerFilter, SamplerWrapFunction};
|
||||||
|
|
@ -164,4 +165,94 @@ impl GliumRenderer {
|
||||||
pub fn display(&self) -> &glium::Display<WindowSurface> {
|
pub fn display(&self) -> &glium::Display<WindowSurface> {
|
||||||
&self.display
|
&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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
50
raidillon_render/src/render_system.rs
Normal file
50
raidillon_render/src/render_system.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue