Moved UI positioning to shaders

master
Mark 2024-01-08 17:57:49 -08:00
parent 9cd4ead368
commit 45bc3d3b41
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
8 changed files with 270 additions and 301 deletions

View File

@ -0,0 +1,41 @@
// Given an anchored position and sprite dimensions,
// return the translation that should be applied on
// vertex coordinates
fn anchor(
anchor: u32, // Anchor index
position: vec2<f32>, // Anchored position
dim: vec2<f32>, // Sprite dimensions (width, height)
) -> vec2<f32> {
var trans: vec2<f32> = vec2(0.0, 0.0);
let window_dim = global_data.window_size / global_data.window_scale.x;
if anchor == 0u { // NW C (screen anchor, sprite anchor)
trans += vec2(-window_dim.x, window_dim.y) / 2.0; // origin
trans += vec2(0.0, 0.0) / 2.0; // offset
} else if anchor == 1u { // NW NW
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
trans += vec2(dim.x, -dim.y) / 2.0;
} else if anchor == 2u { // NW NE
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
trans += vec2(-dim.x, -dim.y) / 2.0;
} else if anchor == 3u { // NW SW
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
trans += vec2(dim.x, dim.y) / 2.0;
} else if anchor == 4u { // NW SE
trans += vec2(-window_dim.x, window_dim.y) / 2.0;
trans += vec2(-dim.x, dim.y) / 2.0;
} else if anchor == 5u { // NE NE
trans += vec2(window_dim.x, window_dim.y) / 2.0;
trans += vec2(dim.x, -dim.y) / 2.0;
} else { // center / center as default, since it's the most visible variant.
trans += vec2(0.0, 0.0);
trans += vec2(0.0, 0.0) / 2.0;
}
trans += position;
// This renders correctly, but the offsets here are off by a factor of two.
// I'm not sure why... might be because WGPU screen coordinates are -1 to 1.
return (trans / window_dim) * 2.0;
}

View File

@ -1,10 +1,10 @@
// INCLUDE: global uniform header
struct InstanceInput {
@location(2) transform_matrix_0: vec4<f32>,
@location(3) transform_matrix_1: vec4<f32>,
@location(4) transform_matrix_2: vec4<f32>,
@location(5) transform_matrix_3: vec4<f32>,
@location(2) anchor: u32,
@location(3) position: vec2<f32>,
@location(4) angle: f32,
@location(5) size: f32,
@location(6) color_transform: vec4<f32>,
@location(7) sprite_index: u32,
};
@ -28,7 +28,7 @@ var sampler_array: binding_array<sampler>;
// INCLUDE: animate.wgsl
// INCLUDE: anchor.wgsl
@vertex
fn vertex_main(
@ -36,17 +36,43 @@ fn vertex_main(
instance: InstanceInput,
) -> VertexOutput {
let transform = mat4x4<f32>(
instance.transform_matrix_0,
instance.transform_matrix_1,
instance.transform_matrix_2,
instance.transform_matrix_3,
let window_dim = global_data.window_size / global_data.window_scale.x;
let scale = instance.size / window_dim.y;
let sprite_aspect = global_sprites[instance.sprite_index].aspect;
// Apply scale and sprite aspect
// Note that our mesh starts centered at (0, 0). This is important!
var pos: vec2<f32> = vec2(
vertex.position.x * scale * sprite_aspect,
vertex.position.y * scale
);
// Apply rotation
pos = mat2x2(
vec2(cos(instance.angle), sin(instance.angle)),
vec2(-sin(instance.angle), cos(instance.angle))
) * pos;
// Correct for screen aspect, preserving height
pos = vec2(
pos.x / global_data.window_aspect.x,
pos.y
);
pos = pos + anchor(
instance.anchor,
instance.position,
vec2(instance.size * sprite_aspect, instance.size)
);
var out: VertexOutput;
out.position = transform * vec4<f32>(vertex.position, 1.0);
out.position = vec4<f32>(pos, 1.0, 1.0);
out.color_transform = instance.color_transform;
// TODO: animate
// Pick texture frame
let t = global_atlas[u32(animate(instance.sprite_index, global_data.current_time.x, 0.0))];
out.texture_index = u32(t.atlas_texture);

View File

@ -0,0 +1,47 @@
/// The location of a UI element, in one of a few
/// possible coordinate systems.
///
/// Positive Y always points up,
/// positive X always points right.
#[derive(Debug, Clone)]
pub enum PositionAnchor {
/// Position of this sprite's center,
/// relative to the nw corner of the window.
NwC,
/// Position of this sprite's nw corner,
/// relative to the nw corner of the window.
NwNw,
/// Position of this sprite's ne corner,
/// relative to the nw corner of the window.
NwNe,
/// Position of this sprite's sw corner,
/// relative to the nw corner of the window.
NwSw,
/// Position of this sprite's se corner,
/// relative to the nw corner of the window.
NwSe,
/// Position of this sprite's ne corner,
/// relative to the ne corner of the window.
NeNe,
}
// These offsets are implemented in wgsl shaders.
impl PositionAnchor {
/// Get the uint that represents this anchor mode in shaders
pub fn to_int(&self) -> u32 {
match self {
Self::NwC => 0,
Self::NwNw => 1,
Self::NwNe => 2,
Self::NwSw => 3,
Self::NwSe => 4,
Self::NeNe => 5,
}
}
}

View File

@ -1,11 +1,9 @@
//! GPUState routines for drawing HUD elements
use cgmath::{Deg, InnerSpace, Point2, Vector2};
use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2};
use galactica_world::util;
use crate::{
sprite::UiSprite, vertexbuffer::types::UiInstance, AnchoredUiPosition, GPUState, RenderState,
};
use crate::{vertexbuffer::types::UiInstance, GPUState, PositionAnchor, RenderState};
impl GPUState {
pub(super) fn hud_add_radar(&mut self, state: &RenderState, instances: &mut Vec<UiInstance>) {
@ -14,7 +12,7 @@ impl GPUState {
let hide_range = 0.85;
let shrink_distance = 20.0;
//let system_object_scale = 1.0 / 600.0;
let ship_scale = 1.0 / 15.0;
let ship_scale = 1.0 / 10.0;
let (_, player_body) = state.world.get_ship_body(*state.player).unwrap();
let player_position = util::rigidbody_position(player_body);
@ -22,33 +20,23 @@ impl GPUState {
let ship_sprite = state.content.get_sprite_handle("ui::shipblip");
let arrow_sprite = state.content.get_sprite_handle("ui::centerarrow");
self.push_ui_sprite(
instances,
&UiSprite {
sprite: state.content.get_sprite_handle("ui::status"),
pos: AnchoredUiPosition::NeNe(Point2 { x: -10.0, y: -10.0 }),
dimensions: Point2 {
x: radar_size,
y: radar_size,
},
angle: Deg(0.0),
color: None,
},
);
instances.push(UiInstance {
anchor: PositionAnchor::NeNe.to_int(),
position: [0.0, 0.0],
angle: 0.0,
size: radar_size,
color: [1.0, 1.0, 1.0, 1.0],
sprite_index: state.content.get_sprite_handle("ui::status").get_index(),
});
self.push_ui_sprite(
instances,
&UiSprite {
sprite: state.content.get_sprite_handle("ui::radar"),
pos: AnchoredUiPosition::NwNw(Point2 { x: 10.0, y: -10.0 }),
dimensions: Point2 {
x: radar_size,
y: radar_size,
},
angle: Deg(0.0),
color: None,
},
);
instances.push(UiInstance {
anchor: PositionAnchor::NwNw.to_int(),
position: [10.0, -10.0],
angle: 0.0,
size: radar_size,
color: [1.0, 1.0, 1.0, 1.0],
sprite_index: state.content.get_sprite_handle("ui::radar").get_index(),
});
/*
// Draw system objects
@ -104,24 +92,20 @@ impl GPUState {
.into();
let f = state.content.get_faction(s.ship.faction).color;
let f = [f[0], f[1], f[2], 1.0];
self.push_ui_sprite(
instances,
&UiSprite {
sprite: ship_sprite,
pos: AnchoredUiPosition::NwC(
Point2 {
let position = Point2 {
x: radar_size / 2.0 + 10.0,
y: radar_size / -2.0 - 10.0,
} + (d * (radar_size / 2.0)),
),
dimensions: Point2 {
x: ship_sprite.aspect,
y: 1.0,
} * size,
angle: -angle,
color: Some(f),
},
);
} + (d * (radar_size / 2.0));
instances.push(UiInstance {
anchor: PositionAnchor::NwC.to_int(),
position: position.into(),
angle: -Rad::from(angle).0, // TODO: consistent angles
size,
color: f.into(),
sprite_index: ship_sprite.get_index(),
});
}
}
@ -132,93 +116,83 @@ impl GPUState {
} / radar_range;
let m = d.magnitude();
let d = d * (radar_size / 2.0);
let color = Some([0.3, 0.3, 0.3, 1.0]);
let color = [0.3, 0.3, 0.3, 1.0];
if m < 0.8 {
let sprite = state.content.get_sprite_handle("ui::radarframe");
let dimensions = Point2 {
x: sprite.aspect,
y: 1.0,
} * 7.0f32.min((0.8 - m) * 70.0);
self.push_ui_sprite(
instances,
&UiSprite {
sprite,
pos: AnchoredUiPosition::NwNw(Point2 {
let size = 7.0f32.min((0.8 - m) * 70.0);
instances.push(UiInstance {
anchor: PositionAnchor::NwNw.to_int(),
position: Point2 {
x: (radar_size / 2.0 + 10.0) - d.x,
y: (radar_size / -2.0 - 10.0) + d.y,
}),
dimensions,
angle: Deg(0.0),
}
.into(),
angle: 0.0,
size,
color,
},
);
sprite_index: sprite.get_index(),
});
self.push_ui_sprite(
instances,
&UiSprite {
sprite,
pos: AnchoredUiPosition::NwSw(Point2 {
instances.push(UiInstance {
anchor: PositionAnchor::NwSw.to_int(),
position: Point2 {
x: (radar_size / 2.0 + 10.0) - d.x,
y: (radar_size / -2.0 - 10.0) - d.y,
}),
dimensions,
angle: Deg(90.0),
}
.into(),
angle: Rad::from(Deg(90.0)).0,
size,
color,
},
);
sprite_index: sprite.get_index(),
});
self.push_ui_sprite(
instances,
&UiSprite {
sprite,
pos: AnchoredUiPosition::NwSe(Point2 {
instances.push(UiInstance {
anchor: PositionAnchor::NwSe.to_int(),
position: Point2 {
x: (radar_size / 2.0 + 10.0) + d.x,
y: (radar_size / -2.0 - 10.0) - d.y,
}),
dimensions,
angle: Deg(180.0),
}
.into(),
angle: Rad::from(Deg(180.0)).0,
size,
color,
},
);
sprite_index: sprite.get_index(),
});
self.push_ui_sprite(
instances,
&UiSprite {
sprite,
pos: AnchoredUiPosition::NwNe(Point2 {
instances.push(UiInstance {
anchor: PositionAnchor::NwNe.to_int(),
position: Point2 {
x: (radar_size / 2.0 + 10.0) + d.x,
y: (radar_size / -2.0 - 10.0) + d.y,
}),
dimensions,
angle: Deg(270.0),
}
.into(),
angle: Rad::from(Deg(270.0)).0,
size,
color,
},
);
sprite_index: sprite.get_index(),
});
}
// Arrow to center of system
let q = Point2 { x: 0.0, y: 0.0 } - player_position;
let m = q.magnitude();
if m > 200.0 {
let player_angle: Deg<f32> = q.angle(Vector2 { x: 0.0, y: 1.0 }).into();
self.push_ui_sprite(
instances,
&UiSprite {
sprite: arrow_sprite,
pos: AnchoredUiPosition::NwC(
Point2 {
let player_angle = q.angle(Vector2 { x: 0.0, y: 1.0 });
let position: Point2<f32> = Point2 {
x: radar_size / 2.0 + 10.0,
y: radar_size / -2.0 - 10.0,
} + ((q.normalize() * 0.865) * (radar_size / 2.0)),
),
dimensions: Point2 {
x: arrow_sprite.aspect,
y: 1.0,
} * 10.0,
angle: -player_angle,
color: Some([1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)]),
},
);
} + ((q.normalize() * 0.865) * (radar_size / 2.0));
instances.push(UiInstance {
anchor: PositionAnchor::NwC.to_int(),
position: position.into(),
angle: -player_angle.0,
size: 10.0,
color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)],
sprite_index: arrow_sprite.get_index(),
});
}
}
}

View File

@ -1,18 +1,17 @@
use anyhow::Result;
use bytemuck;
use cgmath::{Matrix4, Point2, Vector3};
use cgmath::Point2;
use galactica_constants;
use rand::seq::SliceRandom;
use std::{iter, rc::Rc};
use wgpu;
use winit::{self, dpi::LogicalSize, window::Window};
use winit::{self, window::Window};
use crate::{
content,
globaluniform::{GlobalDataContent, GlobalUniform},
pipeline::PipelineBuilder,
sprite::UiSprite,
starfield::Starfield,
texturearray::TextureArray,
vertexbuffer::{
@ -23,7 +22,7 @@ use crate::{
},
BufferObject, VertexBuffer,
},
RenderState, OPENGL_TO_WGPU_MATRIX,
RenderState,
};
// Additional implementaitons for GPUState
@ -94,6 +93,15 @@ fn preprocess_shader(
)),
);
let shader = shader.replace(
"// INCLUDE: anchor.wgsl",
&include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shaders/include/",
"anchor.wgsl"
)),
);
return shader;
}
@ -337,65 +345,6 @@ impl GPUState {
self.update_starfield_buffer()
}
// TODO:remove
/// Create a UiInstance for a ui sprite and add it to `instances`
fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) {
let logical_size: LogicalSize<f32> =
self.window_size.to_logical(self.window.scale_factor());
let width = s.dimensions.x;
let height = s.dimensions.y;
// Compute square scale, since we must apply screen aspect ratio
// AFTER rotation.
let scale = Matrix4::from_nonuniform_scale(
width / logical_size.height,
height / logical_size.height,
1.0,
);
let rotate = Matrix4::from_angle_z(s.angle);
let translate = Matrix4::from_translation(match s.pos {
super::AnchoredUiPosition::NwC(p) => Vector3 {
// Note the signs. Positive y points north!
x: -1.0 + p.x / (logical_size.width / 2.0),
y: 1.0 + p.y / (logical_size.height / 2.0),
z: 0.0,
},
super::AnchoredUiPosition::NwNw(p) => Vector3 {
x: -1.0 + (width / 2.0 + p.x) / (logical_size.width / 2.0),
y: 1.0 - (height / 2.0 - p.y) / (logical_size.height / 2.0),
z: 0.0,
},
super::AnchoredUiPosition::NwNe(p) => Vector3 {
x: -1.0 - (width / 2.0 - p.x) / (logical_size.width / 2.0),
y: 1.0 - (height / 2.0 - p.y) / (logical_size.height / 2.0),
z: 0.0,
},
super::AnchoredUiPosition::NwSw(p) => Vector3 {
x: -1.0 + (width / 2.0 + p.x) / (logical_size.width / 2.0),
y: 1.0 + (height / 2.0 + p.y) / (logical_size.height / 2.0),
z: 0.0,
},
super::AnchoredUiPosition::NwSe(p) => Vector3 {
x: -1.0 - (width / 2.0 - p.x) / (logical_size.width / 2.0),
y: 1.0 + (height / 2.0 + p.y) / (logical_size.height / 2.0),
z: 0.0,
},
super::AnchoredUiPosition::NeNe(p) => Vector3 {
x: 1.0 - (width / 2.0 - p.x) / (logical_size.width / 2.0),
y: 1.0 - (height / 2.0 - p.y) / (logical_size.height / 2.0),
z: 0.0,
},
});
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
instances.push(UiInstance {
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(),
sprite_index: s.sprite.get_index(),
color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]),
});
}
/// Make an instance for all the game's sprites
/// (Objects and UI)
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
@ -637,7 +586,7 @@ impl GPUState {
]),
);*/
// Ui pipeline
// Radial progress bar
// TODO: do we need to do this every time?
self.vertex_buffers.radialbar.set_in_pass(&mut render_pass);
render_pass.set_pipeline(&self.radialbar_pipeline);

View File

@ -7,19 +7,19 @@
//! and the only one external code should interact with.
//! (Excluding data structs, like [`ObjectSprite`])
mod anchoredposition;
mod globaluniform;
mod gpustate;
mod pipeline;
mod renderstate;
mod sprite;
mod starfield;
mod texturearray;
mod vertexbuffer;
pub use anchoredposition::PositionAnchor;
use galactica_content as content;
pub use gpustate::GPUState;
pub use renderstate::RenderState;
pub use sprite::AnchoredUiPosition;
use cgmath::Matrix4;

View File

@ -1,79 +0,0 @@
use crate::content;
use cgmath::{Deg, Point2};
/// The location of a UI element, in one of a few
/// possible coordinate systems.
///
/// Positive Y always points up,
/// positive X always points right.
#[derive(Debug, Clone)]
pub enum AnchoredUiPosition {
/// Position of this sprite's center,
/// relative to the nw corner of the window.
NwC(Point2<f32>),
/// Position of this sprite's nw corner,
/// relative to the nw corner of the window.
NwNw(Point2<f32>),
/// Position of this sprite's ne corner,
/// relative to the nw corner of the window.
NwNe(Point2<f32>),
/// Position of this sprite's sw corner,
/// relative to the nw corner of the window.
NwSw(Point2<f32>),
/// Position of this sprite's se corner,
/// relative to the nw corner of the window.
NwSe(Point2<f32>),
/// Position of this sprite's ne corner,
/// relative to the ne corner of the window.
NeNe(Point2<f32>),
}
impl AnchoredUiPosition {
pub fn to_anchor_int(&self) -> u32 {
match self {
Self::NwC(_) => 0,
Self::NwNw(_) => 1,
Self::NwNe(_) => 2,
Self::NwSw(_) => 3,
Self::NwSe(_) => 4,
Self::NeNe(_) => 5,
}
}
pub fn position(&self) -> &Point2<f32> {
match self {
Self::NwC(x)
| Self::NwNw(x)
| Self::NwNe(x)
| Self::NwSw(x)
| Self::NwSe(x)
| Self::NeNe(x) => x,
}
}
}
// TODO: remove
/// A sprite that represents a ui element
#[derive(Debug, Clone)]
pub struct UiSprite {
/// The sprite to draw
pub sprite: content::SpriteHandle,
/// This object's position, in logical (dpi-adjusted) pixels
pub pos: AnchoredUiPosition,
/// This sprite's color will be multiplied by this value.
/// If this is None, color will not be changed.
pub color: Option<[f32; 4]>,
/// The size of this sprite, in logical (dpi-adjusted) pixels
pub dimensions: Point2<f32>,
/// This sprite's rotation, measured ccw
pub angle: Deg<f32>,
}

View File

@ -120,10 +120,18 @@ impl BufferObject for ObjectInstance {
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct UiInstance {
/// Extra transformations this sprite
/// (rotation, etc)
/// TODO: remove
pub transform: [[f32; 4]; 4],
/// How to interpret this object's coordinates.
/// this should be generated from an AnchoredUiPosition
pub anchor: u32,
/// Position of this object in logical pixels
pub position: [f32; 2],
/// The angle of this sprite, in radians
pub angle: f32,
/// The height of this sprite, in logical pixels
pub size: f32,
/// This lets us color ui sprites dynamically.
/// Each fragment's color is multiplied by this value.
@ -143,36 +151,39 @@ impl BufferObject for UiInstance {
// instance when the shader starts processing a new instance
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
// 4 arrays = 1 4x4 matrix
// Anchor
wgpu::VertexAttribute {
offset: 0,
shader_location: 2,
format: wgpu::VertexFormat::Float32x4,
format: wgpu::VertexFormat::Uint32,
},
// Position
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x2,
},
// Angle
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32,
},
// Size
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
format: wgpu::VertexFormat::Float32,
},
// Color
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 6,
format: wgpu::VertexFormat::Float32x4,
},
// Sprite
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 20]>() as wgpu::BufferAddress,
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
shader_location: 7,
format: wgpu::VertexFormat::Uint32,
},
@ -286,7 +297,7 @@ pub struct RadialBarInstance {
/// this should be generated from an AnchoredUiPosition
pub anchor: u32,
/// Position of this particle in logical pixels
/// Position of this object in logical pixels
pub position: [f32; 2],
/// The diameter of this bar, in logical pixels