use std::collections::HashMap;
use std::time::{Instant, Duration};
use std::thread;
use std::cell::Cell;
use std::iter::FromIterator;
use cgmath::{self, SquareMatrix, InnerSpace};
use conrod;
use glium::{self, Surface, DisplayBuild, VertexBuffer, IndexBuffer};
use glium::index::PrimitiveType;
use glium::glutin::{Event, WindowBuilder};
use glium::texture::Texture2d;
use mesh::{Mesh, Vertex};
use game::Game;
pub struct Shader {
program: glium::Program,
}
pub struct Context {
display: glium::Display,
camera: Camera,
size: (u32, u32),
mouse: (i32, i32),
}
impl Context {
pub fn create_mesh(&self, vertices: &[Vertex], indices: &[u32]) -> Mesh {
Mesh {
vertices: VertexBuffer::dynamic(&self.display, &vertices).unwrap(),
indices: IndexBuffer::new(&self.display, PrimitiveType::TrianglesList, indices).unwrap(),
}
}
pub fn create_shader(&self, vertex_path: &str, fragment_path: &str) -> Shader {
let vertex_src = string_from_file(vertex_path);
let fragment_src = string_from_file(fragment_path);
Shader {
program: glium::Program::from_source(&self.display, &vertex_src, &fragment_src, None).unwrap()
}
}
pub fn mouse(&self) -> (i32, i32) {
self.mouse
}
pub fn size(&self) -> (u32, u32) {
self.size
}
pub fn screen_point_to_direction(&self, x: i32, y: i32) -> cgmath::Vector3<f32> {
let w = self.size.0 as f32;
let h = self.size.1 as f32;
let x = (2.0 * (x as f32)) / w - 1.0;
let y = 1.0 - (2.0 * (y as f32)) / h;
let u = (self.camera.perspective(w / h) * self.camera.view.get()).invert().unwrap();
let v0 = u * cgmath::Vector4::new(x, y, 0.0, 1.0);
let v1 = u * cgmath::Vector4::new(x, y, 1.0, 1.0);
((v1 / v1.w) - (v0 / v0.w)).truncate().normalize()
}
pub fn set_view(&self, view: cgmath::Matrix4<f32>) {
self.camera.view.set(view);
}
}
pub struct Camera {
near: f32,
far: f32,
fov: cgmath::Rad<f32>,
view: Cell<cgmath::Matrix4<f32>>,
}
impl Camera {
pub fn new(near: f32, far: f32, fov: cgmath::Rad<f32>) -> Self {
Camera {
near: near,
far: far,
fov: fov,
view: Cell::new(cgmath::Matrix4::identity()),
}
}
pub fn perspective(&self, aspect: f32) -> cgmath::Matrix4<f32> {
cgmath::perspective(self.fov, aspect, self.near, self.far)
}
}
struct Conrod {
conrod: conrod::Ui,
renderer: conrod::backend::glium::Renderer,
images: conrod::image::Map<Texture2d>,
needs_update: bool,
}
pub struct Engine {
context: Context,
ui: Conrod,
last_update: Instant,
}
impl Engine {
pub fn new(title: &str, size: (u32, u32)) -> Self {
let display = WindowBuilder::new()
.with_title(title)
.with_dimensions(size.0, size.1)
.with_vsync()
.build_glium()
.unwrap();
let mut conrod = conrod::UiBuilder::new([size.0 as f64, size.1 as f64]).theme(theme()).build();
conrod.fonts.insert_from_file("assets/fonts/NotoSans/NotoSans-Regular.ttf").unwrap();
let renderer = conrod::backend::glium::Renderer::new(&display).unwrap();
Engine {
context: Context {
camera: Camera::new(0.1, 1024.0, cgmath::Deg(60.0).into()),
display: display,
mouse: (0, 0),
size: size,
},
ui: Conrod {
conrod: conrod,
renderer: renderer,
images: conrod::image::Map::new(),
needs_update: true,
},
last_update: Instant::now(),
}
}
pub fn run<G: Game>(mut self) {
let mut game = {
let mut ui_builder = ::ui::UiBuilder {
conrod: &mut self.ui.conrod,
};
G::new(&self.context, &mut ui_builder)
};
'main: loop {
for event in self.get_events() {
if self.handle_ui_event(&event) {
self.ui.needs_update = true;
}
match event.clone() {
Event::Closed => break 'main,
event => if let Some(action) = game.on_event(&self.context, event) {
game.on_action(&self.context, action);
}
}
match event {
Event::MouseMoved(x, y) => self.context.mouse = (x, y),
Event::Resized(w, h) => self.context.size = (w, h),
_ => {},
}
}
{
let mut ui = self.ui.conrod.set_widgets();
for action in game.on_ui(&mut ui) {
game.on_action(&self.context, action);
}
}
self.draw(&game);
}
}
fn draw<G: Game>(&mut self, game: &G) {
let target = self.context.display.draw();
let aspect = aspect(&target);
let params = glium::DrawParameters {
depth: glium::Depth {
test: glium::draw_parameters::DepthTest::IfLess,
write: true,
..Default::default()
},
backface_culling: glium::draw_parameters::BackfaceCullingMode::CullCounterClockwise,
..Default::default()
};
let mut frame = Frame {
target: target,
view: self.context.camera.view.get().into(),
perspective: self.context.camera.perspective(aspect).into(),
params: params,
};
frame.target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
game.draw(&mut frame);
let primitives = self.ui.conrod.draw();
self.ui.renderer.fill(&self.context.display, primitives, &self.ui.images);
self.ui.renderer.draw(&self.context.display, &mut frame.target, &self.ui.images).unwrap();
frame.target.finish().unwrap();
}
fn handle_ui_event(&mut self, event: &Event) -> bool {
if let Some(event) = conrod::backend::winit::convert(event.clone(), &self.context.display) {
self.ui.conrod.handle_event(event);
true
} else {
false
}
}
fn get_events(&mut self) -> Vec<glium::glutin::Event> {
let last_update = self.last_update;
let sixteen_ms = Duration::from_millis(16);
let duration_since_last_update = Instant::now().duration_since(last_update);
if duration_since_last_update < sixteen_ms {
thread::sleep(sixteen_ms - duration_since_last_update);
}
let mut events = Vec::from_iter(self.context.display.poll_events());
if events.is_empty() && !self.ui.needs_update {
events.extend(self.context.display.wait_events().next());
}
self.ui.needs_update = false;
self.last_update = Instant::now();
events
}
}
fn aspect(target: &glium::Frame) -> f32 {
let (width, height) = target.get_dimensions();
width as f32 / height as f32
}
pub struct Frame<'a> {
view: [[f32; 4]; 4],
perspective: [[f32; 4]; 4],
target: glium::Frame,
params: glium::DrawParameters<'a>,
}
impl<'a> Frame<'a> {
pub fn draw(&mut self, mesh: &Mesh, shader: &Shader, transform: cgmath::Matrix4<f32>) {
let model: [[f32; 4]; 4] = transform.into();
self.target
.draw(&mesh.vertices,
&mesh.indices,
&shader.program,
&uniform! {
model: model,
view: self.view,
perspective: self.perspective,
},
&self.params)
.unwrap();
}
}
fn theme() -> conrod::Theme {
use conrod::position::{Align, Direction, Padding, Position, Relative};
conrod::Theme {
name: "Demo Theme".to_string(),
padding: Padding::none(),
x_position: Position::Relative(Relative::Align(Align::Start), None),
y_position: Position::Relative(Relative::Direction(Direction::Backwards, 20.0), None),
background_color: conrod::color::DARK_CHARCOAL,
shape_color: conrod::color::LIGHT_CHARCOAL,
border_color: conrod::color::BLACK,
border_width: 0.0,
label_color: conrod::color::WHITE,
font_id: None,
font_size_large: 26,
font_size_medium: 18,
font_size_small: 12,
widget_styling: HashMap::new(),
mouse_drag_threshold: 0.0,
double_click_threshold: Duration::from_millis(500),
}
}
fn string_from_file(path: &str) -> String {
use std::fs::File;
use std::io::Read;
let mut data = String::new();
let mut file = File::open(path).expect(path);
file.read_to_string(&mut data).expect(path);
data
}