// Copyright 2020 Sergiusz 'q3k' Bazanski // // 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 . 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, events_loop: EventLoop<()>, surface: Arc>, 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, pub view: cgm::Matrix4, 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> = 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) -> (EventLoop<()>, Arc>) { 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) { 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); } }