~starkingdoms/starkingdoms

4174a429888ef87273b8513556a6a259b52798b9 — ghostly_zsh 8 months ago 27a53b0
camera and other transform added, also bevy
A crates/client/src/components.rs => crates/client/src/components.rs +28 -0
@@ 0,0 1,28 @@
use bevy_ecs::{component::Component, system::Resource};
use nalgebra::{Matrix4, Rotation3, Scale3, Translation3};

#[derive(Component)]
pub struct Texture {
    pub name: String,
}

#[derive(Component, Debug)]
pub struct Transform {
    pub translation: Translation3<f32>,
    pub rotation: Rotation3<f32>,
    pub scale: Scale3<f32>,
}
impl Transform {
    pub fn to_matrix(&self) -> Matrix4<f32> {
        self.translation.to_homogeneous() * self.rotation.to_homogeneous() * self.scale.to_homogeneous()
    }
}

#[derive(Resource, Debug)]
pub struct Camera {
    pub x: f32,
    pub y: f32,
    pub zoom: f32,
    pub width: u32, // screen width (these are for aspect ratio)
    pub height: u32, // screen height
}

M crates/client/src/lib.rs => crates/client/src/lib.rs +23 -3
@@ 1,4 1,7 @@
use rendering::App;
use bevy_ecs::world::World;
use components::{Camera, Texture, Transform};
use nalgebra::{Rotation2, Rotation3, Scale2, Scale3, Translation2, Translation3, Vector3};
use rendering::{assets::Assets, App};
use tracing::info;
use winit::event_loop::{ControlFlow, EventLoop};



@@ 10,6 13,7 @@ pub mod platform;
pub mod platform;

pub mod rendering;
pub mod components;

// Hi, you've found the real main function! This is called AFTER platform-specific initialization code.
pub fn start() {


@@ 24,10 28,26 @@ pub fn start() {
    );

    info!("Creating the ECS world...");
    let mut world = World::new();

    world.insert_resource(Camera {
        x: 0.0,
        y: 0.0,
        zoom: 1.0,
        width: 0,
        height: 0,
    });

    world.insert_resource(Assets::new());

    world.spawn((Transform {
        translation: Translation3::new(0.0, 0.0, 0.0),
        rotation: Rotation3::from_axis_angle(&Vector3::z_axis(), 0.0),
        scale: Scale3::new(20.0, 20.0, 1.0),
    }, Texture { name: "hearty.svg".to_string() }));

    let event_loop = EventLoop::new().unwrap();
    
    event_loop.set_control_flow(ControlFlow::Wait);

    event_loop.run_app(&mut App::default()).unwrap();
    event_loop.run_app(&mut App::new(world)).unwrap();
}

A crates/client/src/rendering/assets_native.rs => crates/client/src/rendering/assets_native.rs +15 -0
@@ 0,0 1,15 @@
use bevy_ecs::system::Resource;

#[derive(Resource)]
pub struct Assets {

}
impl Assets {
    pub fn new() -> Self {
        Assets {  }
    }
    pub fn get(&self, local_path: impl Into<String>) -> Option<Vec<u8>> {
        std::fs::read(format!("src/textures/{}", local_path.into())).ok()
    }
}


A crates/client/src/rendering/assets_wasm.rs => crates/client/src/rendering/assets_wasm.rs +111 -0
@@ 0,0 1,111 @@
use std::{collections::HashMap, fmt::Display, sync::{Arc, Mutex}};

use bevy_ecs::system::Resource;
use image::EncodableLayout;
use poll_promise::Promise;
use resvg::{tiny_skia, usvg};

#[derive(Debug, Clone)]
pub enum AssetError {
    AssetNotFound,
    ResponseNotOk(u16),
}
impl Display for AssetError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AssetError::AssetNotFound => write!(f, "Asset not found"),
            AssetError::ResponseNotOk(code) => write!(f, "Server response was not ok {}", code),
        }
    }
}
impl std::error::Error for AssetError {}

#[derive(Debug, Clone)]
pub struct ImgData {
    pub bytes: Vec<u8>,
    pub width: u32,
    pub height: u32,
}

#[derive(Resource)]
pub struct Assets {
    texture_promises: Arc<Mutex<HashMap<String, Promise<ImgData>>>>,
    textures: Arc<Mutex<HashMap<String, ImgData>>>,
}

