use std::collections::HashMap;
use std::num::NonZeroU32;
use std::sync::Arc;
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::query::With;
use bevy_ecs::world::World;
use egui_glow::EguiGlow;
use glow::{HasContext, PixelUnpackData};
#[cfg(not(target_arch = "wasm32"))]
use glutin::surface::{GlSurface, Surface, SwapInterval, WindowSurface};
#[cfg(not(target_arch = "wasm32"))]
use glutin::{
config::{ConfigTemplateBuilder, GlConfig},
context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext},
display::GetGlDisplay,
prelude::{GlDisplay, NotCurrentGlContext},
};
#[cfg(not(target_arch = "wasm32"))]
use glutin_winit::{DisplayBuilder, GlWindow};
use nalgebra::{Scale3, Translation3, Vector3};
use starkingdoms_common::packet::{ButtonType, Packet};
use starkingdoms_common::PlanetType;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{prelude::Closure, JsCast};
#[cfg(target_arch = "wasm32")]
use web_sys::{Event, HtmlCanvasElement};
use winit::dpi::PhysicalPosition;
use winit::event::{ElementState, MouseButton, MouseScrollDelta};
use winit::event_loop::ControlFlow;
use winit::keyboard::{KeyCode, PhysicalKey};
#[cfg(target_arch = "wasm32")]
use winit::platform::web::{WindowAttributesExtWebSys, WindowExtWebSys};
use winit::{
application::ApplicationHandler,
dpi::LogicalSize,
event::WindowEvent,
event_loop::ActiveEventLoop,
raw_window_handle::HasWindowHandle,
window::{Window},
};
use crate::components::{Camera, Menu, Player, RecvPacket, SendPacket, Texture, Transform};
use crate::networking::process_packets;
use crate::networking::ws::Ws;
use crate::ui::{draw_ui, init_ui};
use assets::AssetLoader;
pub mod assets;
#[derive(Default)]
pub struct App {
window: Option<Window>,
world: World,
program: Option<glow::Program>,
vertex_array: Option<glow::VertexArray>,
vertex_buffer: Option<glow::Buffer>,
element_buffer: Option<glow::Buffer>,
#[cfg(not(target_arch = "wasm32"))]
gl_surface: Option<Surface<WindowSurface>>,
#[cfg(not(target_arch = "wasm32"))]
gl_context: Option<PossiblyCurrentContext>,
egui_glow: Option<EguiGlow>,
gl: Option<Arc<glow::Context>>,
textures: HashMap<String, glow::Texture>,
send_packet_events: Events<SendPacket>,
recv_packet_events: Events<RecvPacket>,
planet_types: HashMap<PlanetType, (Entity, u32)>, // (world entity, server id)
up: bool,
down: bool,
left: bool,
right: bool,
mouse_pos: PhysicalPosition<f64>,
}
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 App {
pub fn new(
world: World,
send_packet_events: Events<SendPacket>,
recv_packet_events: Events<RecvPacket>,
) -> Self {
Self {
world,
send_packet_events,
recv_packet_events,
..Default::default()
}
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
#[cfg(target_arch = "wasm32")]
let attributes = {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("canvas").unwrap();
let canvas = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
canvas.set_width(
web_sys::window()
.unwrap()
.inner_width()
.unwrap()
.as_f64()
.unwrap() as u32,
);
canvas.set_height(
web_sys::window()
.unwrap()
.inner_height()
.unwrap()
.as_f64()
.unwrap() as u32,
);
Window::default_attributes()
.with_title("StarKingdoms.TK")
.with_canvas(Some(canvas))
};
#[cfg(not(target_arch = "wasm32"))]
let attributes = {
Window::default_attributes()
.with_transparent(true)
.with_title("StarKingdoms.TK")
.with_inner_size(LogicalSize::new(400, 300))
};
self.window = Some(event_loop.create_window(attributes.clone()).unwrap());
let window = self.window.as_ref().unwrap();
#[cfg(target_arch = "wasm32")]
let context = window
.canvas()
.unwrap()
.get_context("webgl2")
.unwrap()
.unwrap()
.dyn_into::<web_sys::WebGl2RenderingContext>()
.unwrap();
#[cfg(target_arch = "wasm32")]
let (gl, shader_version) = (
Arc::new(glow::Context::from_webgl2_context(context)),
"#version 300 es",
);
#[cfg(not(target_arch = "wasm32"))]
let (gl, shader_version) = unsafe {
let template = ConfigTemplateBuilder::new().with_transparency(true);
let display_builder = DisplayBuilder::new().with_window_attributes(Some(attributes));
let (window, gl_config) = display_builder
.build(event_loop, template, |configs| {
configs
.reduce(|accum, config| {
let supports_transparency =
config.supports_transparency().unwrap_or(false)
&& !accum.supports_transparency().unwrap_or(false);
if supports_transparency || config.num_samples() > accum.num_samples() {
config
} else {
accum
}
})
.unwrap()
})
.unwrap();
let raw_handle = window.as_ref().map(|window| {
window
.window_handle()
.unwrap()
.window_handle()
.unwrap()
.as_raw()
});
let gl_display = gl_config.display();
let context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::OpenGl(Some(glutin::context::Version {
major: 3,
minor: 0,
})))
.build(raw_handle);
let not_current_gl_context = gl_display
.create_context(&gl_config, &context_attributes)
.unwrap();
let window = window.unwrap();
let surface_attributes = window.build_surface_attributes(Default::default()).unwrap();
let gl_surface = gl_display
.create_window_surface(&gl_config, &surface_attributes)
.unwrap();
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
let gl = glow::Context::from_loader_function_cstr(|s| gl_display.get_proc_address(s));
gl_surface
.set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
.unwrap();
self.gl_surface = Some(gl_surface);
self.gl_context = Some(gl_context);
(Arc::new(gl), "#version 300 es")
};
unsafe {
let shaders = [
(
"vertex",
include_str!("../shaders/vertex.glsl"),
glow::VERTEX_SHADER,
),
(
"fragment",
include_str!("../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, &format!("{}\n{}", shader_version, 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.viewport(
0,
0,
window.inner_size().width as i32,
window.inner_size().height as i32,
);
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
self.program = Some(program);
self.vertex_array = Some(vertex_array);
self.vertex_buffer = Some(vertex_buffer);
self.element_buffer = Some(element_buffer);
}
#[cfg(target_arch = "wasm32")]
web_sys::window().unwrap().set_onresize(Some(
Closure::<dyn Fn(Event)>::new(move |_| {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("canvas").unwrap();
let canvas = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.map_err(|_| ())
.unwrap();
canvas.set_width(
web_sys::window()
.unwrap()
.inner_width()
.unwrap()
.as_f64()
.unwrap() as u32,
);
canvas.set_height(
web_sys::window()
.unwrap()
.inner_height()
.unwrap()
.as_f64()
.unwrap() as u32,
);
})
.into_js_value()
.as_ref()
.unchecked_ref(),
));
let egui_glow = egui_glow::EguiGlow::new(event_loop, gl.clone(), None, None, true);
init_ui(egui_glow.egui_ctx.clone());
self.egui_glow = Some(egui_glow);
self.gl = Some(gl);
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
let event_response = self
.egui_glow
.as_mut()
.unwrap()
.on_window_event(self.window.as_ref().unwrap(), &event);
if event_response.consumed {
return;
}
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(size) => {
#[cfg(not(target_arch = "wasm32"))]
self.gl_surface.as_ref().unwrap().resize(
self.gl_context.as_ref().unwrap(),
NonZeroU32::new(size.width).unwrap(),
NonZeroU32::new(size.height).unwrap(),
);
let mut camera = self.world.get_resource_mut::<Camera>().unwrap();
camera.width = size.width;
camera.height = size.height;
unsafe {
self.gl
.as_ref()
.unwrap()
.viewport(0, 0, size.width as i32, size.height as i32);
}
}
WindowEvent::MouseWheel { delta, .. } => {
let mut camera = self.world.get_resource_mut::<Camera>().unwrap();
let raw_delta = match delta {
MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
MouseScrollDelta::LineDelta(y, ..) => y,
};
let delta = 1.1;
if raw_delta < 0.0 {
camera.zoom = (camera.zoom * 1.0 / delta).max(0.13);
} else {
camera.zoom = (camera.zoom * delta).min(5.0);
}
}
WindowEvent::KeyboardInput { ref event, .. } => {
match event.physical_key {
PhysicalKey::Code(key) => {
let matched: bool;
match event.state {
ElementState::Pressed => match key {
KeyCode::KeyW => {
self.up = true;
matched = true;
}
KeyCode::KeyS => {
self.down = true;
matched = true;
}
KeyCode::KeyA => {
self.left = true;
matched = true;
}
KeyCode::KeyD => {
self.right = true;
matched = true;
}
_ => matched = false,
},
ElementState::Released => match key {
KeyCode::KeyW => {
self.up = false;
matched = true;
}
KeyCode::KeyS => {
self.down = false;
matched = true;
}
KeyCode::KeyA => {
self.left = false;
matched = true;
}
KeyCode::KeyD => {
self.right = false;
matched = true;
}
_ => matched = false,
},
}
if matched {
self.send_packet_events
.send(SendPacket(Packet::PlayerInput {
up: self.up,
down: self.down,
left: self.left,
right: self.right,
}));
}
}
PhysicalKey::Unidentified(_) => {} // unsupported
}
}
WindowEvent::CursorMoved { position, .. } => self.mouse_pos = position,
WindowEvent::MouseInput { state, button, .. } => {
let button = match button {
MouseButton::Left => ButtonType::Left,
MouseButton::Middle => ButtonType::Middle,
MouseButton::Right => ButtonType::Right,
_ => return,
};
if state.is_pressed() {
let mut menu_query = self.world.query_filtered::<Entity, With<Menu>>();
let mut menus = Vec::new();
for menu in menu_query.iter(&self.world) {
menus.push(menu);
}
for menu in menus {
self.world.entity_mut(menu).remove::<Menu>();
}
}
let camera = self.world.get_resource::<Camera>().unwrap();
let view = camera.to_cursor_matrix();
let pos =
view * Vector3::new(self.mouse_pos.x as f32, self.mouse_pos.y as f32, 1.0);
let pos = pos / pos.z;
self.send_packet_events
.send(SendPacket(Packet::PlayerMouseInput {
x: pos.x,
y: pos.y,
released: !state.is_pressed(),
button,
}));
}
_ => {}
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let mut ws = self
.world
.get_resource_mut::<Ws>()
.expect("Failed to get Ws resource");
#[cfg(target_arch = "wasm32")]
while let Ok(Some(packet)) = ws.receiver.try_next() {
self.recv_packet_events.send(RecvPacket(packet));
}
#[cfg(not(target_arch = "wasm32"))]
for packet in ws.receiver.iter() {
self.recv_packet_events.send(RecvPacket(packet));
}
self.send_packet_events.update();
self.recv_packet_events.update();
process_packets(
&mut self.world,
&mut self.send_packet_events,
&mut self.recv_packet_events,
&mut self.planet_types,
);
let gl = self.gl.as_ref().unwrap();
let mut player = self.world.query_filtered::<&Transform, With<Player>>();
self.egui_glow
.as_mut()
.unwrap()
.run(self.window.as_ref().unwrap(), |ctx| {
draw_ui(ctx, &mut self.world, &mut self.send_packet_events);
});
let camera = self.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 = self.world.query::<(&Transform, &mut Texture)>();
let mut sprites = Vec::new();
for (transform, texture) in sprite_query.iter(&self.world) {
sprites.push((transform, texture));
}
unsafe {
gl.clear(glow::COLOR_BUFFER_BIT);
gl.use_program(self.program);
gl.bind_vertex_array(self.vertex_array);
gl.bind_buffer(glow::ARRAY_BUFFER, self.vertex_buffer);
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, self.element_buffer);
gl.active_texture(glow::TEXTURE0);
let view_loc = gl.get_uniform_location(self.program.unwrap(), "view");
let model_loc = gl.get_uniform_location(self.program.unwrap(), "model");
gl.uniform_matrix_4_f32_slice(view_loc.as_ref(), true, view);
if !self.textures.contains_key("starfield.svg") {
let assets = self.world.resource::<crate::platform::assets::Assets>();
match assets.get("starfield.svg") {
Some(image) => {
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);
}
None => {}
}
}
if self.textures.contains_key("starfield.svg") {
gl.bind_texture(glow::TEXTURE_2D, self.textures.get("starfield.svg").copied());
let camera = self.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 = self.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);
}
}
self.egui_glow
.as_mut()
.unwrap()
.paint(self.window.as_ref().unwrap());
#[cfg(not(target_arch = "wasm32"))]
self.gl_surface
.as_ref()
.unwrap()
.swap_buffers(self.gl_context.as_ref().unwrap())
.unwrap();
let mut ws = self
.world
.get_resource_mut::<Ws>()
.expect("Failed to get Ws resource");
let mut send_event_cursor = self.send_packet_events.get_cursor();
for event in send_event_cursor.read(&self.send_packet_events) {
ws.send_packet(event.0.clone());
}
event_loop.set_control_flow(ControlFlow::WaitUntil(
web_time::Instant::now()
.checked_add(web_time::Duration::from_millis(16))
.unwrap(),
));
}
}