use glium::{
glutin::{
dpi::{LogicalSize, PhysicalPosition},
event::{ElementState, Event, MouseButton, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
platform::desktop::EventLoopExtDesktop,
window::WindowBuilder,
ContextBuilder,
},
implement_vertex,
index::{NoIndices, PrimitiveType},
program::ProgramCreationInput,
texture::{CompressedTexture2d, RawImage2d},
uniform, Blend, Display, DrawParameters, Frame, IndexBuffer, Program, Smooth, Surface,
VertexBuffer,
};
use rand::prelude::Rng;
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use std::cmp::min;
use std::hash::{Hash, Hasher};
pub use nalgebra_glm::{distance, Vec2};
pub fn clamp_vec_to_magnitude(v: &mut Vec2, magnitude: f32) {
if v.magnitude() > magnitude {
v.data = (v.normalize() * magnitude).data;
}
}
pub fn angle_facing(v1: &Vec2, v2: &Vec2) -> f32 {
(v2.data[1] - v1.data[1]).atan2(v2.data[0] - v1.data[0])
}
pub fn new_in_square<T: Rng>(dimension: f32, rng: &mut T) -> Vec2 {
Vec2::new(
rng.gen_range(-dimension, dimension),
rng.gen_range(-dimension, dimension),
)
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
}
impl Color {
pub fn new(r: f32, g: f32, b: f32) -> Self {
Self { r, g, b }
}
}
impl Hash for Color {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.r as u32).hash(state);
(self.g as u32).hash(state);
(self.b as u32).hash(state);
}
}
impl PartialEq for Color {
fn eq(&self, other: &Self) -> bool {
(self.r == other.r) && (self.g == other.g) && (self.b == other.b)
}
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum ButtonValue {
Up,
Down,
Left,
Right,
Action1,
Action2,
Action3,
Increase,
Decrease,
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum ButtonState {
Pressed,
Released,
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum GameEvent {
Quit,
MouseMoved { position: Vec2 },
Button {
button_value: ButtonValue,
button_state: ButtonState,
},
}
#[derive(Copy, Clone, Debug)]
struct ShapeVertex {
position: [f32; 2],
color: [f32; 3],
}
implement_vertex!(ShapeVertex, position, color);
fn create_circle_vertices(radius: f32, num_vertices: usize, color: Color) -> Vec<ShapeVertex> {
let mut v = Vec::<ShapeVertex>::with_capacity(num_vertices + 2);
v.push(ShapeVertex {
position: [0.0, 0.0],
color: [color.r, color.g, color.b],
});
for x in 0..=num_vertices {
let inner: f64 = 2.0 * PI / num_vertices as f64 * x as f64;
let color = if x == 0 || x == num_vertices {
Color {
r: 0.0,
g: 0.0,
b: 0.0,
}
} else {
color
};
v.push(ShapeVertex {
position: [inner.cos() as f32 * radius, inner.sin() as f32 * radius],
color: [color.r, color.g, color.b],
});
}
v
}
fn create_ring_vertices(radius: f32, num_vertices: usize, color: Color) -> Vec<ShapeVertex> {
let mut v = Vec::<ShapeVertex>::with_capacity(num_vertices + 1);
for x in 0..=num_vertices {
let inner: f64 = 2.0 * PI / num_vertices as f64 * x as f64;
v.push(ShapeVertex {
position: [inner.cos() as f32 * radius, inner.sin() as f32 * radius],
color: [color.r, color.g, color.b],
});
}
v
}
#[derive(Debug)]
pub struct Shape {
pub pos: Vec2,
pub direction: f32,
vertex_buffer: VertexBuffer<ShapeVertex>,
indices: NoIndices,
}
impl Shape {
pub fn new_circle(
window: &Window,
radius: f32,
pos: Vec2,
direction: f32,
color: Color,
) -> Self {
let vertex_buffer =
VertexBuffer::new(&window.display, &create_circle_vertices(radius, 32, color)).unwrap();
Self {
pos,
direction,
vertex_buffer,
indices: NoIndices(PrimitiveType::TriangleFan),
}
}
pub fn new_ring(
window: &Window,
radius: f32,
pos: Vec2,
direction: f32,
color: Color,
) -> Self {
let vertex_buffer =
VertexBuffer::new(&window.display, &create_ring_vertices(radius, 32, color)).unwrap();
Self {
pos,
direction,
vertex_buffer,
indices: NoIndices(PrimitiveType::LineLoop),
}
}
}
#[derive(Copy, Clone, Debug)]
struct ImgVertex {
position: [f32; 2],
tex_coords: [f32; 2],
color: [f32; 3],
tint: u8,
}
implement_vertex!(ImgVertex, position, tex_coords, color, tint);
#[derive(Debug)]
pub struct Img {
pub pos: Vec2,
pub direction: f32,
vertex_buffer: VertexBuffer<ImgVertex>,
index_buffer: IndexBuffer<u16>,
texture: CompressedTexture2d,
}
impl Img {
pub fn new(
window: &Window,
pos: Vec2,
direction: f32,
color: Option<Color>,
filename: &str,
) -> Self {
let file = std::fs::File::open(filename).unwrap();
let reader = std::io::BufReader::new(file);
let image = image::load(reader, image::PNG).unwrap().to_rgba();
let image_dimensions = image.dimensions();
let image = RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dimensions);
let texture = CompressedTexture2d::new(&window.display, image).unwrap();
let tint = if color.is_some() { 1 } else { 0 };
let color = color.unwrap_or_else(|| Color::new(1.0, 1.0, 1.0));
let vertex_buffer = {
let scale = 0.1;
VertexBuffer::new(
&window.display,
&[
ImgVertex {
position: [-scale, -scale],
tex_coords: [0.0, 0.0],
color: [color.r, color.g, color.b],
tint,
},
ImgVertex {
position: [-scale, scale],
tex_coords: [0.0, 1.0],
color: [color.r, color.g, color.b],
tint,
},
ImgVertex {
position: [scale, scale],
tex_coords: [1.0, 1.0],
color: [color.r, color.g, color.b],
tint,
},
ImgVertex {
position: [scale, -scale],
tex_coords: [1.0, 0.0],
color: [color.r, color.g, color.b],
tint,
},
],
)
.unwrap()
};
let index_buffer = IndexBuffer::new(
&window.display,
PrimitiveType::TriangleStrip,
&[1 as u16, 2, 0, 3],
)
.unwrap();
Self {
pos,
direction,
vertex_buffer,
index_buffer,
texture,
}
}
}
pub struct Window {
event_loop: EventLoop<()>,
display: Display,
shape_program: Program,
img_program: Program,
screen_to_opengl: Box<dyn Fn(PhysicalPosition<f64>) -> Vec2>,
target: Option<Frame>,
}
impl Window {
pub fn new(override_dimension: Option<u32>, window_title: &str) -> Self {
let event_loop = EventLoop::<()>::new();
let mut min_height = 1024;
for monitor in event_loop.available_monitors() {
min_height = min(min_height, monitor.size().to_logical::<u32>(monitor.scale_factor()).height- 100);
}
let dimension = override_dimension.unwrap_or(min_height);
let logical_size = LogicalSize::new(dimension, dimension);
let window = WindowBuilder::new()
.with_inner_size(logical_size)
.with_title(window_title);
let context = ContextBuilder::new();
let display = Display::new(window, context, &event_loop).unwrap();
let current_monitor = display.gl_window().window().current_monitor();
let scale_factor: f64 = current_monitor.scale_factor();
let inverse_half_dimension = 1.0 / (dimension as f32 * 0.5);
let screen_to_opengl = Box::new(move |pos: PhysicalPosition<f64>| -> Vec2 {
let logical_pos: (f64, f64) = pos.to_logical::<f64>(scale_factor).into();
let x = (logical_pos.0 as f32 * inverse_half_dimension) - 1.0;
let y = 1.0 - (logical_pos.1 as f32 * inverse_half_dimension);
Vec2::new(x, y)
});
let shape_vertex_shader = r#"
#version 140
in vec2 position;
in vec3 color;
out vec3 v_color;
uniform mat4 matrix;
void main() {
v_color = color;
gl_Position = matrix * vec4(position, 0.0, 1.0);
}
"#;
let shape_fragment_shader = r#"
#version 140
in vec3 v_color;
out vec4 color;
void main() {
color = vec4(v_color, 1.0);
}
"#;
let program = Program::new(
&display,
ProgramCreationInput::SourceCode {
vertex_shader: shape_vertex_shader,
tessellation_control_shader: None,
tessellation_evaluation_shader: None,
geometry_shader: None,
fragment_shader: shape_fragment_shader,
transform_feedback_varyings: None,
outputs_srgb: true,
uses_point_size: true,
},
)
.unwrap();
let vertex_shader_img = r#"
#version 140
uniform mat4 matrix;
in vec2 position;
in vec2 tex_coords;
in vec3 color;
in uint tint;
out vec3 v_color;
out vec2 v_tex_coords;
flat out uint u_tint;
void main() {
u_tint = tint;
v_color = color;
gl_Position = matrix * vec4(position, 0.0, 1.0);
v_tex_coords = tex_coords;
}
"#;
let fragment_shader_img = r#"
#version 140
uniform sampler2D tex;
in vec2 v_tex_coords;
in vec3 v_color;
flat in uint u_tint;
out vec4 f_color;
void main() {
if ((texture(tex, v_tex_coords).a < 0.9) || (u_tint == 0u)) {
f_color = texture(tex, v_tex_coords);
} else {
f_color = mix(texture(tex, v_tex_coords), vec4(v_color, 1.0), 0.5);
}
}
"#;
let program_img = Program::new(
&display,
ProgramCreationInput::SourceCode {
vertex_shader: vertex_shader_img,
tessellation_control_shader: None,
tessellation_evaluation_shader: None,
geometry_shader: None,
fragment_shader: fragment_shader_img,
transform_feedback_varyings: None,
outputs_srgb: true,
uses_point_size: true,
},
)
.unwrap();
Self {
event_loop: event_loop,
display,
shape_program: program,
img_program: program_img,
screen_to_opengl,
target: None,
}
}
pub fn drawstart(&mut self) {
self.target = Some(self.display.draw());
if let Some(ref mut target) = self.target {
target.clear_color(0.0, 0.0, 0.0, 1.0);
}
}
pub fn draw_shape(&mut self, shape: &Shape) {
if let Some(ref mut target) = self.target {
let uniforms = uniform! {
matrix: [
[shape.direction.cos() as f32, shape.direction.sin() as f32, 0.0, 0.0],
[-shape.direction.sin() as f32, shape.direction.cos() as f32, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[shape.pos.x, shape.pos.y, 0.0, 1.0f32],
]
};
let draw_parameters = DrawParameters {
blend: Blend::alpha_blending(),
line_width: Some(5.0),
point_size: Some(5.0),
smooth: Some(Smooth::Nicest),
..Default::default()
};
target
.draw(
&shape.vertex_buffer,
&shape.indices,
&self.shape_program,
&uniforms,
&draw_parameters,
)
.unwrap();
}
}
pub fn draw(&mut self, img: &Img) {
if let Some(ref mut target) = self.target {
let uniforms = uniform! {
matrix: [
[img.direction.cos() as f32, img.direction.sin() as f32, 0.0, 0.0],
[-img.direction.sin() as f32, img.direction.cos() as f32, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[img.pos.x, img.pos.y, 0.0, 1.0f32],
],
tex: &img.texture
};
let draw_parameters = DrawParameters {
blend: Blend::alpha_blending(),
line_width: Some(5.0),
point_size: Some(5.0),
smooth: Some(Smooth::Nicest),
..Default::default()
};
target
.draw(
&img.vertex_buffer,
&img.index_buffer,
&self.img_program,
&uniforms,
&draw_parameters,
)
.unwrap();
}
}
pub fn drawfinish(&mut self) {
self.target.take().unwrap().finish().unwrap();
}
pub fn poll_game_events(&mut self) -> Vec<GameEvent> {
let screen_to_opengl = &mut (self.screen_to_opengl);
let mut events = Vec::<GameEvent>::new();
self.event_loop.run_return(|ev, _, control_flow| {
*control_flow = ControlFlow::Exit;
if let Event::WindowEvent { event, .. } = ev {
match event {
WindowEvent::CloseRequested => events.push(GameEvent::Quit),
WindowEvent::CursorMoved { position, .. } => {
let mouse_pos = screen_to_opengl(position);
events.push(GameEvent::MouseMoved {
position: mouse_pos,
});
}
WindowEvent::KeyboardInput { input, .. } => {
let button_state = match input.state {
ElementState::Pressed => ButtonState::Pressed,
ElementState::Released => ButtonState::Released,
};
use VirtualKeyCode::*;
if let Some(vkey) = input.virtual_keycode {
match vkey {
W | Up | Comma => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Up,
}),
S | Down | O => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Down,
}),
A | Left => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Left,
}),
D | Right | E => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Right,
}),
Escape => events.push(GameEvent::Quit),
Space | Delete => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Action1,
}),
NumpadEnter | Return => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Action2,
}),
Tab => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Action3,
}),
Equals => events.push( GameEvent::Button {
button_state,
button_value: ButtonValue::Increase,
}),
Minus => events.push(GameEvent::Button {
button_state,
button_value: ButtonValue::Decrease,
}),
_ => (),
}
}
}
WindowEvent::MouseInput { state, button, .. } => {
let button_state = match state {
ElementState::Pressed => ButtonState::Pressed,
ElementState::Released => ButtonState::Released,
};
events.push(GameEvent::Button {
button_state,
button_value: {
match button {
MouseButton::Left => ButtonValue::Action1,
MouseButton::Right => ButtonValue::Action2,
MouseButton::Middle | MouseButton::Other(_) => ButtonValue::Action3,
}
},
});
}
_ => (),
}
}
});
events
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}