impl Assets {
    pub fn new() -> Self {
        Assets {
            textures: Arc::new(Mutex::new(HashMap::new())),
            texture_promises: Arc::new(Mutex::new(HashMap::new())),
        }
    }
    pub fn get(&self, local_path: impl Into<String>) -> Option<ImgData> {
        let local_path = local_path.into();
        let contains_texture = {
            self.textures.lock().unwrap().contains_key(&local_path)
        };
        let contains_texture_promise = {
            self.texture_promises.lock().unwrap().contains_key(&local_path)
        };
        if !contains_texture && !contains_texture_promise {
            let local_path_clone = local_path.clone();
            let request_promise = poll_promise::Promise::spawn_local(async move {
                let window = web_sys::window().unwrap();
                let request = ehttp::Request::get(format!("{}/src/assets/{}", window.location().origin().unwrap(), local_path_clone));
                let response = match ehttp::fetch_async(request).await {
                    Ok(resp) => resp,
                    Err(e) => {
                        panic!("{}", e);
                    },
                };
                if local_path_clone.ends_with(".svg") {
                    let opt = usvg::Options {
                        default_size: usvg::Size::from_wh(20.0, 20.0).unwrap(),
                        ..Default::default()
                    };
                    let tree = usvg::Tree::from_data(&response.bytes, &opt).expect(&format!("Couldn't parse svg {}", local_path_clone));
                    let tree_size = tree.size().to_int_size();
                    let size = usvg::Size::from_wh(200.0, 200.0).unwrap().to_int_size();
                    assert!(size.width() > 0 && size.height() > 0);
                    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).expect("Failed to construct pixmap");
                    resvg::render(&tree, tiny_skia::Transform::from_scale((size.width() as f32)/(tree_size.height() as f32), (size.height() as f32)/(tree_size.height() as f32)), &mut pixmap.as_mut());
                    let data = ImgData {
                        bytes: pixmap.data().to_vec(),
                        width: size.width(),
                        height: size.height(),
                    };
                    
                    data
                } else if local_path_clone.ends_with(".png") {
                    let img = image::load_from_memory(&response.bytes).unwrap();
                    let rgba = img.to_rgba8();
                    let data = ImgData {
                        bytes: rgba.as_bytes().to_vec(),
                        width: rgba.width(),
                        height: rgba.height(),
                    };
                    data
                } else {
                    panic!("Unsupported sprite type");
                }
            });
            {
                self.texture_promises.lock().unwrap().insert(local_path.clone(), request_promise);
            }
            None
        } else if !contains_texture {
            let mut texture_promises = self.texture_promises.lock().unwrap();
            let promise = texture_promises.get_mut(&local_path).unwrap();
            let mut returned_value = None;
            if let Some(texture) = promise.ready() {
                self.textures.lock().unwrap().insert(local_path.clone(), texture.clone());
                returned_value = Some(texture.clone());
                texture_promises.remove(&local_path);
            }
            return returned_value
        } else {
            self.textures.lock().unwrap().get(&local_path).cloned()
        }
    }
}

D crates/client/src/rendering/init.rs => crates/client/src/rendering/init.rs +0 -0
M crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +75 -15
@@ 1,6 1,9 @@
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::sync::Arc;

use assets::Assets;
use bevy_ecs::world::World;
use egui_glow::EguiGlow;
use glow::{HasContext, PixelUnpackData};
#[cfg(not(target_arch = "wasm32"))]


@@ 18,22 21,30 @@ use winit::event_loop::ControlFlow;
use winit::platform::web::{WindowAttributesExtWebSys, WindowExtWebSys};
use winit::{application::ApplicationHandler, dpi::LogicalSize, event::WindowEvent, event_loop::ActiveEventLoop, raw_window_handle::HasWindowHandle, window::{Window, WindowAttributes}};

pub mod init;
use crate::components::{Camera, Texture, Transform};
#[cfg(not(target_arch="wasm32"))]
#[path = "assets_native.rs"]
pub mod assets;
#[cfg(target_arch="wasm32")]
#[path = "assets_wasm.rs"]
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>,
    texture_object: Option<glow::Texture>,
    #[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>,
}

const VERTICES: [f32; 16] = [


@@ 47,6 58,15 @@ const INDICES: [u32; 6] = [
    2, 3, 0,
];

impl App {
    pub fn new(world: World) -> Self {
        Self {
            world,
            ..Default::default()
        }
    }
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        #[cfg(target_arch = "wasm32")]


@@ 157,15 177,6 @@ impl ApplicationHandler for App {
            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);

            let texture = gl.create_texture().expect("Failed to create texture object");
            gl.active_texture(glow::TEXTURE0);
            gl.bind_texture(glow::TEXTURE_2D, Some(texture));
            let image = image::load_from_memory(include_bytes!("../assets/happy-tree.png")).unwrap();
            let image = image.to_rgba8();
            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.into_raw())));
            gl.generate_mipmap(glow::TEXTURE_2D);

            gl.clear_color(0.0, 0.0, 0.0, 0.0);
            gl.viewport(0, 0, window.inner_size().width as i32, window.inner_size().height as i32);


@@ 176,7 187,6 @@ impl ApplicationHandler for App {
            self.vertex_array = Some(vertex_array);
            self.vertex_buffer = Some(vertex_buffer);
            self.element_buffer = Some(element_buffer);
            self.texture_object = Some(texture);
        }
        #[cfg(target_arch = "wasm32")]
        web_sys::window().unwrap().set_onresize(Some(Closure::<dyn Fn(Event)>::new(move |_| {


@@ 207,6 217,10 @@ impl ApplicationHandler for App {
                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);
                }


@@ 230,15 244,61 @@ impl ApplicationHandler for App {
            },
        );

        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.active_texture(glow::TEXTURE0);
            gl.bind_texture(glow::TEXTURE_2D, self.texture_object);
            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.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
            gl.active_texture(glow::TEXTURE0);
            
            let view_loc = gl.get_uniform_location(self.program.unwrap(), "view");
            gl.uniform_matrix_4_f32_slice(view_loc.as_ref(), true, view);
            let model_loc = gl.get_uniform_location(self.program.unwrap(), "model");

            for (transform, texture) in sprites {
                if !self.textures.contains_key(&texture.name) {
                    let assets = self.world.resource::<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_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());

M crates/client/src/shaders/vertex.glsl => crates/client/src/shaders/vertex.glsl +4 -3
@@ 3,11 3,12 @@ precision mediump float;
in vec2 pos;
in vec2 texcoord;

out vec2 v_pos;
out vec2 v_texcoord;

uniform mat4 model;
uniform mat4 view;

void main() {
    v_pos = pos;
    v_texcoord = texcoord;
    gl_Position = vec4(v_pos, 0.0, 1.0);
    gl_Position = view * model * vec4(pos, 0.0, 1.0);
}