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<String, glow::Texture>,
pub(crate) send_packet_events: Events<SendPacket>,
pub(crate) recv_packet_events: Events<RecvPacket>,
pub(crate) planet_types: HashMap<PlanetType, (Entity, u32)>, // (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<f64>,
}
pub struct RenderCreateContext {
pub send_packet_events: Events<SendPacket>,
pub recv_packet_events: Events<RecvPacket>,
pub planet_types: HashMap<PlanetType, (Entity, u32)>,
}
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<glow::Context>,
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::<f32>() as i32, 0);
gl.enable_vertex_attrib_array(0);
gl.vertex_attrib_pointer_f32(
1,
2,
glow::FLOAT,
false,
4 * size_of::<f32>() as i32,
2 * size_of::<f32>() 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<Player>>();
// draw the UI
self.egui_glow.run(window, |ctx| {
draw_ui(ctx, &mut *world, &mut self.send_packet_events);
});
let camera = world.get_resource::<Camera>().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::<crate::platform::assets::Assets>();
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::<Camera>().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::<crate::platform::assets::Assets>();
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);
}
}