use crate::components::{Camera, Player, RecvPacket, SendPacket, Texture, Transform}; use crate::rendering::assets::AssetLoader; use crate::ui::draw_ui; use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; use bevy_ecs::prelude::With; use bevy_ecs::world::World; use egui_glow::EguiGlow; use glow::PixelUnpackData; use nalgebra::{Scale3, Translation3}; use starkingdoms_common::PlanetType; use std::collections::HashMap; use std::sync::Arc; use winit::dpi::PhysicalPosition; use winit::event_loop::ActiveEventLoop; pub struct Renderer { program: glow::Program, vertex_array: glow::VertexArray, vertex_buffer: glow::Buffer, element_buffer: glow::Buffer, pub egui_glow: EguiGlow, textures: HashMap, pub(crate) send_packet_events: Events, pub(crate) recv_packet_events: Events, pub(crate) planet_types: HashMap, // (world entity, server id) pub(crate) up: bool, pub(crate) down: bool, pub(crate) left: bool, pub(crate) right: bool, pub(crate) mouse_pos: PhysicalPosition, } pub struct RenderCreateContext { pub send_packet_events: Events, pub recv_packet_events: Events, pub planet_types: HashMap, } const VERTICES: [f32; 16] = [ -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0, ]; const INDICES: [u32; 6] = [0, 1, 2, 2, 3, 0]; impl Renderer { pub unsafe fn new( gl: Arc, event_loop: &ActiveEventLoop, ctx: RenderCreateContext, ) -> Self { use glow::HasContext as _; let shaders = [ ( "vertex", include_str!("../assets/shaders/vertex.glsl"), glow::VERTEX_SHADER, ), ( "fragment", include_str!("../assets/shaders/fragment.glsl"), glow::FRAGMENT_SHADER, ), ]; let program = gl.create_program().expect("Failed to create program"); for (name, source, shader_type) in shaders { let shader = gl .create_shader(shader_type) .expect("Failed to create vertex shader"); gl.shader_source(shader, source); gl.compile_shader(shader); if !gl.get_shader_compile_status(shader) { tracing::error!( "error in {} shader: {}", name, gl.get_shader_info_log(shader) ); } gl.attach_shader(program, shader); gl.delete_shader(shader); } gl.link_program(program); gl.use_program(Some(program)); let vertex_array = gl .create_vertex_array() .expect("Failed to create vertex array"); gl.bind_vertex_array(Some(vertex_array)); let vertex_buffer = gl.create_buffer().expect("Failed to create vertex buffer"); gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer)); let element_buffer = gl.create_buffer().expect("Failed to create element buffer"); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(element_buffer)); gl.buffer_data_u8_slice( glow::ARRAY_BUFFER, std::slice::from_raw_parts(VERTICES.as_ptr() as *const u8, size_of_val(&VERTICES)), glow::STATIC_DRAW, ); gl.buffer_data_u8_slice( glow::ELEMENT_ARRAY_BUFFER, std::slice::from_raw_parts(INDICES.as_ptr() as *const u8, size_of_val(&INDICES)), glow::STATIC_DRAW, ); gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 4 * size_of::() as i32, 0); gl.enable_vertex_attrib_array(0); gl.vertex_attrib_pointer_f32( 1, 2, glow::FLOAT, false, 4 * size_of::() as i32, 2 * size_of::() as i32, ); gl.enable_vertex_attrib_array(1); gl.clear_color(0.1, 0.1, 0.1, 1.0); gl.enable(glow::BLEND); gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); let egui_glow = EguiGlow::new(event_loop, gl.clone(), None, None, true); crate::ui::init_ui(egui_glow.egui_ctx.clone()); Self { program, vertex_array, vertex_buffer, element_buffer, egui_glow, textures: HashMap::new(), send_packet_events: ctx.send_packet_events, recv_packet_events: ctx.recv_packet_events, planet_types: ctx.planet_types, left: false, right: false, up: false, down: false, mouse_pos: Default::default(), } } pub unsafe fn draw( &mut self, gl: &glow::Context, window: &winit::window::Window, world: &mut World, ) { use glow::HasContext as _; let player = world.query_filtered::<&Transform, With>(); // draw the UI self.egui_glow.run(window, |ctx| { draw_ui(ctx, &mut *world, &mut self.send_packet_events); }); let camera = world.get_resource::().unwrap(); let x_scale = camera.zoom / camera.width as f32 * 2.0; let y_scale = camera.zoom / camera.height as f32 * 2.0; let view = &[ x_scale, 0.0, 0.0, camera.x * x_scale, 0.0, y_scale, 0.0, camera.y * y_scale, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, ]; let mut sprite_query = world.query::<(&Transform, &mut Texture)>(); let mut sprites = Vec::new(); for (transform, texture) in sprite_query.iter(world) { sprites.push((transform, texture)); } gl.clear(glow::COLOR_BUFFER_BIT); gl.use_program(Some(self.program)); gl.bind_vertex_array(Some(self.vertex_array)); gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer)); gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_buffer)); gl.active_texture(glow::TEXTURE0); let view_loc = gl.get_uniform_location(self.program, "view"); let model_loc = gl.get_uniform_location(self.program, "model"); gl.uniform_matrix_4_f32_slice(view_loc.as_ref(), true, view); if !self.textures.contains_key("starfield.svg") { let assets = world.resource::(); if let Some(image) = assets.get("starfield.svg") { let texture_object = gl .create_texture() .expect("Failed to create texture object"); gl.bind_texture(glow::TEXTURE_2D, Some(texture_object)); gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR_MIPMAP_LINEAR as i32, ); gl.tex_image_2d( glow::TEXTURE_2D, 0, glow::RGBA as i32, image.width as i32, image.height as i32, 0, glow::RGBA, glow::UNSIGNED_BYTE, PixelUnpackData::Slice(Some(&image.bytes)), ); gl.generate_mipmap(glow::TEXTURE_2D); self.textures .insert("starfield.svg".to_string(), texture_object); } } if self.textures.contains_key("starfield.svg") { gl.bind_texture( glow::TEXTURE_2D, self.textures.get("starfield.svg").copied(), ); let camera = world.get_resource::().unwrap(); let x = -(camera.x + camera.x.signum() * 200.0) + camera.x % 400.0; let y = -(camera.y + camera.y.signum() * 200.0) + camera.y % 400.0; let x_range = camera.width as f32 / camera.zoom / 400.0; let y_range = camera.height as f32 / camera.zoom / 400.0; for i in ((-x_range / 2.0) as i32 - 1)..=((x_range / 2.0) as i32 + 1) { for j in ((-y_range / 2.0) as i32 - 1)..=((y_range / 2.0) as i32 + 1) { let model = Translation3::new(x + (i * 400) as f32, y + (j * 400) as f32, 0.0) .to_homogeneous() * Scale3::new(200.0, 200.0, 1.0).to_homogeneous(); gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model.as_slice()); gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0); } } } for (transform, texture) in sprites { if !self.textures.contains_key(&texture.name) { let assets = world.resource::(); let image = match assets.get(texture.name.clone()) { Some(t) => t, None => continue, }; let texture_object = gl .create_texture() .expect("Failed to create texture object"); gl.bind_texture(glow::TEXTURE_2D, Some(texture_object)); gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR_MIPMAP_LINEAR as i32, ); gl.tex_image_2d( glow::TEXTURE_2D, 0, glow::RGBA as i32, image.width as i32, image.height as i32, 0, glow::RGBA, glow::UNSIGNED_BYTE, PixelUnpackData::Slice(Some(&image.bytes)), ); gl.generate_mipmap(glow::TEXTURE_2D); self.textures.insert(texture.name.clone(), texture_object); } // now the texture must exist let model = transform.to_matrix(); let model = model.as_slice(); gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model); gl.bind_texture(glow::TEXTURE_2D, self.textures.get(&texture.name).copied()); gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0); } // paint UI self.egui_glow.paint(window); } }