~starkingdoms/starkingdoms

aa2aadee626d8d90bc9fb67699cda2a588a6a992 — core 11 months ago 859f8c9
switch to matricies for sprite positioning
M Cargo.lock => Cargo.lock +36 -6
@@ 1068,7 1068,7 @@ dependencies = [
 "bevy",
 "bitflags 2.6.0",
 "log",
 "nalgebra",
 "nalgebra 0.32.6",
 "rapier2d",
 "serde",
]


@@ 3687,7 3687,23 @@ dependencies = [
 "num-rational",
 "num-traits",
 "serde",
 "simba",
 "simba 0.8.1",
 "typenum",
]

[[package]]
name = "nalgebra"
version = "0.33.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b"
dependencies = [
 "approx",
 "matrixmultiply",
 "nalgebra-macros",
 "num-complex",
 "num-rational",
 "num-traits",
 "simba 0.9.0",
 "typenum",
]



@@ 4246,12 4262,12 @@ dependencies = [
 "bitflags 1.3.2",
 "downcast-rs",
 "either",
 "nalgebra",
 "nalgebra 0.32.6",
 "num-derive",
 "num-traits",
 "rustc-hash",
 "serde",
 "simba",
 "simba 0.8.1",
 "slab",
 "smallvec",
 "spade",


@@ 4602,13 4618,13 @@ dependencies = [
 "bitflags 1.3.2",
 "crossbeam",
 "downcast-rs",
 "nalgebra",
 "nalgebra 0.32.6",
 "num-derive",
 "num-traits",
 "parry2d",
 "rustc-hash",
 "serde",
 "simba",
 "simba 0.8.1",
]

[[package]]


@@ 5053,6 5069,19 @@ dependencies = [
]

[[package]]
name = "simba"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa"
dependencies = [
 "approx",
 "num-complex",
 "num-traits",
 "paste",
 "wide",
]

[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5227,6 5256,7 @@ dependencies = [
 "egui-winit",
 "futures",
 "image 0.25.5",
 "nalgebra 0.33.2",
 "pollster",
 "thiserror 2.0.9",
 "tracing",

M starkingdoms-client/Cargo.toml => starkingdoms-client/Cargo.toml +1 -0
@@ 20,6 20,7 @@ egui-winit = { version = "0.30", default-features = false, features = ["links", 
egui-wgpu = "0.30"
web-time = "1"
futures = "0.3"
nalgebra = "0.33"

# WASM dependencies
[target.'cfg(target_arch = "wasm32")'.dependencies]

M starkingdoms-client/src/ecs.rs => starkingdoms-client/src/ecs.rs +38 -2
@@ 1,17 1,52 @@
use bevy_ecs::bundle::Bundle;
use bevy_ecs::component::Component;
use bevy_ecs::system::Resource;
use nalgebra::Matrix3;

#[derive(Component, Debug, Clone, Copy)]
pub struct Position {
pub struct Translation {
    pub x: f32,
    pub y: f32,
}
impl Translation {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        Matrix3::from_iterator([
            1.0, 0.0, 0.0,
            0.0, 1.0, 0.0,
            self.x, self.y, 1.0,
        ])
    }
}

#[derive(Component, Debug, Clone, Copy)]
pub struct Scale {
    pub width: f32,
    pub height: f32,
}
impl Scale {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        Matrix3::from_iterator([
            self.width, 0.0, 0.0,
            0.0, self.height, 0.0,
            0.0, 0.0, 1.0
        ])
    }
}
#[derive(Component, Debug, Clone, Copy)]
pub struct Rotation {
    pub radians: f32
}
impl Rotation {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        let x = self.radians.cos();
        let y = self.radians.sin();
        Matrix3::from_iterator([
            x, y, 0.0,
            -y, x, 0.0,
            0.0, 0.0, 1.0
        ])
    }
}

#[derive(Component, Debug, Clone)]
pub struct SpriteTexture {


@@ 20,9 55,10 @@ pub struct SpriteTexture {

#[derive(Bundle)]
pub struct SpriteBundle {
    pub position: Position,
    pub position: Translation,
    pub scale: Scale,
    pub texture: SpriteTexture,
    pub rotation: Rotation
}

#[derive(Resource, Debug)]

M starkingdoms-client/src/lib.rs => starkingdoms-client/src/lib.rs +10 -4
@@ 1,4 1,4 @@
use crate::ecs::{Camera, Position, Scale, SpriteBundle, SpriteTexture};
use crate::ecs::{Camera, Translation, Rotation, Scale, SpriteBundle, SpriteTexture};
use crate::input::MouseWheelEvent;
use crate::rendering::ui::UiRenderable;
use crate::rendering::App;


@@ 54,10 54,16 @@ pub fn start() {
    update_schedule.add_systems(zoom_camera_on_mouse_events);

    world.spawn(SpriteBundle {
        position: Position { x: 0.0, y: 0.0 },
        position: Translation {
            x: 100.0,
            y: 100.0
        },
        scale: Scale {
            width: 50.0,
            height: 50.0,
            width: 100.0,
            height: 100.0,
        },
        rotation: Rotation {
            radians: 45.0_f32.to_radians()
        },
        texture: SpriteTexture {
            texture: "happy-tree".to_string(),

M starkingdoms-client/src/rendering/renderer.rs => starkingdoms-client/src/rendering/renderer.rs +47 -26
@@ 1,4 1,4 @@
use crate::ecs::{Camera, Position, Scale, SpriteTexture};
use crate::ecs::{Camera, Translation, Rotation, Scale, SpriteTexture};
use crate::rendering::mipmap::MipGenerator;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::texture;


@@ 47,7 47,8 @@ pub struct Renderer<T: UiRenderable> {
    pub textures: HashMap<String, texture::Texture>,
    pub mip_generator: MipGenerator,

    pub uniform_buffer: Buffer,
    pub frame_uniform: Buffer,
    pub local_uniform: Buffer,
    pub scale_factor: f64,

    pub window: Arc<Window>,


@@ 159,9 160,15 @@ impl<T: UiRenderable> Renderer<T> {
        );
        let gui_renderer = egui_wgpu::Renderer::new(&device, format.format, None, 1, false);

        let uniform_buffer = device.create_buffer(&BufferDescriptor {
            label: Some("quad uniforms"),
            size: 16,
        let frame_uniform = device.create_buffer(&BufferDescriptor {
            label: Some("frame uniforms"),
            size: 16, // vec2f, vec2f
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let local_uniform = device.create_buffer(&BufferDescriptor {
            label: Some("local uniforms"),
            size: 48, // mat3x3f
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });


@@ 185,7 192,8 @@ impl<T: UiRenderable> Renderer<T> {
            logical_size: LogicalSize::from_physical(size, window.scale_factor()),
            scale_factor: window.scale_factor(),
            window,
            uniform_buffer,
            frame_uniform,
            local_uniform,
            size,
            surface_configuration: format,
        })


@@ 231,16 239,25 @@ impl<T: UiRenderable> Renderer<T> {
                label: Some("command encoder"),
            });

        let mut sprites_to_render: Vec<(Position, Scale, SpriteTexture)> = vec![];
        let mut sprites_to_render: Vec<(Translation, Scale, SpriteTexture, Rotation)> = vec![];

        let mut things_to_render = self.world.query::<(&Position, &Scale, &SpriteTexture)>();
        let mut things_to_render = self.world.query::<(&Translation, &Scale, &SpriteTexture, &Rotation)>();
        for thing in things_to_render.iter_mut(&mut self.world) {
            sprites_to_render.push((*thing.0, *thing.1, thing.2.clone()));
            sprites_to_render.push((*thing.0, *thing.1, thing.2.clone(), *thing.3));
        }

        let cam = self.world.resource::<Camera>();

        for (pos, scale, tex) in sprites_to_render {
        let mut frame_uniform = vec![];
        let frame_uniform_values = [cam.x, cam.y, self.logical_size.width as f32, self.logical_size.height as f32];
        for i in frame_uniform_values {
            let mut bytes = i.to_ne_bytes().to_vec();
            frame_uniform.append(&mut bytes);
        }

        self.queue.write_buffer(&self.frame_uniform, 0, &frame_uniform);

        for (pos, scale, tex, rot) in sprites_to_render {
            let tex = self.textures.entry(tex.texture.clone()).or_insert_with(|| {
                info!("loading texture {}", &tex.texture);
                let b: &[u8] = match tex.texture.as_str() {


@@ 258,28 275,26 @@ impl<T: UiRenderable> Renderer<T> {
                )
            });

            // need to calculate width, height, x, and y, using logical size & aspect
            let xy_matrix = pos.as_matrix();
            let rot_matrix = rot.as_matrix();
            let scale_matrix = scale.as_matrix();

            // calculate "viewport position" w/ the camera
            let viewport_x = pos.x - cam.x;
            let viewport_y = pos.y - cam.y;
            let scaled_w = scale.width * cam.zoom;
            let scaled_h = scale.height * cam.zoom;
            let transform_matrix = xy_matrix * rot_matrix * scale_matrix;

            let x_screen = viewport_x / self.logical_size.width as f32;
            let y_screen = viewport_y / self.logical_size.height as f32;
            let w_screen = scaled_w / self.logical_size.width as f32;
            let h_screen = scaled_h / self.logical_size.height as f32;

            let mut uniform_buffer = vec![];
            let uniform_buffer_data = [w_screen, h_screen, x_screen, y_screen];
            for i in uniform_buffer_data {
            let mut local_buffer = vec![];
            let local_buffer_data = [
                transform_matrix.m11, transform_matrix.m12, transform_matrix.m13, 0.0,
                transform_matrix.m21, transform_matrix.m22, transform_matrix.m23, 0.0,
                transform_matrix.m31, transform_matrix.m32, transform_matrix.m33, 0.0,
            ];
            for i in local_buffer_data {
                let mut bytes = i.to_ne_bytes().to_vec();
                uniform_buffer.append(&mut bytes);
                local_buffer.append(&mut bytes);
            }

            self.queue
                .write_buffer(&self.uniform_buffer, 0, &uniform_buffer);
                .write_buffer(&self.local_uniform, 0, &local_buffer);

            let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
                label: Some("test_bind_group"),


@@ 298,7 313,13 @@ impl<T: UiRenderable> Renderer<T> {
                    BindGroupEntry {
                        binding: 2,
                        resource: BindingResource::Buffer(
                            self.uniform_buffer.as_entire_buffer_binding(),
                            self.frame_uniform.as_entire_buffer_binding(),
                        ),
                    },
                    BindGroupEntry {
                        binding: 3,
                        resource: BindingResource::Buffer(
                            self.local_uniform.as_entire_buffer_binding(),
                        ),
                    },
                ],

M starkingdoms-client/src/shaders/sprite.wgsl => starkingdoms-client/src/shaders/sprite.wgsl +29 -14
@@ 3,28 3,43 @@ struct VertexShaderOut {
    @location(0) texcoord: vec2<f32>
}

struct Uniforms {
    scale: vec2f,
    offset: vec2f
struct FrameUniforms {
    camera_position: vec2f,
    viewport_size: vec2f
}
@group(0) @binding(2) var<uniform> uni: Uniforms;
struct LocalUniforms {
    transform: mat3x3f,
}
@group(0) @binding(2) var<uniform> frame_uni: FrameUniforms;
@group(0) @binding(3) var<uniform> local_uni: LocalUniforms;

@vertex fn vs(
    @builtin(vertex_index) vertexIndex : u32
) -> VertexShaderOut {
    let pos = array(
        vec2<f32>(0.0, 0.0),
        vec2<f32>(1.0, 0.0),
        vec2<f32>(0.0, 1.0),
        vec2<f32>(0.0, 1.0),
        vec2<f32>(1.0, 0.0),
        vec2<f32>(1.0, 1.0)
    );
            vec2<f32>(0.0, 0.0),
            vec2<f32>(1.0, 0.0),
            vec2<f32>(0.0, 1.0),
            vec2<f32>(0.0, 1.0),
            vec2<f32>(1.0, 0.0),
            vec2<f32>(1.0, 1.0)
        );

    var vsOutput: VertexShaderOut;
    let xy = pos[vertexIndex];
    vsOutput.position = vec4f(xy * uni.scale + uni.offset, 0.0, 1.0);
    vsOutput.texcoord = vec2f(xy.x, 1.0 - xy.y);

    let position = (local_uni.transform * vec3f(pos[vertexIndex], 1)).xy;
    // convert from pixels to 0.0 to 1.0
    let zeroToOne = position / frame_uni.viewport_size;
    // convert from 0 - 1 to 0 - 2
    let zeroToTwo = zeroToOne * 2.0;
    // convert from 0 - 2 to -1 - +1 (clip space)
    let flippedClipSpace = zeroToTwo - 1.0;
    // flip Y
    let clipSpace = flippedClipSpace * vec2f(1, -1);

    vsOutput.position = vec4f(clipSpace, 0.0, 1.0);
    vsOutput.texcoord = pos[vertexIndex];

    return vsOutput;
}