Fixed transformations
parent
6e13c91d37
commit
13f74b9d85
|
@ -8,12 +8,13 @@ pub struct Doodad {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spriteable for Doodad {
|
impl Spriteable for Doodad {
|
||||||
fn sprite(&self, camera: &Camera) -> Sprite {
|
fn sprite(&self, camera: Camera) -> Sprite {
|
||||||
return Sprite {
|
return Sprite {
|
||||||
position: self.pos,
|
position: self.pos,
|
||||||
camera: camera.pos,
|
camera: camera,
|
||||||
name: self.sprite.clone(),
|
name: self.sprite.clone(),
|
||||||
angle: Deg { 0: 0.0 },
|
angle: Deg { 0: 0.0 },
|
||||||
|
scale: 1.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -1,7 +1,10 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cgmath::{Deg, Point2};
|
use cgmath::{Deg, Point2};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
event::{
|
||||||
|
ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, TouchPhase,
|
||||||
|
VirtualKeyCode, WindowEvent,
|
||||||
|
},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::WindowBuilder,
|
window::WindowBuilder,
|
||||||
};
|
};
|
||||||
|
@ -22,12 +25,18 @@ use crate::{
|
||||||
system::System,
|
system::System,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
struct Camera {
|
struct Camera {
|
||||||
|
// Camera center
|
||||||
pos: Point2<Pfloat>,
|
pos: Point2<Pfloat>,
|
||||||
|
|
||||||
|
// Camera zoom
|
||||||
|
// (How many game units tall is the viewport?)
|
||||||
|
zoom: Pfloat,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Spriteable {
|
trait Spriteable {
|
||||||
fn sprite(&self, camera: &Camera) -> Sprite;
|
fn sprite(&self, camera: Camera) -> Sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Sprite {
|
struct Sprite {
|
||||||
|
@ -37,12 +46,14 @@ struct Sprite {
|
||||||
// This object's position, in world coordinates.
|
// This object's position, in world coordinates.
|
||||||
position: Point2<Pfloat>,
|
position: Point2<Pfloat>,
|
||||||
|
|
||||||
|
scale: Pfloat,
|
||||||
|
|
||||||
// This sprite's rotation
|
// This sprite's rotation
|
||||||
// (relative to north, measured ccw)
|
// (relative to north, measured ccw)
|
||||||
angle: Deg<Pfloat>,
|
angle: Deg<Pfloat>,
|
||||||
|
|
||||||
// The camera we want to draw this sprite from, in world coordinates
|
// The camera we want to draw this sprite from
|
||||||
camera: Point2<Pfloat>,
|
camera: Camera,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Game {
|
struct Game {
|
||||||
|
@ -61,6 +72,7 @@ impl Game {
|
||||||
player: Ship::new(ShipKind::Gypsum, (0.0, 0.0).into()),
|
player: Ship::new(ShipKind::Gypsum, (0.0, 0.0).into()),
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
pos: (0.0, 0.0).into(),
|
pos: (0.0, 0.0).into(),
|
||||||
|
zoom: 500.0,
|
||||||
},
|
},
|
||||||
system: System::new(),
|
system: System::new(),
|
||||||
}
|
}
|
||||||
|
@ -95,8 +107,8 @@ impl Game {
|
||||||
fn sprites(&self) -> Vec<Sprite> {
|
fn sprites(&self) -> Vec<Sprite> {
|
||||||
let mut sprites: Vec<Sprite> = Vec::new();
|
let mut sprites: Vec<Sprite> = Vec::new();
|
||||||
|
|
||||||
sprites.append(&mut self.system.sprites(&self.camera));
|
sprites.append(&mut self.system.sprites(self.camera));
|
||||||
sprites.push(self.player.sprite(&self.camera));
|
sprites.push(self.player.sprite(self.camera));
|
||||||
|
|
||||||
return sprites;
|
return sprites;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,6 @@ pub struct GPUState {
|
||||||
instance_buffer: wgpu::Buffer,
|
instance_buffer: wgpu::Buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = 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 {
|
struct Instance {
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
texture_index: u32,
|
texture_index: u32,
|
||||||
|
@ -40,7 +32,7 @@ struct Instance {
|
||||||
impl Instance {
|
impl Instance {
|
||||||
fn to_raw(&self) -> InstanceRaw {
|
fn to_raw(&self) -> InstanceRaw {
|
||||||
InstanceRaw {
|
InstanceRaw {
|
||||||
model: (self.transform.build_view_projection_matrix()).into(),
|
model: (self.transform.to_matrix()).into(),
|
||||||
texture_index: self.texture_index,
|
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<f32> = 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 {
|
struct Transform {
|
||||||
pos: Point2<f32>,
|
pos: Point2<f32>, // position on screen
|
||||||
aspect: f32, // width / height
|
screen_aspect: f32, // width / height. Screen aspect ratio.
|
||||||
scale: f32,
|
aspect: f32, // width / height. Sprite aspect ratio.
|
||||||
|
scale: f32, // if scale = 1, this sprite will be as tall as the screen.
|
||||||
rotate: Deg<f32>, // Around this object's center, in degrees measured ccw from vertical
|
rotate: Deg<f32>, // Around this object's center, in degrees measured ccw from vertical
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transform {
|
impl Transform {
|
||||||
fn build_view_projection_matrix(&self) -> Matrix4<f32> {
|
/// Build a matrix that corresponds to this transformation.
|
||||||
// Apply aspect ratio and scale
|
fn to_matrix(&self) -> Matrix4<f32> {
|
||||||
let mut scale = Matrix4::from_nonuniform_scale(1.0, 1.0 / self.aspect, 1.0);
|
// Note that our mesh starts centered at (0, 0).
|
||||||
scale = scale * Matrix4::from_scale(self.scale);
|
// 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.
|
// Apply sprite aspect ratio, preserving height.
|
||||||
// Note that we translate AFTER scaling.
|
// 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);
|
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 {
|
let translate = Matrix4::from_translation(Vector3 {
|
||||||
x: self.pos.x,
|
x: self.pos.x,
|
||||||
y: self.pos.y,
|
y: self.pos.y,
|
||||||
|
@ -124,8 +142,8 @@ impl Transform {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Order matters!
|
// Order matters!
|
||||||
// These are applied right-to-left
|
// The rightmost matrix is applied first.
|
||||||
return OPENGL_TO_WGPU_MATRIX * translate * rotate * scale;
|
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,
|
// These vertices form a rectangle that covers the whole screen.
|
||||||
// so scaling works properly.
|
// 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] = &[
|
const VERTICES: &[Vertex] = &[
|
||||||
Vertex {
|
Vertex {
|
||||||
position: [-0.5, 0.5, 0.0],
|
position: [-1.0, 1.0, 0.0],
|
||||||
tex_coords: [0.0, 0.0],
|
tex_coords: [0.0, 0.0],
|
||||||
},
|
},
|
||||||
Vertex {
|
Vertex {
|
||||||
position: [0.5, 0.5, 0.0],
|
position: [1.0, 1.0, 0.0],
|
||||||
tex_coords: [1.0, 0.0],
|
tex_coords: [1.0, 0.0],
|
||||||
},
|
},
|
||||||
Vertex {
|
Vertex {
|
||||||
position: [0.5, -0.5, 0.0],
|
position: [1.0, -1.0, 0.0],
|
||||||
tex_coords: [1.0, 1.0],
|
tex_coords: [1.0, 1.0],
|
||||||
},
|
},
|
||||||
Vertex {
|
Vertex {
|
||||||
position: [-0.5, -0.5, 0.0],
|
position: [-1.0, -1.0, 0.0],
|
||||||
tex_coords: [0.0, 1.0],
|
tex_coords: [0.0, 1.0],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -402,16 +426,17 @@ impl GPUState {
|
||||||
// TODO: warning when too many sprites are drawn.
|
// TODO: warning when too many sprites are drawn.
|
||||||
let mut instances: Vec<Instance> = Vec::new();
|
let mut instances: Vec<Instance> = Vec::new();
|
||||||
for s in sprites {
|
for s in sprites {
|
||||||
// Compute position on screen
|
// Compute position on screen,
|
||||||
let screen_pos: Point2<f32> = (s.position - s.camera.to_vec()) / 400.0;
|
// using logical pixels
|
||||||
|
let screen_pos: Point2<f32> = (s.position - s.camera.pos.to_vec()) / s.camera.zoom;
|
||||||
let texture = self.texture_array.get_texture(&s.name[..]);
|
let texture = self.texture_array.get_texture(&s.name[..]);
|
||||||
|
|
||||||
instances.push(Instance {
|
instances.push(Instance {
|
||||||
transform: Transform {
|
transform: Transform {
|
||||||
pos: screen_pos,
|
pos: screen_pos,
|
||||||
aspect: texture.aspect / screen_aspect,
|
aspect: texture.aspect,
|
||||||
scale: 0.25,
|
screen_aspect,
|
||||||
|
scale: s.scale * (texture.height / s.camera.zoom),
|
||||||
rotate: s.angle,
|
rotate: s.angle,
|
||||||
},
|
},
|
||||||
texture_index: texture.index,
|
texture_index: texture.index,
|
||||||
|
|
|
@ -11,12 +11,12 @@ pub struct TextureArray {
|
||||||
texture_indices: HashMap<String, u32>,
|
texture_indices: HashMap<String, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TEX: &[&str] = &["error", "gypsum", "earth", "a0"];
|
const TEX: &[&str] = &["error", "red", "gypsum", "earth", "a0"];
|
||||||
|
|
||||||
pub struct Texture {
|
pub struct Texture {
|
||||||
pub index: u32,
|
pub index: u32, // Index in texture array
|
||||||
pub dimensions: (u32, u32),
|
pub aspect: f32, // width / height
|
||||||
pub aspect: f32,
|
pub height: f32, // Height in game units
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureArray {
|
impl TextureArray {
|
||||||
|
@ -30,7 +30,7 @@ impl TextureArray {
|
||||||
|
|
||||||
return Texture {
|
return Texture {
|
||||||
index,
|
index,
|
||||||
dimensions,
|
height: 100.0,
|
||||||
aspect: dimensions.0 as f32 / dimensions.1 as f32,
|
aspect: dimensions.0 as f32 / dimensions.1 as f32,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,12 +33,13 @@ impl Ship {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spriteable for Ship {
|
impl Spriteable for Ship {
|
||||||
fn sprite(&self, camera: &Camera) -> Sprite {
|
fn sprite(&self, camera: Camera) -> Sprite {
|
||||||
return Sprite {
|
return Sprite {
|
||||||
position: self.body.pos,
|
position: self.body.pos,
|
||||||
camera: camera.pos,
|
camera: camera,
|
||||||
name: self.kind.sprite().to_owned(),
|
name: self.kind.sprite().to_owned(),
|
||||||
angle: self.body.angle,
|
angle: self.body.angle,
|
||||||
|
scale: 1.0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl System {
|
||||||
s.bodies.push(Doodad {
|
s.bodies.push(Doodad {
|
||||||
pos: Polar {
|
pos: Polar {
|
||||||
center: (0.0, 0.0).into(),
|
center: (0.0, 0.0).into(),
|
||||||
radius: 300.0,
|
radius: 100.0,
|
||||||
angle: Deg { 0: 31.0 },
|
angle: Deg { 0: 31.0 },
|
||||||
}
|
}
|
||||||
.to_cartesian(),
|
.to_cartesian(),
|
||||||
|
@ -32,7 +32,7 @@ impl System {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sprites(&self, camera: &Camera) -> Vec<Sprite> {
|
pub fn sprites(&self, camera: Camera) -> Vec<Sprite> {
|
||||||
return self.bodies.iter().map(|x| x.sprite(camera)).collect();
|
return self.bodies.iter().map(|x| x.sprite(camera)).collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue