o3 refactor
This commit is contained in:
parent
341d531db3
commit
049f522bb1
19 changed files with 508 additions and 214 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
|
@ -811,9 +811,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e1cbc675ee8d97b4d206a985137f8ad59666538f56f906474f554467a63c776d"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"hecs-macros",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hecs-macros"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052fc25b12dc326082605cd2098eb76050a72fa0c0e9ea7faaa3f58b565fc970"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
|
|
@ -1728,6 +1740,14 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|||
[[package]]
|
||||
name = "raidillon_core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glam",
|
||||
"glium",
|
||||
"hecs",
|
||||
"raidillon_ecs",
|
||||
"raidillon_render",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raidillon_ecs"
|
||||
|
|
@ -1743,6 +1763,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"glam",
|
||||
"glium",
|
||||
"hecs",
|
||||
"raidillon_core",
|
||||
"raidillon_ecs",
|
||||
|
|
@ -1757,6 +1778,9 @@ name = "raidillon_input"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"hecs",
|
||||
"raidillon_core",
|
||||
"raidillon_render",
|
||||
"winit",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,3 +2,11 @@
|
|||
name = "raidillon_core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
glam = "0.30.4"
|
||||
hecs = "0.10.5"
|
||||
raidillon_render = { path = "../raidillon_render" }
|
||||
glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] }
|
||||
raidillon_ecs = { path = "../raidillon_ecs" }
|
||||
anyhow = "1.0.98"
|
||||
|
|
|
|||
42
raidillon_core/src/assets.rs
Normal file
42
raidillon_core/src/assets.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use std::collections::HashMap;
|
||||
use raidillon_render::model::Model;
|
||||
use glium::glutin::surface::WindowSurface;
|
||||
use glium::Display;
|
||||
use raidillon_render::gltf_loader;
|
||||
use raidillon_ecs::ModelId;
|
||||
use raidillon_render::render_system::ModelProvider;
|
||||
use anyhow::Result;
|
||||
|
||||
pub struct AssetManager {
|
||||
models: Vec<Model>,
|
||||
model_cache: HashMap<String, ModelId>,
|
||||
}
|
||||
|
||||
impl AssetManager {
|
||||
pub fn new() -> Self {
|
||||
Self { models: Vec::new(), model_cache: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Load or retrieve a cached model, returning its `ModelId`.
|
||||
pub fn load_model<P: AsRef<str>>(&mut self, path: P, display: &Display<WindowSurface>) -> Result<ModelId> {
|
||||
let path_str = path.as_ref();
|
||||
if let Some(&id) = self.model_cache.get(path_str) {
|
||||
return Ok(id);
|
||||
}
|
||||
let model = gltf_loader::load_gltf(path_str, display)?;
|
||||
let id = ModelId(self.models.len());
|
||||
self.models.push(model);
|
||||
self.model_cache.insert(path_str.to_string(), id);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn get_model(&self, id: ModelId) -> Option<&Model> {
|
||||
self.models.get(id.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelProvider for AssetManager {
|
||||
fn get_model(&self, id: ModelId) -> Option<&Model> {
|
||||
self.get_model(id)
|
||||
}
|
||||
}
|
||||
59
raidillon_core/src/events.rs
Normal file
59
raidillon_core/src/events.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::any::TypeId;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Core event enumeration.
|
||||
/// Generic over the `Action` type to keep the engine agnostic to concrete
|
||||
/// game-specific input enumerations.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GameEvent<A> {
|
||||
InputAction(A),
|
||||
CameraMove { position: glam::Vec3, front: glam::Vec3 },
|
||||
WindowResize { width: u32, height: u32 },
|
||||
EntitySpawned(hecs::Entity),
|
||||
}
|
||||
|
||||
pub trait EventHandler<A>: 'static {
|
||||
fn handle(&mut self, event: &GameEvent<A>);
|
||||
}
|
||||
|
||||
pub struct EventBus<A> {
|
||||
events: Vec<GameEvent<A>>,
|
||||
subscribers: HashMap<TypeId, Vec<Box<dyn EventHandler<A>>>>,
|
||||
}
|
||||
|
||||
impl<A: 'static + Clone> EventBus<A> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
events: Vec::new(),
|
||||
subscribers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe<H: EventHandler<A> + 'static>(&mut self, handler: H) {
|
||||
self.subscribers
|
||||
.entry(TypeId::of::<H>())
|
||||
.or_default()
|
||||
.push(Box::new(handler));
|
||||
}
|
||||
|
||||
pub fn emit(&mut self, event: GameEvent<A>) {
|
||||
self.events.push(event);
|
||||
}
|
||||
|
||||
/// Process all queued events, dispatching them to every registered
|
||||
/// subscriber.
|
||||
pub fn process(&mut self) {
|
||||
let events = std::mem::take(&mut self.events);
|
||||
for ev in &events {
|
||||
for subs in self.subscribers.values_mut() {
|
||||
for h in subs {
|
||||
h.handle(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drain(&mut self) -> Vec<GameEvent<A>> {
|
||||
std::mem::take(&mut self.events)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,14 @@
|
|||
pub mod time;
|
||||
|
||||
pub use time::Time;
|
||||
|
||||
pub mod events;
|
||||
|
||||
pub use events::{EventBus, GameEvent, EventHandler};
|
||||
|
||||
pub mod assets;
|
||||
|
||||
pub use assets::AssetManager;
|
||||
|
||||
pub mod systems;
|
||||
pub use systems::{System, SystemRegistry};
|
||||
|
|
|
|||
33
raidillon_core/src/systems.rs
Normal file
33
raidillon_core/src/systems.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use hecs::World;
|
||||
|
||||
use crate::{AssetManager, EventBus};
|
||||
|
||||
/// A game/engine system that updates every frame.
|
||||
pub trait System<A> {
|
||||
/// Update the system for the current frame.
|
||||
///
|
||||
/// * `world` – mutable ECS world
|
||||
/// * `assets` – read-only resource manager
|
||||
/// * `events` – event bus for publishing/consuming game events
|
||||
/// * `dt` – time delta in seconds
|
||||
fn update(&mut self, world: &mut World, assets: &AssetManager, events: &mut EventBus<A>, dt: f32);
|
||||
}
|
||||
|
||||
/// Stores and updates a collection of boxed systems.
|
||||
pub struct SystemRegistry<A> {
|
||||
systems: Vec<Box<dyn System<A>>>,
|
||||
}
|
||||
|
||||
impl<A> SystemRegistry<A> {
|
||||
pub fn new() -> Self { Self { systems: Vec::new() } }
|
||||
|
||||
pub fn add_system<S: System<A> + 'static>(&mut self, sys: S) {
|
||||
self.systems.push(Box::new(sys));
|
||||
}
|
||||
|
||||
pub fn update_all(&mut self, world: &mut World, assets: &AssetManager, events: &mut EventBus<A>, dt: f32) {
|
||||
for s in &mut self.systems {
|
||||
s.update(world, assets, events, dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,5 +4,5 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
hecs = { version = "0.10.5", features = ["macros"] }
|
||||
glam = "0.30.4"
|
||||
hecs = "0.10.5"
|
||||
|
|
|
|||
|
|
@ -13,5 +13,7 @@ impl Transform {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ModelHandle(pub usize);
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ModelId(pub usize);
|
||||
|
||||
pub type ModelHandle = ModelId;
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@ raidillon_ui = { path = "../raidillon_ui" }
|
|||
raidillon_core = { path = "../raidillon_core" }
|
||||
hecs = "0.10.5"
|
||||
raidillon_input = { path = "../raidillon_input" }
|
||||
glium = { version = "0.35.0", features = ["glutin_backend", "simple_window_builder"] }
|
||||
|
|
|
|||
90
raidillon_game/src/engine.rs
Normal file
90
raidillon_game/src/engine.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
use hecs::{World, Entity};
|
||||
use glium::Surface;
|
||||
use raidillon_render::{RenderSystem, window::DisplayHandle, Camera};
|
||||
use raidillon_input::{InputSystem, CameraSystem, Action};
|
||||
use raidillon_core::{Time, AssetManager, EventBus, SystemRegistry};
|
||||
use glam::{Vec3, Quat};
|
||||
|
||||
pub struct Engine {
|
||||
world: World,
|
||||
assets: AssetManager,
|
||||
events: EventBus<Action>,
|
||||
systems: SystemRegistry<Action>,
|
||||
render_system: RenderSystem,
|
||||
input_system: InputSystem,
|
||||
time: Time,
|
||||
camera_entity: Entity,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new(display: &DisplayHandle) -> anyhow::Result<Self> {
|
||||
let mut world = World::new();
|
||||
let mut assets = AssetManager::new();
|
||||
let events = EventBus::new();
|
||||
let mut systems = SystemRegistry::new();
|
||||
let render_system = RenderSystem::new(display.clone())?;
|
||||
let input_system = InputSystem::new();
|
||||
let time = Time::new();
|
||||
|
||||
let tree_model = assets.load_model("resources/models/tree.gltf", render_system.display())?;
|
||||
let ground_model = assets.load_model("resources/models/plane.gltf", render_system.display())?;
|
||||
|
||||
world.spawn((raidillon_ecs::Transform {
|
||||
translation: Vec3::new(0.0, -2.5, -5.0),
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::splat(0.01),
|
||||
}, tree_model));
|
||||
|
||||
world.spawn((raidillon_ecs::Transform {
|
||||
translation: Vec3::new(0.0, -1.5, 0.0),
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::ONE,
|
||||
}, ground_model));
|
||||
|
||||
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,
|
||||
znear: 0.1,
|
||||
zfar: 100.0,
|
||||
},));
|
||||
|
||||
systems.add_system(CameraSystem::new(camera_entity));
|
||||
|
||||
Ok(Self {
|
||||
world,
|
||||
assets,
|
||||
events,
|
||||
systems,
|
||||
render_system,
|
||||
input_system,
|
||||
time,
|
||||
camera_entity,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn handle_event<T>(&mut self, event: &winit::event::Event<T>) {
|
||||
self.input_system.handle_event(event);
|
||||
if let winit::event::Event::WindowEvent { event, .. } = event {
|
||||
if let winit::event::WindowEvent::Resized(sz) = event {
|
||||
if let Ok(mut cam) = self.world.query_one_mut::<&mut Camera>(self.camera_entity) {
|
||||
cam.aspect = sz.width as f32 / sz.height as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
self.time.tick();
|
||||
let dt = self.time.delta_seconds();
|
||||
self.input_system.update(&mut self.events);
|
||||
self.systems.update_all(&mut self.world, &self.assets, &mut self.events, dt);
|
||||
let _ = self.events.drain();
|
||||
}
|
||||
|
||||
pub fn render_into<S: Surface>(&mut self, target: &mut S) {
|
||||
self.render_system.render_into(&self.world, &self.assets, target);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,168 +1,44 @@
|
|||
use anyhow::Result;
|
||||
use glam::{Quat, Vec3, EulerRot};
|
||||
use raidillon_core::Time;
|
||||
use raidillon_ecs::Transform;
|
||||
use raidillon_render::{Camera, ECSRenderer, init_render_window, DisplayHandle};
|
||||
use raidillon_render::{init_render_window, DisplayHandle};
|
||||
use raidillon_ui::Gui;
|
||||
use raidillon_input::{Input, FPSCameraController};
|
||||
use winit::keyboard::KeyCode;
|
||||
use winit::window::CursorGrabMode;
|
||||
use winit::event::MouseButton;
|
||||
mod engine;
|
||||
use crate::engine::Engine;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
enum Action {
|
||||
MoveForward,
|
||||
MoveBackward,
|
||||
MoveLeft,
|
||||
MoveRight,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let event_loop = winit::event_loop::EventLoop::builder()
|
||||
.build()
|
||||
.expect("create event-loop");
|
||||
|
||||
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
|
||||
let mut ecsr = ECSRenderer::from_display_handle(&_display)?;
|
||||
let mut engine = Engine::new(&display)?;
|
||||
|
||||
// Dear ImGui integration
|
||||
let mut gui = Gui::new(&_display, &window)?;
|
||||
|
||||
let mut input = Input::<Action>::new();
|
||||
input.map_key(KeyCode::KeyW, Action::MoveForward);
|
||||
input.map_key(KeyCode::KeyS, Action::MoveBackward);
|
||||
input.map_key(KeyCode::KeyA, Action::MoveLeft);
|
||||
input.map_key(KeyCode::KeyD, Action::MoveRight);
|
||||
|
||||
let mut camera_controller = FPSCameraController::new(Vec3::new(0.0, 0.0, 2.0));
|
||||
|
||||
let mut right_mouse_held = false;
|
||||
|
||||
let mut time = Time::new();
|
||||
|
||||
let object_ent = ecsr.load_mesh_from_gltf("resources/models/tree.gltf", Transform {
|
||||
translation: Vec3::new(0.0, -2.5, -5.0),
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::new(0.01, 0.01, 0.01),
|
||||
})?;
|
||||
|
||||
let ground_ent = ecsr.load_mesh_from_gltf("resources/models/plane.gltf", Transform {
|
||||
translation: Vec3::new(0.0, -1.5, 0.0),
|
||||
rotation: Quat::IDENTITY,
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
})?;
|
||||
|
||||
|
||||
let camera_ent = {
|
||||
let (w, h): (u32, u32) = window.inner_size().into();
|
||||
ecsr.world.spawn((Camera {
|
||||
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,
|
||||
},))
|
||||
};
|
||||
let mut gui = Gui::new(&display, &window)?;
|
||||
|
||||
event_loop
|
||||
.run(move |event, el| {
|
||||
use winit::event::{Event, WindowEvent};
|
||||
use winit::event::{Event};
|
||||
|
||||
gui.handle_event(&window, &event);
|
||||
|
||||
input.handle_event(&event);
|
||||
engine.handle_event(&event);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent { event, .. } => match event {
|
||||
WindowEvent::CloseRequested => el.exit(),
|
||||
WindowEvent::Resized(sz) => {
|
||||
ecsr.world.query_one_mut::<&mut Camera>(camera_ent).map(|mut cam| {
|
||||
cam.aspect = sz.width as f32 / sz.height as f32;
|
||||
});
|
||||
}
|
||||
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;
|
||||
Event::WindowEvent { event, .. } => {
|
||||
if let winit::event::WindowEvent::CloseRequested = event {
|
||||
el.exit();
|
||||
}
|
||||
}
|
||||
winit::event::ElementState::Released => {
|
||||
let _ = window.set_cursor_grab(CursorGrabMode::None);
|
||||
window.set_cursor_visible(true);
|
||||
right_mouse_held = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::RedrawRequested => {
|
||||
// First render the 3D world
|
||||
let mut target = ecsr.renderer.display().draw();
|
||||
ecsr.render_into(&mut target);
|
||||
|
||||
// Then overlay ImGui on top
|
||||
gui.render_with(&mut target, &window, |ui| {
|
||||
if let Ok(mut tr) = ecsr.world.query_one_mut::<&mut Transform>(object_ent) {
|
||||
ui.text("Hold right click to control the camera");
|
||||
ui.text("WASD to move");
|
||||
|
||||
// Translation controls
|
||||
let mut translation = [tr.translation.x, tr.translation.y, tr.translation.z];
|
||||
if ui.input_float3("Translation", &mut translation).build() {
|
||||
tr.translation = Vec3::from(translation);
|
||||
}
|
||||
|
||||
// Scale controls
|
||||
let mut scale = [tr.scale.x, tr.scale.y, tr.scale.z];
|
||||
if ui.input_float3("Scale", &mut scale).build() {
|
||||
tr.scale = Vec3::from(scale);
|
||||
}
|
||||
|
||||
// Rotation controls
|
||||
let (yaw, pitch, roll) = tr.rotation.to_euler(EulerRot::YXZ);
|
||||
let mut rotation_deg = [yaw.to_degrees(), pitch.to_degrees(), roll.to_degrees()];
|
||||
if ui.input_float3("Rotation (deg)", &mut rotation_deg).build() {
|
||||
let yaw = rotation_deg[0].to_radians();
|
||||
let pitch = rotation_deg[1].to_radians();
|
||||
let roll = rotation_deg[2].to_radians();
|
||||
tr.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
target.finish().expect("Failed to swap buffers");
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Event::WindowEvent { .. } => {}
|
||||
Event::AboutToWait => {
|
||||
time.tick();
|
||||
engine.update();
|
||||
|
||||
{
|
||||
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) {
|
||||
cam.eye = camera_controller.position;
|
||||
cam.center = camera_controller.position + camera_controller.front();
|
||||
}
|
||||
}
|
||||
|
||||
input.end_frame();
|
||||
// Render
|
||||
let mut target = display.as_inner().draw();
|
||||
engine.render_into(&mut target);
|
||||
gui.render_with(&mut target, &window, |_| {});
|
||||
target.finish().unwrap();
|
||||
|
||||
gui.prepare_frame(&window);
|
||||
window.request_redraw();
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ edition = "2021"
|
|||
[dependencies]
|
||||
winit = "0.30"
|
||||
glam = "0.30.4"
|
||||
raidillon_core = { path = "../raidillon_core" }
|
||||
hecs = "0.10.5"
|
||||
raidillon_render = { path = "../raidillon_render" }
|
||||
57
raidillon_input/src/camera_system.rs
Normal file
57
raidillon_input/src/camera_system.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use glam::Vec3;
|
||||
use hecs::World;
|
||||
|
||||
use crate::camera::FPSCameraController;
|
||||
use crate::Action;
|
||||
use raidillon_core::{System, AssetManager, EventHandler, GameEvent};
|
||||
use raidillon_render::Camera;
|
||||
|
||||
pub struct CameraSystem {
|
||||
controller: FPSCameraController,
|
||||
camera_entity: hecs::Entity,
|
||||
}
|
||||
|
||||
impl CameraSystem {
|
||||
pub fn new(camera_entity: hecs::Entity) -> Self {
|
||||
Self {
|
||||
controller: FPSCameraController::new(Vec3::new(0.0, 0.0, 2.0)),
|
||||
camera_entity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, world: &mut World, dt: f32) {
|
||||
// After processing events, write camera pose back to ECS component.
|
||||
if let Ok(mut cam) = world.query_one_mut::<&mut Camera>(self.camera_entity) {
|
||||
cam.eye = self.controller.position;
|
||||
cam.center = self.controller.position + self.controller.front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl System<crate::Action> for CameraSystem {
|
||||
fn update(&mut self, world: &mut World, _assets: &AssetManager, _events: &mut raidillon_core::EventBus<crate::Action>, dt: f32) {
|
||||
self.update(world, dt);
|
||||
}
|
||||
}
|
||||
|
||||
impl EventHandler<Action> for CameraSystem {
|
||||
fn handle(&mut self, event: &GameEvent<Action>) {
|
||||
match event {
|
||||
GameEvent::InputAction(action) => {
|
||||
match action {
|
||||
Action::MoveForward => self.controller.position += self.controller.front() * 0.1,
|
||||
Action::MoveBackward => self.controller.position -= self.controller.front() * 0.1,
|
||||
Action::MoveLeft => {
|
||||
let right = self.controller.front().cross(Vec3::Y).normalize();
|
||||
self.controller.position -= right * 0.1;
|
||||
}
|
||||
Action::MoveRight => {
|
||||
let right = self.controller.front().cross(Vec3::Y).normalize();
|
||||
self.controller.position += right * 0.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
raidillon_input/src/input_system.rs
Normal file
44
raidillon_input/src/input_system.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use crate::{Input, Action};
|
||||
use raidillon_core::{System, AssetManager};
|
||||
use hecs::World;
|
||||
use raidillon_core::EventBus;
|
||||
use raidillon_core::GameEvent;
|
||||
|
||||
pub struct InputSystem {
|
||||
input: Input<Action>,
|
||||
}
|
||||
|
||||
impl InputSystem {
|
||||
pub fn new() -> Self {
|
||||
let mut input = Input::<Action>::new();
|
||||
use winit::keyboard::KeyCode;
|
||||
input.map_key(KeyCode::KeyW, Action::MoveForward);
|
||||
input.map_key(KeyCode::KeyS, Action::MoveBackward);
|
||||
input.map_key(KeyCode::KeyA, Action::MoveLeft);
|
||||
input.map_key(KeyCode::KeyD, Action::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, bus: &mut EventBus<Action>) {
|
||||
for action in [Action::MoveForward, Action::MoveBackward, Action::MoveLeft, Action::MoveRight] {
|
||||
if self.input.action_held(action) {
|
||||
bus.emit(GameEvent::InputAction(action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.input.end_frame();
|
||||
}
|
||||
}
|
||||
|
||||
impl System<crate::Action> for InputSystem {
|
||||
fn update(&mut self, _world: &mut World, _assets: &AssetManager, events: &mut raidillon_core::EventBus<crate::Action>, _dt: f32) {
|
||||
self.update(events);
|
||||
self.end_frame();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,23 @@ use std::hash::Hash;
|
|||
use winit::event::{DeviceEvent, ElementState, Event, WindowEvent};
|
||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub enum Action {
|
||||
MoveForward,
|
||||
MoveBackward,
|
||||
MoveLeft,
|
||||
MoveRight,
|
||||
}
|
||||
|
||||
pub mod input_system;
|
||||
pub use input_system::InputSystem;
|
||||
|
||||
pub mod camera;
|
||||
pub use camera::FPSCameraController;
|
||||
|
||||
pub mod camera_system;
|
||||
pub use camera_system::CameraSystem;
|
||||
|
||||
pub struct Input<A: Copy + Eq + Hash> {
|
||||
pressed_keys: HashSet<KeyCode>,
|
||||
pressed_once: HashSet<KeyCode>,
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
use raidillon_ecs::{Transform, ModelHandle};
|
||||
use hecs::{Entity, World};
|
||||
use crate::render::GliumRenderer;
|
||||
use crate::model::Model;
|
||||
|
||||
/// This system joins the renderer and ECS,
|
||||
/// and provides tools to use them together
|
||||
/// effectively.
|
||||
pub struct ECSRenderer {
|
||||
pub renderer: GliumRenderer,
|
||||
pub world: World,
|
||||
}
|
||||
|
||||
impl ECSRenderer {
|
||||
pub fn from_display_handle(handle: &crate::window::DisplayHandle) -> anyhow::Result<Self> {
|
||||
let world = World::new();
|
||||
let renderer = crate::render::GliumRenderer::new(handle.as_inner().clone())?;
|
||||
Ok(Self { renderer, world })
|
||||
}
|
||||
pub fn new(renderer: GliumRenderer, world: World) -> Self {
|
||||
Self { renderer, world }
|
||||
}
|
||||
|
||||
pub fn spawn_mesh(&mut self, model: Model, transform: Transform) -> Entity {
|
||||
let model_id = self.renderer.models.len();
|
||||
self.renderer.models.push(model);
|
||||
|
||||
self.world.spawn((
|
||||
transform,
|
||||
ModelHandle(model_id),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn despawn_mesh(&mut self, entity: Entity) {
|
||||
if let Ok(model_handle) = self.world.get::<&ModelHandle>(entity) {
|
||||
if model_handle.0 < self.renderer.models.len() {
|
||||
self.renderer.models.remove(model_handle.0);
|
||||
}
|
||||
}
|
||||
let _ = self.world.despawn(entity);
|
||||
}
|
||||
|
||||
/// Render a single frame using the internal renderer & world.
|
||||
pub fn render(&mut self) {
|
||||
self.renderer.render(&self.world);
|
||||
}
|
||||
|
||||
/// Render into an existing glium target surface. Useful for composing with
|
||||
/// other render passes (e.g. Dear ImGui).
|
||||
pub fn render_into<S: glium::Surface>(&mut self, target: &mut S) {
|
||||
self.renderer.render_into(&self.world, target);
|
||||
}
|
||||
|
||||
pub fn load_mesh_from_gltf<P: AsRef<std::path::Path> + std::fmt::Debug>(
|
||||
&mut self,
|
||||
path: P,
|
||||
transform: Transform,
|
||||
) -> anyhow::Result<Entity> {
|
||||
let model = crate::gltf_loader::load_gltf(path, self.renderer.display())?;
|
||||
Ok(self.spawn_mesh(model, transform))
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ pub mod camera;
|
|||
pub mod model;
|
||||
pub mod gltf_loader;
|
||||
pub mod render;
|
||||
pub mod ecs_renderer;
|
||||
pub mod window;
|
||||
pub mod render_system;
|
||||
|
||||
pub use camera::Camera;
|
||||
pub use render::GliumRenderer;
|
||||
pub use ecs_renderer::ECSRenderer;
|
||||
pub use window::{DisplayHandle, init_window as init_render_window};
|
||||
pub use render_system::RenderSystem;
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ use glium::draw_parameters::DepthTest;
|
|||
|
||||
pub struct GliumRenderer {
|
||||
display: glium::Display<WindowSurface>,
|
||||
program: Program,
|
||||
white_tex: SrgbTexture2d,
|
||||
pub(crate) program: Program,
|
||||
pub(crate) white_tex: SrgbTexture2d,
|
||||
|
||||
pub models: Vec<Model>,
|
||||
|
||||
params: glium::DrawParameters<'static>,
|
||||
pub(crate) params: glium::DrawParameters<'static>,
|
||||
|
||||
skybox_program: Program,
|
||||
skybox_texture: SrgbTexture2d,
|
||||
|
|
|
|||
92
raidillon_render/src/render_system.rs
Normal file
92
raidillon_render/src/render_system.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use crate::render::GliumRenderer;
|
||||
use glium::Surface;
|
||||
use hecs::World;
|
||||
use raidillon_ecs::ModelId;
|
||||
use crate::model::Model;
|
||||
|
||||
pub trait ModelProvider {
|
||||
fn get_model(&self, id: ModelId) -> Option<&Model>;
|
||||
}
|
||||
|
||||
/// Pure render system that owns the low-level renderer but **not** the ECS
|
||||
/// world, allowing it to be plugged into any external world.
|
||||
pub struct RenderSystem {
|
||||
renderer: GliumRenderer,
|
||||
}
|
||||
|
||||
impl RenderSystem {
|
||||
/// Construct a RenderSystem from a window `DisplayHandle`.
|
||||
pub fn new(display: crate::window::DisplayHandle) -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
renderer: GliumRenderer::new(display.as_inner().clone())?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Render the given `world` into an arbitrary glium surface.
|
||||
pub fn render_into<S: Surface, P: ModelProvider>(&mut self, world: &World, assets: &P, target: &mut S) {
|
||||
// delegate to custom draw that uses assets
|
||||
self.draw_scene(world, assets, target);
|
||||
}
|
||||
|
||||
pub fn render<S: Surface, P: ModelProvider>(&mut self, world: &World, assets: &P) {
|
||||
let mut frame = self.renderer.display().draw();
|
||||
self.draw_scene(world, assets, &mut frame);
|
||||
frame.finish().unwrap();
|
||||
}
|
||||
|
||||
/// Load model via AssetManager caching.
|
||||
pub fn load_model<P: AsRef<str>, A: ModelProvider + ?Sized>( &self, path: P, assets: &mut A ) -> anyhow::Result<ModelId> where A: crate::render_system::ModelProvider {
|
||||
// cannot implement generic load here without knowing concrete; will leave stub not used.
|
||||
anyhow::bail!("Not implemented - load via AssetManager in core");
|
||||
}
|
||||
|
||||
/// Expose the underlying display (useful for ImGui, etc.).
|
||||
pub fn display(&self) -> &glium::Display<glium::glutin::surface::WindowSurface> {
|
||||
self.renderer.display()
|
||||
}
|
||||
|
||||
fn draw_scene<S: Surface, P: ModelProvider>(&self, world: &World, assets: &P, target: &mut S) {
|
||||
// replicate old GliumRenderer::draw_scene but using assets
|
||||
use glium::{uniform, uniforms::{MinifySamplerFilter, MagnifySamplerFilter, SamplerWrapFunction}};
|
||||
use glam::{Vec3, Vec4};
|
||||
use raidillon_ecs::{Transform};
|
||||
|
||||
let cam = match world.query::<&crate::camera::Camera>().iter().next() {
|
||||
Some((_, cam)) => *cam,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let light_dir: Vec3 = Vec3::new(0.0, -1.0, 0.0).normalize();
|
||||
|
||||
for (_, (tr, mh)) in world.query::<(&Transform, &ModelId)>().iter() {
|
||||
if let Some(model) = assets.get_model(*mh) {
|
||||
let mesh = &model.mesh;
|
||||
let mat = &model.material;
|
||||
|
||||
let tex_ref = mat.base_color.as_ref().unwrap_or(&self.renderer.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.renderer.program, &uniforms, &self.renderer.params).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// skybox omitted for brevity
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue