abrasion/engine/render/mod.rs

303 lines
9.7 KiB
Rust

// Copyright 2020 Sergiusz 'q3k' Bazanski <q3k@q3k.org>
//
// This file is part of Abrasion.
//
// Abrasion is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation, version 3.
//
// Abrasion is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
// details.
//
// You should have received a copy of the GNU General Public License along with
// Abrasion. If not, see <https://www.gnu.org/licenses/>.
use std::sync::Arc;
use std::collections::BTreeMap;
use cgmath as cgm;
use cgmath::SquareMatrix;
use winit::{
dpi::LogicalSize,
window::Window,
window::WindowBuilder,
event_loop::EventLoop,
event::Event,
event::DeviceEvent,
event::WindowEvent,
platform::run_return::EventLoopExtRunReturn,
};
use vulkano_win::VkSurfaceBuild;
use vulkano::instance as vi;
use vulkano::swapchain as vs;
use ecs::Join;
pub mod light;
pub mod material;
pub mod mesh;
pub mod renderable;
pub mod resource;
pub mod vulkan;
pub use light::Light;
pub use material::Material;
pub use mesh::Mesh;
pub use renderable::{Transform, Renderable};
pub use resource::{Resource, ResourceID};
use crate::input;
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
pub struct Renderer {
instance: vulkan::Instance<Window>,
events_loop: EventLoop<()>,
surface: Arc<vs::Surface<Window>>,
cursor_locked: bool,
}
#[derive(Clone, Debug)]
pub struct Status {
pub closed: bool,
pub input_device_id: u64,
pub resolution: [u32; 2],
}
impl ecs::Global for Status {}
#[derive(Clone, Debug)]
pub struct SceneInfo {
pub camera: cgm::Point3<f32>,
pub view: cgm::Matrix4<f32>,
pub lock_cursor: bool,
}
impl ecs::Global for SceneInfo {}
impl<'a> ecs::System<'a> for Renderer {
type SystemData = (
ecs::ReadComponent<'a, Transform>,
ecs::ReadComponent<'a, Renderable>,
ecs::ReadWriteGlobal<'a, Status>,
ecs::ReadGlobal<'a, SceneInfo>,
ecs::ReadGlobal<'a, resource::Manager>,
ecs::ReadWriteGlobal<'a, input::Input>,
);
fn run(&mut self,
( transforms
, renderables
, status
, scene
, rm
, input): Self::SystemData,
) {
let transformedRenderables = (transforms, renderables);
let mut input = input.get();
let mut status = status.get();
let scene = scene.get();
// Render sceneinfo and renderables.
let mut rd = vulkan::RenderData {
meshes: BTreeMap::new(),
lights: Vec::new(),
};
for (transform, renderable) in transformedRenderables.join_all() {
match renderable {
Renderable::Light(lrid) => {
rd.lights.push((*lrid, transform.xyzw()));
},
Renderable::Mesh(mesh_id, material_id) => {
rd.meshes.entry((*mesh_id, *material_id)).or_insert(Vec::new()).push(transform.m4());
},
_ => (),
}
}
let camera = &scene.camera;
let view = &scene.view;
self.instance.flip(camera, view, &rd, &rm.get());
// Retrieve current resolution into status.
match self.instance.swapchain_dimensions() {
Some(res) => {
status.resolution = res.clone()
},
None => (),
}
// Process events.
if status.input_device_id == 0 {
status.input_device_id = input.allocate_device();
}
let (close, events) = self.poll_close();
if close {
status.closed = true;
} else {
let mut device = input.devices.entry(status.input_device_id).or_insert(input::Device::MouseCursor(input::MouseCursor::new()));
if let &mut input::Device::MouseCursor(cursor) = &mut device {
let mut per_axis: BTreeMap<u32, Vec<f32>> = BTreeMap::new();
let (rx, ry) = (status.resolution[0], status.resolution[1]);
// Always reset cursor delta at each frame.
cursor.dx = 0.0;
cursor.dy = 0.0;
for event in events {
match event {
InternalEvent::MousePressed(button) => cursor.set_mouse_pressed(button),
InternalEvent::MouseReleased(button) => cursor.set_mouse_released(button),
InternalEvent::CursorMoved(x, y) => {
if rx != 0 && ry != 0 {
cursor.x = (x as f32) / (rx as f32);
cursor.y = (y as f32) / (ry as f32);
}
},
// TODO(q3k): do something better than hardcode a sensitivity
InternalEvent::MouseMoved(dx, dy) => {
cursor.dx = (dx as f32) / 200.0;
cursor.dy = (dy as f32) / 200.0;
}
InternalEvent::AxisMotion(axis, delta) => {
per_axis.entry(axis).or_insert(vec![]).push(delta as f32);
},
}
}
// TODO(q3k): handle per_axis/AxisMotion. This might be needed for some platforms?
}
}
let window = self.surface.window();
// Lock cursor, if requested.
if scene.lock_cursor && !self.cursor_locked {
window.set_cursor_visible(false);
window.set_cursor_grab(true);
self.cursor_locked = true;
} else if self.cursor_locked && !scene.lock_cursor {
window.set_cursor_visible(true);
window.set_cursor_grab(false);
self.cursor_locked = false;
}
}
}
#[derive(Clone,Debug)]
enum InternalEvent {
MousePressed(input::MouseButton),
MouseReleased(input::MouseButton),
CursorMoved(f64, f64),
MouseMoved(f64, f64),
AxisMotion(u32, f64),
}
impl Renderer {
pub fn initialize(world: &mut ecs::World) -> Self {
world.set_global(SceneInfo {
camera: cgm::Point3::new(0.0, 0.0, 0.0),
view: cgm::Matrix4::identity(),
lock_cursor: false,
});
world.set_global(Status {
closed: false,
input_device_id: 0,
resolution: [0u32; 2],
});
let mut instance = vulkan::Instance::new("abrasion".to_string());
let (events_loop, surface) = Self::init_window(instance.get_vulkan());
instance.use_surface(&surface);
Self {
instance,
events_loop,
surface,
cursor_locked: false,
}
}
fn init_window(instance: Arc<vi::Instance>) -> (EventLoop<()>, Arc<vs::Surface<Window>>) {
let events_loop = EventLoop::new();
let surface = WindowBuilder::new()
.with_title("abrasion")
.with_inner_size(LogicalSize::new(f64::from(WIDTH), f64::from(HEIGHT)))
.build_vk_surface(&events_loop, instance.clone())
.expect("could not create surface");
(events_loop, surface)
}
fn poll_close(&mut self) -> (bool, Vec<InternalEvent>) {
let mut close = false;
let mut events = vec![];
// TODO(q3k): migrate to EventLoop::run
self.events_loop.run_return(|ev, _, control_flow| {
*control_flow = winit::event_loop::ControlFlow::Poll;
match ev {
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => {
close = true;
*control_flow = winit::event_loop::ControlFlow::Exit;
},
Event::MainEventsCleared => {
*control_flow = winit::event_loop::ControlFlow::Exit;
},
Event::WindowEvent {
event: WindowEvent::CursorMoved { position, .. },
..
} => {
events.push(InternalEvent::CursorMoved(position.x, position.y));
},
Event::WindowEvent {
event: WindowEvent::MouseInput { state, button, .. },
..
} => {
let button = match button {
winit::event::MouseButton::Left => input::MouseButton::Left,
winit::event::MouseButton::Middle => input::MouseButton::Middle,
winit::event::MouseButton::Right => input::MouseButton::Right,
_ => input::MouseButton::Other,
};
match state {
winit::event::ElementState::Pressed => {
events.push(InternalEvent::MousePressed(button));
},
winit::event::ElementState::Released => {
events.push(InternalEvent::MouseReleased(button));
},
}
},
Event::DeviceEvent {
event: DeviceEvent::MouseMotion { delta, .. },
..
} => {
events.push(InternalEvent::MouseMoved(delta.0, delta.1));
}
Event::WindowEvent {
event: WindowEvent::AxisMotion { axis, value, .. },
..
} => {
events.push(InternalEvent::AxisMotion(axis, value));
},
_ => {},
}
});
return (close, events);
}
}