use glium;
use glium::glutin::surface::WindowSurface;
use glium::implement_vertex;
use glium::Surface;
pub use winit;
use winit::event_loop::EventLoop;
pub mod primitives;
pub mod shaders;
use shaders::*;
pub mod drawable_object;
use drawable_object::*;
use rustc_hash::FxHashMap;
pub mod lights;
#[doc = include_str!("../lumenpyx wiki/Home.md")]
#[doc = include_str!("../lumenpyx wiki/Common-problems-and-their-solutions.md")]
#[doc = include_str!("../lumenpyx wiki/Creating-custom-drawable-objects.md")]
#[doc = include_str!("../lumenpyx wiki/Creating-Custom-Lights.md")]
#[doc = include_str!("../lumenpyx wiki/Creating-Custom-Renderables.md")]
#[doc = include_str!("../lumenpyx wiki/Rendering-a-Sprite.md")]
#[doc = include_str!("../lumenpyx wiki/Technical-Documentation.md")]
pub(crate) const DEFAULT_BEHAVIOR: glium::uniforms::SamplerBehavior =
glium::uniforms::SamplerBehavior {
minify_filter: glium::uniforms::MinifySamplerFilter::Nearest,
magnify_filter: glium::uniforms::MagnifySamplerFilter::Nearest,
max_anisotropy: 1,
wrap_function: (
glium::uniforms::SamplerWrapFunction::Mirror,
glium::uniforms::SamplerWrapFunction::Mirror,
glium::uniforms::SamplerWrapFunction::Mirror,
),
depth_texture_comparison: None,
};
pub enum DebugOption {
None,
Albedo,
Height,
Roughness,
Normal,
ShadowStrength,
}
impl Default for DebugOption {
fn default() -> Self {
DebugOption::None
}
}
pub struct LumenpyxProgram {
pub window: winit::window::Window,
pub display: glium::Display<WindowSurface>,
pub indices: glium::index::NoIndices,
shaders: FxHashMap<String, glium::Program>,
dimensions: [u32; 2],
pub debug: DebugOption,
pub render_settings: RenderSettings,
}
impl LumenpyxProgram {
pub fn new(resolution: [u32; 2], name: &str) -> (LumenpyxProgram, EventLoop<()>) {
let (event_loop, window, display, indices) = setup_program();
let mut program = LumenpyxProgram {
window,
display,
indices,
shaders: FxHashMap::default(),
dimensions: resolution,
debug: DebugOption::None,
render_settings: RenderSettings {
shadows: true,
reflections: true,
},
};
program.set_name(name);
shaders::load_all_system_shaders(&mut program);
(program, event_loop)
}
pub fn add_shader(&mut self, program: glium::Program, name: &str) {
self.shaders.insert(name.to_string(), program);
}
pub fn get_shader(&self, name: &str) -> Option<&glium::Program> {
self.shaders.get(name)
}
pub fn remove_shader(&mut self, name: &str) {
self.shaders.remove(name);
}
pub fn set_name(&mut self, name: &str) {
self.window.set_title(name);
}
pub fn set_debug(&mut self, debug: DebugOption) {
self.debug = debug;
}
pub fn set_render_settings(&mut self, settings: RenderSettings) {
self.render_settings = settings;
}
pub fn set_resolution(&mut self, resolution: [u32; 2]) {
self.dimensions = resolution;
}
pub fn run<F>(&mut self, event_loop: EventLoop<()>, mut update: F)
where
F: FnMut(&mut Self),
{
event_loop
.run(move |ev, window_target| match ev {
winit::event::Event::WindowEvent { event, .. } => match event {
winit::event::WindowEvent::CloseRequested => {
window_target.exit();
}
winit::event::WindowEvent::Resized(physical_size) => {
self.display.resize(physical_size.into());
}
winit::event::WindowEvent::RedrawRequested => {
update(self);
}
_ => (),
},
winit::event::Event::AboutToWait => {
self.window.request_redraw();
}
_ => (),
})
.unwrap();
}
}
#[derive(Copy, Clone)]
pub struct Transform {
matrix: [[f32; 4]; 4],
}
impl Transform {
pub fn new(pos: [f32; 3]) -> Transform {
Transform {
matrix: [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[pos[0] * 2.0, pos[1] * 2.0, pos[2] * 2.0, 1.0],
],
}
}
pub fn get_matrix(&self) -> [[f32; 4]; 4] {
self.matrix
}
pub fn translate(&mut self, x: f32, y: f32, z: f32) {
self.matrix[3][0] = x * 2.0;
self.matrix[3][1] = y * 2.0;
self.matrix[3][2] = z * 2.0;
}
pub fn set_scale(&mut self, x: f32, y: f32, z: f32) {
self.matrix[0][0] = x;
self.matrix[1][1] = y;
self.matrix[2][2] = z;
}
pub fn set_x(&mut self, x: f32) {
self.matrix[3][0] = x;
}
pub fn get_x(&self) -> f32 {
self.matrix[3][0] / 2.0
}
pub fn set_y(&mut self, y: f32) {
self.matrix[3][1] = y;
}
pub fn get_y(&self) -> f32 {
self.matrix[3][1]
}
pub fn set_z(&mut self, z: f32) {
self.matrix[3][2] = z;
}
pub fn get_z(&self) -> f32 {
self.matrix[3][2]
}
}
#[derive(Copy, Clone)]
pub struct Vertex {
position: [f32; 2],
tex_coords: [f32; 2],
}
implement_vertex!(Vertex, position, tex_coords);
pub(crate) fn setup_program() -> (
EventLoop<()>,
winit::window::Window,
glium::Display<WindowSurface>,
glium::index::NoIndices,
) {
let (event_loop, display, window, indices) = setup_window();
(event_loop, window, display, indices)
}
fn load_image(path: &str) -> glium::texture::RawImage2d<f32> {
let img = image::open(path).unwrap();
img.flipv();
let path = format!("{}", path);
let image = image::load(
std::io::Cursor::new(std::fs::read(path).unwrap()),
image::ImageFormat::Png,
)
.unwrap()
.to_rgba32f();
let image_dimensions = image.dimensions();
let image = glium::texture::RawImage2d::from_raw_rgba_reversed(&image, image_dimensions);
image
}
fn setup_window() -> (
EventLoop<()>,
glium::Display<WindowSurface>,
winit::window::Window,
glium::index::NoIndices,
) {
let event_loop = winit::event_loop::EventLoopBuilder::new().build().unwrap();
let (window, display) = glium::backend::glutin::SimpleWindowBuilder::new().build(&event_loop);
let indices = glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList);
(event_loop, display, window, indices)
}
pub struct Camera {
pub position: [f32; 3],
}
impl Camera {
pub fn new(position: [f32; 3]) -> Camera {
Camera { position }
}
}
pub struct RenderSettings {
pub shadows: bool,
pub reflections: bool,
}
pub fn draw_all(
lights: Vec<&dyn lights::LightDrawable>,
drawables: Vec<&dyn Drawable>,
program: &mut LumenpyxProgram,
camera: &Camera,
) {
for drawable in &drawables {
drawable.try_load_shaders(program);
}
for light in &lights {
light.try_load_shaders(program);
}
let display = &program.display;
let debug = &program.debug;
let render_settings = &program.render_settings;
let albedo_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.unwrap();
let height_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.unwrap();
let normal_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.unwrap();
let roughness_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.unwrap();
let shadow_strength_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.unwrap();
{
let last_drawable_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.unwrap();
let last_drawable_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &last_drawable_texture).unwrap();
let last_drawable_sampler =
glium::uniforms::Sampler(&last_drawable_texture, DEFAULT_BEHAVIOR);
let this_drawable_sampler = glium::uniforms::Sampler(&albedo_texture, DEFAULT_BEHAVIOR);
let mut albedo_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &albedo_texture).unwrap();
let mut height_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &height_texture).unwrap();
let mut roughness_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &roughness_texture).unwrap();
let mut normal_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &normal_texture).unwrap();
normal_framebuffer.clear_color(0.0, 0.0, 1.0, 1.0);
let mut shadow_strength_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &shadow_strength_texture).unwrap();
for drawable in &drawables {
let mut new_matrix = drawable.get_position();
if program.dimensions[0] > program.dimensions[1] {
new_matrix[0][0] *= program.dimensions[1] as f32 / program.dimensions[0] as f32;
} else {
new_matrix[1][1] *= program.dimensions[0] as f32 / program.dimensions[1] as f32;
}
new_matrix[3][0] -= camera.position[0];
new_matrix[3][1] -= camera.position[1];
drawable.draw(
program,
new_matrix,
&mut albedo_framebuffer,
&mut height_framebuffer,
&mut roughness_framebuffer,
&mut normal_framebuffer,
);
if render_settings.shadows {
let shadow_strength = drawable.get_recieve_shadows_strength();
shaders::draw_recieve_shadows(
&mut shadow_strength_framebuffer,
&program,
shadow_strength,
last_drawable_sampler,
this_drawable_sampler,
);
albedo_framebuffer.blit_whole_color_to(
&last_drawable_framebuffer,
&glium::BlitTarget {
left: 0,
bottom: 0,
width: program.dimensions[0] as i32,
height: program.dimensions[1] as i32,
},
glium::uniforms::MagnifySamplerFilter::Nearest,
);
}
}
}
let lit_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.expect("Failed to create lit frame buffer");
if render_settings.shadows {
let albedo = glium::uniforms::Sampler(&albedo_texture, DEFAULT_BEHAVIOR);
let height_sampler = glium::uniforms::Sampler(&height_texture, DEFAULT_BEHAVIOR);
let roughness_sampler = glium::uniforms::Sampler(&roughness_texture, DEFAULT_BEHAVIOR);
let shadow_strength_sampler =
glium::uniforms::Sampler(&shadow_strength_texture, DEFAULT_BEHAVIOR);
let mut lit_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &lit_texture).unwrap();
for light in lights {
let mut new_matrix = light.get_transform();
if program.dimensions[0] > program.dimensions[1] {
new_matrix[0][0] *= program.dimensions[1] as f32 / program.dimensions[0] as f32;
} else {
new_matrix[1][1] *= program.dimensions[0] as f32 / program.dimensions[1] as f32;
}
new_matrix[3][0] -= camera.position[0];
new_matrix[3][1] -= camera.position[1];
light.draw(
program,
new_matrix,
&mut lit_framebuffer,
height_sampler,
albedo,
roughness_sampler,
shadow_strength_sampler,
);
}
}
let reflected_texture = glium::texture::Texture2d::empty_with_format(
display,
glium::texture::UncompressedFloatFormat::U8U8U8U8,
glium::texture::MipmapsOption::NoMipmap,
program.dimensions[0],
program.dimensions[1],
)
.expect("Failed to create reflected frame buffer");
if render_settings.reflections {
let roughness = glium::uniforms::Sampler(&roughness_texture, DEFAULT_BEHAVIOR);
let height = glium::uniforms::Sampler(&height_texture, DEFAULT_BEHAVIOR);
let normal = glium::uniforms::Sampler(&normal_texture, DEFAULT_BEHAVIOR);
let lit_sampler = if render_settings.shadows {
glium::uniforms::Sampler(&lit_texture, DEFAULT_BEHAVIOR)
} else {
glium::uniforms::Sampler(&albedo_texture, DEFAULT_BEHAVIOR)
};
let mut reflected_framebuffer =
glium::framebuffer::SimpleFrameBuffer::new(display, &reflected_texture).unwrap();
draw_reflections(
camera,
lit_sampler,
height,
roughness,
normal,
&mut reflected_framebuffer,
&program,
);
}
{
let finished_texture = match debug {
DebugOption::None => glium::uniforms::Sampler(&reflected_texture, DEFAULT_BEHAVIOR),
DebugOption::Albedo => glium::uniforms::Sampler(&albedo_texture, DEFAULT_BEHAVIOR),
DebugOption::Height => glium::uniforms::Sampler(&height_texture, DEFAULT_BEHAVIOR),
DebugOption::Roughness => {
glium::uniforms::Sampler(&roughness_texture, DEFAULT_BEHAVIOR)
}
DebugOption::Normal => glium::uniforms::Sampler(&normal_texture, DEFAULT_BEHAVIOR),
DebugOption::ShadowStrength => {
glium::uniforms::Sampler(&shadow_strength_texture, DEFAULT_BEHAVIOR)
}
};
draw_upscale(finished_texture, &program);
}
}