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;
}