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, PolygonMode};
use rand::prelude::Rng;
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use std::hash::{Hash, Hasher};
use rusty_core::glm::{self, Vec2};
use std::convert::TryInto;
pub mod prelude {
pub use crate::{GameEvent, Window};
}
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,
},
}
pub enum ShapeStyle {
Fill,
Line,
}
pub enum DrawBundle<'a> {
Img(&'a VertexBuffer<ImgVertex>, &'a IndexBuffer<u16>, &'a CompressedTexture2d),
Shape(&'a VertexBuffer<ShapeVertex>, &'a NoIndices, PolygonMode),
}
pub trait Drawable {
fn get_shader_items(&self) -> DrawBundle;
}
pub struct Sprite {
pub pos: Vec2,
pub direction: f32,
pub scale: f32,
affine: [[f32; 4]; 4],
affine_cache: (Vec2, f32, f32),
drawable: Box<dyn Drawable>,
}
impl Sprite {
pub fn new_image(
window: &Window,
pos: Vec2,
direction: f32,
scale: f32,
color: Option<Color>,
filename: &str,
) -> Self {
Self {
pos,
direction,
scale,
affine: [[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]],
affine_cache: (glm::vec2(std::f32::NAN, std::f32::NAN), std::f32::NAN, std::f32::NAN),
drawable: Box::new(Img::new(&window, color, filename)),
}
}
pub fn new_circle(
window: &Window,
pos: Vec2,
direction: f32,
scale: f32,
radius: f32,
color: Color,
shape_style: ShapeStyle,
) -> Self {
Self {
pos,
direction,
scale,
affine: [[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]],
affine_cache: (glm::vec2(std::f32::NAN, std::f32::NAN), std::f32::NAN, std::f32::NAN),
drawable: Box::new(Shape::new_circle(&window, radius, color, shape_style)),
}
}
pub fn new_rectangle(
window: &Window,
pos: Vec2,
direction: f32,
scale: f32,
width: f32,
height: f32,
color: Color,
shape_style: ShapeStyle,
) -> Self {
Self {
pos,
direction,
scale,
affine: [[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]],
affine_cache: (glm::vec2(std::f32::NAN, std::f32::NAN), std::f32::NAN, std::f32::NAN),
drawable: Box::new(Shape::new_rectangle(&window, width, height, color, shape_style)),
}
}
pub fn draw(&mut self, window: &mut Window) {
if let Some(ref mut target) = window.target {
let affine = if self.affine_cache == (self.pos, self.direction, self.scale) {
self.affine
} else {
let translated = glm::translation(&glm::vec2_to_vec3(&self.pos));
let rotated = glm::rotate(&translated, self.direction, &glm::vec3(0.0f32, 0., 1.));
let scaled = glm::scale(&rotated, &glm::vec3(self.scale, self.scale, self.scale));
self.affine_cache = (self.pos, self.direction, self.scale);
self.affine = scaled.try_into().unwrap();
self.affine
};
match self.drawable.get_shader_items() {
DrawBundle::Img(vertex_buffer, index_buffer, texture) => {
let uniforms = uniform! {
matrix: affine,
tex: texture,
};
let draw_parameters = DrawParameters {
blend: Blend::alpha_blending(),
..Default::default()
};
target
.draw(
vertex_buffer,
index_buffer,
&window.img_program,
&uniforms,
&draw_parameters,
)
.unwrap();
}
DrawBundle::Shape(vertex_buffer, indices, polygon_mode) => {
let uniforms = uniform! {
matrix: affine,
};
let draw_parameters = DrawParameters {
blend: Blend::alpha_blending(),
smooth: Some(Smooth::Nicest),
polygon_mode,
..Default::default()
};
target
.draw(
vertex_buffer,
indices,
&window.shape_program,
&uniforms,
&draw_parameters,
)
.unwrap();
},
}
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct ShapeVertex {
position: [f32; 2],
color: [f32; 3],
}
implement_vertex!(ShapeVertex, position, color);
#[derive(Debug)]
pub struct Shape {
vertex_buffer: VertexBuffer<ShapeVertex>,
indices: NoIndices,
polygon_mode: PolygonMode,
}
impl Drawable for Shape {
fn get_shader_items(&self) -> DrawBundle {
DrawBundle::Shape(&self.vertex_buffer, &self.indices, self.polygon_mode)
}
}
impl Shape {
pub fn new_rectangle(
window: &Window,
width: f32,
height: f32,
color: Color,
shape_style: ShapeStyle,
) -> Self {
let vertex_buffer = VertexBuffer::new(
&window.display,
&vec![
ShapeVertex {
position: (glm::vec2(width * 0.5, height * 0.5)).into(),
color: [color.r, color.g, color.b],
},
ShapeVertex {
position: (glm::vec2(width * 0.5, -height * 0.5)).into(),
color: [color.r, color.g, color.b],
},
ShapeVertex {
position: (glm::vec2(-width * 0.5, -height * 0.5)).into(),
color: [color.r, color.g, color.b],
},
ShapeVertex {
position: (glm::vec2(-width * 0.5, height * 0.5)).into(),
color: [color.r, color.g, color.b],
},
]).unwrap();
let (primitive_type, polygon_mode) = match shape_style {
ShapeStyle::Fill => (PrimitiveType::TriangleFan, PolygonMode::Fill),
ShapeStyle::Line => (PrimitiveType::LineLoop, PolygonMode::Line),
};
Self {
vertex_buffer,
indices: NoIndices(primitive_type),
polygon_mode,
}
}
pub fn new_circle(
window: &Window,
radius: f32,
color: Color,
shape_style: ShapeStyle,
) -> Self {
let num_vertices = 63;
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],
});
}
let vertex_buffer =
VertexBuffer::new(&window.display, &v).unwrap();
let (primitive_type, polygon_mode) = match shape_style {
ShapeStyle::Fill => (PrimitiveType::TriangleFan, PolygonMode::Fill),
ShapeStyle::Line => (PrimitiveType::LineLoop, PolygonMode::Line),
};
Self {
vertex_buffer,
indices: NoIndices(primitive_type),
polygon_mode,
}
}
}
#[derive(Copy, Clone, Debug)]
pub 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 {
vertex_buffer: VertexBuffer<ImgVertex>,
index_buffer: IndexBuffer<u16>,
texture: CompressedTexture2d,
}
impl Drawable for Img {
fn get_shader_items(&self) -> DrawBundle {
DrawBundle::Img(&self.vertex_buffer, &self.index_buffer, &self.texture)
}
}
impl Img {
pub fn new(
window: &Window,
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 pixels_per_game_unit = window.dimension as f32 * 0.0625;
let scale_x = image_dimensions.0 as f32 / pixels_per_game_unit / 16.;
let scale_y = image_dimensions.1 as f32 / pixels_per_game_unit / 16.;
let vertex_buffer =
VertexBuffer::new(
&window.display,
&[
ImgVertex {
position: [-scale_x, -scale_y],
tex_coords: [0.0, 0.0],
color: [color.r, color.g, color.b],
tint,
},
ImgVertex {
position: [-scale_x, scale_y],
tex_coords: [0.0, 1.0],
color: [color.r, color.g, color.b],
tint,
},
ImgVertex {
position: [scale_x, scale_y],
tex_coords: [1.0, 1.0],
color: [color.r, color.g, color.b],
tint,
},
ImgVertex {
position: [scale_x, -scale_y],
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 {
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>,
dimension: u32,
target: Option<Frame>,
}
impl Window {
pub fn new(override_dimension: Option<u32>, window_title: &str) -> Self {
let event_loop = EventLoop::<()>::new();
let dimension = override_dimension.unwrap_or(1024);
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 shape_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 img_program = 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,
display,
shape_program,
img_program,
screen_to_opengl,
dimension,
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 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);
}
}