diff --git a/src/doodad.rs b/src/doodad.rs index a4277dd..82cb204 100644 --- a/src/doodad.rs +++ b/src/doodad.rs @@ -8,12 +8,13 @@ pub struct Doodad { } impl Spriteable for Doodad { - fn sprite(&self, camera: &Camera) -> Sprite { + fn sprite(&self, camera: Camera) -> Sprite { return Sprite { position: self.pos, - camera: camera.pos, + camera: camera, name: self.sprite.clone(), angle: Deg { 0: 0.0 }, + scale: 1.0, }; } } diff --git a/src/main.rs b/src/main.rs index eb35450..9d0a0b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ use anyhow::Result; use cgmath::{Deg, Point2}; use winit::{ - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ + ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase, + VirtualKeyCode, WindowEvent, + }, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, }; @@ -22,12 +25,18 @@ use crate::{ system::System, }; +#[derive(Debug, Clone, Copy)] struct Camera { + // Camera center pos: Point2, + + // Camera zoom + // (How many game units tall is the viewport?) + zoom: Pfloat, } trait Spriteable { - fn sprite(&self, camera: &Camera) -> Sprite; + fn sprite(&self, camera: Camera) -> Sprite; } struct Sprite { @@ -37,12 +46,14 @@ struct Sprite { // This object's position, in world coordinates. position: Point2, + scale: Pfloat, + // This sprite's rotation // (relative to north, measured ccw) angle: Deg, - // The camera we want to draw this sprite from, in world coordinates - camera: Point2, + // The camera we want to draw this sprite from + camera: Camera, } struct Game { @@ -61,6 +72,7 @@ impl Game { player: Ship::new(ShipKind::Gypsum, (0.0, 0.0).into()), camera: Camera { pos: (0.0, 0.0).into(), + zoom: 500.0, }, system: System::new(), } @@ -95,8 +107,8 @@ impl Game { fn sprites(&self) -> Vec { let mut sprites: Vec = Vec::new(); - sprites.append(&mut self.system.sprites(&self.camera)); - sprites.push(self.player.sprite(&self.camera)); + sprites.append(&mut self.system.sprites(self.camera)); + sprites.push(self.player.sprite(self.camera)); return sprites; } diff --git a/src/render/gpu.rs b/src/render/gpu.rs index 2bd1b0c..09a1bd9 100644 --- a/src/render/gpu.rs +++ b/src/render/gpu.rs @@ -25,14 +25,6 @@ pub struct GPUState { instance_buffer: wgpu::Buffer, } -#[rustfmt::skip] -const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0, -); - struct Instance { transform: Transform, texture_index: u32, @@ -40,7 +32,7 @@ struct Instance { impl Instance { fn to_raw(&self) -> InstanceRaw { InstanceRaw { - model: (self.transform.build_view_projection_matrix()).into(), + model: (self.transform.to_matrix()).into(), texture_index: self.texture_index, } } @@ -100,23 +92,49 @@ impl InstanceRaw { } } +/// API correction matrix. +/// cgmath uses OpenGL's matrix format, which +/// needs to be converted to wgpu's matrix format. +#[rustfmt::skip] +const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0, +); + struct Transform { - pos: Point2, - aspect: f32, // width / height - scale: f32, - rotate: Deg, // Around this object's center, in degrees measured ccw from vertical + pos: Point2, // position on screen + screen_aspect: f32, // width / height. Screen aspect ratio. + aspect: f32, // width / height. Sprite aspect ratio. + scale: f32, // if scale = 1, this sprite will be as tall as the screen. + rotate: Deg, // Around this object's center, in degrees measured ccw from vertical } impl Transform { - fn build_view_projection_matrix(&self) -> Matrix4 { - // Apply aspect ratio and scale - let mut scale = Matrix4::from_nonuniform_scale(1.0, 1.0 / self.aspect, 1.0); - scale = scale * Matrix4::from_scale(self.scale); + /// Build a matrix that corresponds to this transformation. + fn to_matrix(&self) -> Matrix4 { + // Note that our mesh starts centered at (0, 0). + // This is essential---we do not want scale and rotation + // changing our sprite's position! - // Our mesh starts at (0, 0), so this will rotate around the object's center. - // Note that we translate AFTER scaling. + // Apply sprite aspect ratio, preserving height. + // This must be done *before* rotation. + let sprite_aspect = Matrix4::from_nonuniform_scale(self.aspect, 1.0, 1.0); + + // Apply provided scale + let scale = Matrix4::from_scale(self.scale); + + // Apply rotation let rotate = Matrix4::from_angle_z(self.rotate); + // Apply screen aspect ratio, again preserving height. + // This must be done AFTER rotation... think about it! + let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.screen_aspect, 1.0, 1.0); + + // After finishing all op, translate. + // This must be done last, all other operations + // require us to be at (0, 0). let translate = Matrix4::from_translation(Vector3 { x: self.pos.x, y: self.pos.y, @@ -124,8 +142,8 @@ impl Transform { }); // Order matters! - // These are applied right-to-left - return OPENGL_TO_WGPU_MATRIX * translate * rotate * scale; + // The rightmost matrix is applied first. + return OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale * sprite_aspect; } } @@ -158,23 +176,29 @@ impl Vertex { } } -// This is centered at 0,0 intentionally, -// so scaling works properly. +// These vertices form a rectangle that covers the whole screen. +// Two facts are important to note: +// - This is centered at (0, 0), so scaling doesn't change a sprite's position +// - At scale = 1, this covers the whole screen. Makes scale calculation easier. +// +// Screen coordinates range from -1 to 1, with the origin at the center. +// Texture coordinates range from 0 to 1, with the origin at the top-left +// and (1,1) at the bottom-right. const VERTICES: &[Vertex] = &[ Vertex { - position: [-0.5, 0.5, 0.0], + position: [-1.0, 1.0, 0.0], tex_coords: [0.0, 0.0], }, Vertex { - position: [0.5, 0.5, 0.0], + position: [1.0, 1.0, 0.0], tex_coords: [1.0, 0.0], }, Vertex { - position: [0.5, -0.5, 0.0], + position: [1.0, -1.0, 0.0], tex_coords: [1.0, 1.0], }, Vertex { - position: [-0.5, -0.5, 0.0], + position: [-1.0, -1.0, 0.0], tex_coords: [0.0, 1.0], }, ]; @@ -402,16 +426,17 @@ impl GPUState { // TODO: warning when too many sprites are drawn. let mut instances: Vec = Vec::new(); for s in sprites { - // Compute position on screen - let screen_pos: Point2 = (s.position - s.camera.to_vec()) / 400.0; - + // Compute position on screen, + // using logical pixels + let screen_pos: Point2 = (s.position - s.camera.pos.to_vec()) / s.camera.zoom; let texture = self.texture_array.get_texture(&s.name[..]); instances.push(Instance { transform: Transform { pos: screen_pos, - aspect: texture.aspect / screen_aspect, - scale: 0.25, + aspect: texture.aspect, + screen_aspect, + scale: s.scale * (texture.height / s.camera.zoom), rotate: s.angle, }, texture_index: texture.index, diff --git a/src/render/texturearray.rs b/src/render/texturearray.rs index b2426cc..280aa69 100644 --- a/src/render/texturearray.rs +++ b/src/render/texturearray.rs @@ -11,12 +11,12 @@ pub struct TextureArray { texture_indices: HashMap, } -const TEX: &[&str] = &["error", "gypsum", "earth", "a0"]; +const TEX: &[&str] = &["error", "red", "gypsum", "earth", "a0"]; pub struct Texture { - pub index: u32, - pub dimensions: (u32, u32), - pub aspect: f32, + pub index: u32, // Index in texture array + pub aspect: f32, // width / height + pub height: f32, // Height in game units } impl TextureArray { @@ -30,7 +30,7 @@ impl TextureArray { return Texture { index, - dimensions, + height: 100.0, aspect: dimensions.0 as f32 / dimensions.1 as f32, }; } diff --git a/src/ship.rs b/src/ship.rs index 969405e..8561e0a 100644 --- a/src/ship.rs +++ b/src/ship.rs @@ -33,12 +33,13 @@ impl Ship { } impl Spriteable for Ship { - fn sprite(&self, camera: &Camera) -> Sprite { + fn sprite(&self, camera: Camera) -> Sprite { return Sprite { position: self.body.pos, - camera: camera.pos, + camera: camera, name: self.kind.sprite().to_owned(), angle: self.body.angle, + scale: 1.0, }; } } diff --git a/src/system.rs b/src/system.rs index 9600b9d..3b30d90 100644 --- a/src/system.rs +++ b/src/system.rs @@ -17,7 +17,7 @@ impl System { s.bodies.push(Doodad { pos: Polar { center: (0.0, 0.0).into(), - radius: 300.0, + radius: 100.0, angle: Deg { 0: 31.0 }, } .to_cartesian(), @@ -32,7 +32,7 @@ impl System { return s; } - pub fn sprites(&self, camera: &Camera) -> Vec { + pub fn sprites(&self, camera: Camera) -> Vec { return self.bodies.iter().map(|x| x.sprite(camera)).collect(); } }