diff --git a/Cargo.lock b/Cargo.lock index f6cdec6..b3414c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,7 +584,6 @@ dependencies = [ "galactica-content", "galactica-gameobject", "galactica-render", - "galactica-ui", "galactica-world", "pollster", "wgpu", @@ -624,7 +623,6 @@ version = "0.0.0" dependencies = [ "cgmath", "galactica-content", - "galactica-render", "rand", ] @@ -650,23 +648,13 @@ dependencies = [ "galactica-constants", "galactica-content", "galactica-packer", + "galactica-world", "image", "rand", "wgpu", "winit", ] -[[package]] -name = "galactica-ui" -version = "0.0.0" -dependencies = [ - "cgmath", - "galactica-content", - "galactica-gameobject", - "galactica-render", - "galactica-world", -] - [[package]] name = "galactica-world" version = "0.0.0" @@ -675,7 +663,6 @@ dependencies = [ "crossbeam", "galactica-content", "galactica-gameobject", - "galactica-render", "nalgebra", "rand", "rapier2d", diff --git a/Cargo.toml b/Cargo.toml index b9df1cf..ae9dfb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,12 +48,12 @@ galactica-render = { path = "crates/render" } galactica-world = { path = "crates/world" } galactica-behavior = { path = "crates/behavior" } galactica-gameobject = { path = "crates/gameobject" } -galactica-ui = { path = "crates/ui" } galactica-packer = { path = "crates/packer" } galactica = { path = "crates/galactica" } image = { version = "0.24", features = ["png"] } serde = { version = "1.0.193", features = ["derive"] } +# TODO: update winit, but wgpu seems to be tied to this version winit = "0.28" wgpu = "0.18" bytemuck = { version = "1.12", features = ["derive"] } diff --git a/crates/galactica/Cargo.toml b/crates/galactica/Cargo.toml index 0645c70..85ffa3d 100644 --- a/crates/galactica/Cargo.toml +++ b/crates/galactica/Cargo.toml @@ -27,7 +27,6 @@ galactica-constants = { workspace = true } galactica-world = { workspace = true } galactica-behavior = { workspace = true } galactica-gameobject = { workspace = true } -galactica-ui = { workspace = true } winit = { workspace = true } wgpu = { workspace = true } diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index 391de5e..48e4c22 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -1,5 +1,4 @@ use cgmath::Point2; -use content::SystemHandle; use std::time::Instant; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; @@ -10,9 +9,8 @@ use galactica_behavior::{behavior, ShipBehavior}; use galactica_constants; use galactica_content as content; use galactica_gameobject as object; -use galactica_render::{ObjectSprite, ParticleBuilder, RenderState, UiSprite}; -use galactica_ui as ui; -use galactica_world::{util, ShipPhysicsHandle, World}; +use galactica_render::RenderState; +use galactica_world::{util, ParticleBuilder, ShipPhysicsHandle, World}; pub struct Game { input: InputStatus, @@ -23,7 +21,8 @@ pub struct Game { start_instant: Instant, camera: Camera, - system: object::System, + // TODO: include system in world + //system: object::System, shipbehaviors: Vec>, playerbehavior: behavior::Player, @@ -98,8 +97,7 @@ impl Game { zoom: 500.0, aspect: 1.0, }, - system: object::System::new(&ct, SystemHandle { index: 0 }), - + //system: object::System::new(&ct, SystemHandle { index: 0 }), paused: false, time_scale: 1.0, world: physics, @@ -179,41 +177,11 @@ impl Game { RenderState { camera_pos: self.camera.pos, camera_zoom: self.camera.zoom, - object_sprites: self.get_object_sprites(), - ui_sprites: self.get_ui_sprites(), - new_particles: &mut self.new_particles, current_time: self.start_instant.elapsed().as_secs_f32(), content: &self.content, + world: &self.world, + particles: &mut self.new_particles, + player: &self.player, } } - - pub fn get_object_sprites(&self) -> Vec { - let mut sprites: Vec = Vec::new(); - - sprites.append(&mut self.system.get_sprites()); - sprites.extend(self.world.get_ship_sprites(&self.content)); - - // Make sure sprites are drawn in the correct order - // (note the reversed a, b in the comparator) - // - // TODO: maybe use a gpu depth buffer instead? - // I've tried this, but it doesn't seem to work with transparent textures. - sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); - - // Don't waste time sorting these, they should always be on top. - sprites.extend(self.world.get_projectile_sprites()); - - return sprites; - } - - pub fn get_ui_sprites(&self) -> Vec { - return ui::build_radar( - &self.content, - self.player, - &self.world, - &self.system, - self.camera.zoom, - self.camera.aspect, - ); - } } diff --git a/crates/gameobject/Cargo.toml b/crates/gameobject/Cargo.toml index f91f357..6b01502 100644 --- a/crates/gameobject/Cargo.toml +++ b/crates/gameobject/Cargo.toml @@ -17,7 +17,6 @@ readme = { workspace = true } workspace = true [dependencies] -galactica-render = { workspace = true } galactica-content = { workspace = true } cgmath = { workspace = true } diff --git a/crates/gameobject/src/outfits.rs b/crates/gameobject/src/outfits.rs index 80edd1a..10b6dd4 100644 --- a/crates/gameobject/src/outfits.rs +++ b/crates/gameobject/src/outfits.rs @@ -1,6 +1,5 @@ -use cgmath::{Deg, Point3}; +use content::{EnginePoint, SpriteHandle}; use galactica_content as content; -use galactica_render::ObjectSubSprite; /// Represents a gun attached to a specific ship at a certain gunpoint. #[derive(Debug)] @@ -80,10 +79,6 @@ pub struct OutfitSet { guns: Vec, enginepoints: Vec, gunpoints: Vec, - - // Minor performance optimization, since we - // rarely need to re-compute these. - engine_flare_sprites: Vec, } impl<'a> OutfitSet { @@ -97,7 +92,6 @@ impl<'a> OutfitSet { //total_space: content.space.clone(), enginepoints: content.engines.clone(), gunpoints: content.guns.clone(), - engine_flare_sprites: vec![], } } @@ -124,7 +118,7 @@ impl<'a> OutfitSet { self.available_space.occupy(&outfit.space); self.stats.add(&outfit); self.outfits.push(o); - self.update_engine_flares(); + //self.update_engine_flares(); return true; } @@ -138,7 +132,7 @@ impl<'a> OutfitSet { self.available_space.free(&outfit.space); self.outfits.remove(i); self.stats.remove(&outfit); - self.update_engine_flares(); + //self.update_engine_flares(); } /// Add a gun to this outfit set. @@ -181,34 +175,13 @@ impl<'a> OutfitSet { .map(|(a, b)| (b, a)) } - /// Update engine flare sprites - pub fn update_engine_flares(&mut self) { - // TODO: better way to pick flare texture - self.engine_flare_sprites.clear(); - let s = if let Some(e) = self.stats.engine_flare_sprites.iter().next() { - e - } else { - return; - }; - - self.engine_flare_sprites = self - .enginepoints - .iter() - .map(|p| ObjectSubSprite { - pos: Point3 { - x: p.pos.x, - y: p.pos.y - p.size / 2.0, - z: 1.0, - }, - sprite: *s, - angle: Deg(0.0), - size: p.size, - }) - .collect(); + // TODO: move to ship + /// Iterate over all ships in this physics system + pub fn iter_enginepoints(&self) -> impl Iterator + '_ { + self.enginepoints.iter() } - /// Get the sprites we should show if this ship is firing its engines - pub fn get_engine_flares(&self) -> Vec { - return self.engine_flare_sprites.clone(); + pub fn get_flare_sprite(&self) -> Option { + self.stats.engine_flare_sprites.iter().next().map(|x| *x) } } diff --git a/crates/gameobject/src/system.rs b/crates/gameobject/src/system.rs index 268aa0c..10ffebb 100644 --- a/crates/gameobject/src/system.rs +++ b/crates/gameobject/src/system.rs @@ -1,6 +1,5 @@ use crate::SystemObject; use galactica_content as content; -use galactica_render::ObjectSprite; pub struct System { pub name: String, @@ -27,7 +26,7 @@ impl System { return s; } - pub fn get_sprites(&self) -> Vec { - return self.bodies.iter().map(|x| x.get_sprite()).collect(); - } + //pub fn get_sprites(&self) -> Vec { + // return self.bodies.iter().map(|x| x.get_sprite()).collect(); + //} } diff --git a/crates/gameobject/src/systemobject.rs b/crates/gameobject/src/systemobject.rs index abe7591..d0e4c95 100644 --- a/crates/gameobject/src/systemobject.rs +++ b/crates/gameobject/src/systemobject.rs @@ -1,7 +1,6 @@ use cgmath::{Deg, Point3}; use galactica_content as content; -use galactica_render::ObjectSprite; pub struct SystemObject { pub sprite: content::SpriteHandle, @@ -11,13 +10,13 @@ pub struct SystemObject { } impl SystemObject { - pub(crate) fn get_sprite(&self) -> ObjectSprite { - return ObjectSprite { - sprite: self.sprite, - pos: self.pos, - angle: self.angle, - size: self.size, - children: None, - }; - } + //pub(crate) fn get_sprite(&self) -> ObjectSprite { + // return ObjectSprite { + // sprite: self.sprite, + // pos: self.pos, + // angle: self.angle, + // size: self.size, + // children: None, + // }; + //} } diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 15fcfa6..e24189d 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -20,6 +20,7 @@ workspace = true galactica-content = { workspace = true } galactica-constants = { workspace = true } galactica-packer = { workspace = true } +galactica-world = { workspace = true } anyhow = { workspace = true } cgmath = { workspace = true } diff --git a/crates/render/src/globaluniform/data.rs b/crates/render/src/globaluniform/data.rs index b8d0494..880b782 100644 --- a/crates/render/src/globaluniform/data.rs +++ b/crates/render/src/globaluniform/data.rs @@ -23,6 +23,10 @@ pub struct GlobalDataContent { /// Size ratio of window, in physical pixels pub window_size: [f32; 2], + /// Physical pixel to logical pixel conversion factor. + /// Second component is ignored. + pub window_scale: [f32; 2], + /// Aspect ratio of window /// Second component is ignored. pub window_aspect: [f32; 2], diff --git a/crates/render/src/globaluniform/globaluniform.rs b/crates/render/src/globaluniform/globaluniform.rs index 43638a3..aff5a78 100644 --- a/crates/render/src/globaluniform/globaluniform.rs +++ b/crates/render/src/globaluniform/globaluniform.rs @@ -27,6 +27,7 @@ impl GlobalUniform { camera_zoom: vec2, camera_zoom_limits: vec2, window_size: vec2, + window_scale: vec2, window_aspect: vec2, starfield_sprite: vec2, starfield_tile_size: vec2, diff --git a/crates/render/src/gpustate/hud.rs b/crates/render/src/gpustate/hud.rs new file mode 100644 index 0000000..205b322 --- /dev/null +++ b/crates/render/src/gpustate/hud.rs @@ -0,0 +1,224 @@ +//! GPUState routines for drawing HUD elements + +use cgmath::{Deg, InnerSpace, Point2, Vector2}; +use galactica_world::util; + +use crate::{ + sprite::UiSprite, vertexbuffer::types::UiInstance, AnchoredUiPosition, GPUState, RenderState, +}; + +impl GPUState { + pub(super) fn hud_add_radar(&mut self, state: &RenderState, instances: &mut Vec) { + let radar_range = 4000.0; + let radar_size = 300.0; + 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 (_, player_body) = state.world.get_ship_body(*state.player).unwrap(); + let player_position = util::rigidbody_position(player_body); + //let planet_sprite = state.content.get_sprite_handle("ui::planetblip"); + 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, + }, + ); + + 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, + }, + ); + + /* + // Draw system objects + for o in &system.bodies { + let size = (o.size / o.pos.z) / (radar_range * system_object_scale); + let p = Point2 { + x: o.pos.x, + y: o.pos.y, + }; + let d = (p - player_position) / radar_range; + // Add half the blip sprite's height to distance + let m = d.magnitude() + (size / (2.0 * radar_size)); + if m < hide_range { + // Shrink blips as they get closeto the edge + let size = size.min((hide_range - m) * size * shrink_distance); + if size <= 2.0 { + // Don't draw super tiny sprites, they flicker + continue; + } + out.push(UiSprite { + sprite: planet_sprite, + pos: AnchoredUiPosition::NwC( + Point2 { + x: radar_size / 2.0 + 10.0, + y: radar_size / -2.0 - 10.0, + } + (d * (radar_size / 2.0)), + ), + dimensions: Point2 { + x: planet_sprite.aspect, + y: 1.0, + } * size, + angle: o.angle, + color: Some([0.5, 0.5, 0.5, 1.0]), + }); + } + } + */ + + // Draw ships + for (s, r) in state.world.iter_ship_body() { + let ship = state.content.get_ship(s.ship.handle); + let size = (ship.size * ship.sprite.aspect) * ship_scale; + let p = util::rigidbody_position(r); + let d = (p - player_position) / radar_range; + let m = d.magnitude() + (size / (2.0 * radar_size)); + if m < hide_range { + let size = size.min((hide_range - m) * size * shrink_distance); + if size < 2.0 { + continue; + } + let angle: Deg = util::rigidbody_rotation(r) + .angle(Vector2 { x: 0.0, y: 1.0 }) + .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 { + 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), + }, + ); + } + } + + // Draw viewport frame + let d = Vector2 { + x: (state.camera_zoom / 2.0) * self.window_aspect, + y: state.camera_zoom / 2.0, + } / radar_range; + let m = d.magnitude(); + let d = d * (radar_size / 2.0); + let color = Some([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 { + x: (radar_size / 2.0 + 10.0) - d.x, + y: (radar_size / -2.0 - 10.0) + d.y, + }), + dimensions, + angle: Deg(0.0), + color, + }, + ); + + self.push_ui_sprite( + instances, + &UiSprite { + sprite, + pos: AnchoredUiPosition::NwSw(Point2 { + x: (radar_size / 2.0 + 10.0) - d.x, + y: (radar_size / -2.0 - 10.0) - d.y, + }), + dimensions, + angle: Deg(90.0), + color, + }, + ); + + self.push_ui_sprite( + instances, + &UiSprite { + sprite, + pos: AnchoredUiPosition::NwSe(Point2 { + x: (radar_size / 2.0 + 10.0) + d.x, + y: (radar_size / -2.0 - 10.0) - d.y, + }), + dimensions, + angle: Deg(180.0), + color, + }, + ); + + self.push_ui_sprite( + instances, + &UiSprite { + sprite, + pos: AnchoredUiPosition::NwNe(Point2 { + x: (radar_size / 2.0 + 10.0) + d.x, + y: (radar_size / -2.0 - 10.0) + d.y, + }), + dimensions, + angle: Deg(270.0), + color, + }, + ); + } + + // 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 = q.angle(Vector2 { x: 0.0, y: 1.0 }).into(); + self.push_ui_sprite( + instances, + &UiSprite { + sprite: arrow_sprite, + pos: AnchoredUiPosition::NwC( + 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)]), + }, + ); + } + } +} diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate/mod.rs similarity index 83% rename from crates/render/src/gpustate.rs rename to crates/render/src/gpustate/mod.rs index 8aa6639..ea6ee63 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate/mod.rs @@ -1,7 +1,8 @@ use anyhow::Result; use bytemuck; -use cgmath::{EuclideanSpace, Matrix4, Point2, Rad, Vector3}; +use cgmath::{Matrix4, Point2, Vector3}; use galactica_constants; + use rand::seq::SliceRandom; use std::{iter, rc::Rc}; use wgpu; @@ -9,18 +10,26 @@ use winit::{self, dpi::LogicalSize, window::Window}; use crate::{ content, - globaluniform::{GlobalDataContent, GlobalUniform, ObjectData}, + globaluniform::{GlobalDataContent, GlobalUniform}, pipeline::PipelineBuilder, + sprite::UiSprite, starfield::Starfield, texturearray::TextureArray, vertexbuffer::{ consts::{SPRITE_INDICES, SPRITE_VERTICES}, - types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance}, + types::{ + ObjectInstance, ParticleInstance, RadialBarInstance, StarfieldInstance, TexturedVertex, + UiInstance, + }, BufferObject, VertexBuffer, }, - ObjectSprite, RenderState, UiSprite, OPENGL_TO_WGPU_MATRIX, + RenderState, OPENGL_TO_WGPU_MATRIX, }; +// Additional implementaitons for GPUState +mod hud; +mod world; + /// A high-level GPU wrapper. Consumes game state, /// produces pretty pictures. pub struct GPUState { @@ -41,6 +50,7 @@ pub struct GPUState { starfield_pipeline: wgpu::RenderPipeline, particle_pipeline: wgpu::RenderPipeline, ui_pipeline: wgpu::RenderPipeline, + radialbar_pipeline: wgpu::RenderPipeline, starfield: Starfield, texture_array: TextureArray, @@ -58,6 +68,8 @@ struct VertexBuffers { /// of the particle instance array. particle_array_head: u64, particle: Rc, + + radialbar: Rc, } /// Basic wgsl preprocesser @@ -183,6 +195,14 @@ impl GPUState { Some(SPRITE_INDICES), galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT, )), + + radialbar: Rc::new(VertexBuffer::new::( + "radial bar", + &device, + Some(SPRITE_VERTICES), + Some(SPRITE_INDICES), + 10, + )), }; // Load uniforms @@ -256,6 +276,22 @@ impl GPUState { .set_bind_group_layouts(bind_group_layouts) .build(); + let radialbar_pipeline = PipelineBuilder::new("radialbar", &device) + .set_shader(&preprocess_shader( + &include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/shaders/", + "radialbar.wgsl" + )), + &global_uniform, + 1, + )) + .set_format(config.format) + .set_triangle(true) + .set_vertex_buffer(&vertex_buffers.radialbar) + .set_bind_group_layouts(bind_group_layouts) + .build(); + let mut starfield = Starfield::new(); starfield.regenerate(); @@ -273,6 +309,7 @@ impl GPUState { starfield_pipeline, ui_pipeline, particle_pipeline, + radialbar_pipeline, starfield, texture_array, @@ -300,90 +337,7 @@ impl GPUState { self.update_starfield_buffer() } - /// Create a ObjectInstance for an object and add it to `instances`. - fn push_object_sprite( - &self, - state: &RenderState, - instances: &mut Vec, - clip_ne: Point2, - clip_sw: Point2, - s: &ObjectSprite, - ) { - // Position adjusted for parallax - // TODO: adjust parallax for zoom? - let pos: Point2 = { - (Point2 { - x: s.pos.x, - y: s.pos.y, - } - state.camera_pos.to_vec()) - / s.pos.z - }; - - // Game dimensions of this sprite post-scale. - // Post-scale width or height, whichever is larger. - // This is in game units. - // - // We take the maximum to account for rotated sprites. - let m = (s.size / s.pos.z) * s.sprite.aspect.max(1.0); - - // Don't draw sprites that are off the screen - if pos.x < clip_ne.x - m - || pos.y > clip_ne.y + m - || pos.x > clip_sw.x + m - || pos.y < clip_sw.y - m - { - return; - } - - let idx = instances.len(); - // Write this object's location data - self.queue.write_buffer( - &self.global_uniform.object_buffer, - ObjectData::SIZE * idx as u64, - bytemuck::cast_slice(&[ObjectData { - xpos: s.pos.x, - ypos: s.pos.y, - zpos: s.pos.z, - angle: Rad::from(s.angle).0, - size: s.size, - parent: 0, - is_child: 0, - _padding: Default::default(), - }]), - ); - - // Push this object's instance - instances.push(ObjectInstance { - sprite_index: s.sprite.get_index(), - object_index: idx as u32, - }); - - // Add children - if let Some(children) = &s.children { - for c in children { - self.queue.write_buffer( - &self.global_uniform.object_buffer, - ObjectData::SIZE * instances.len() as u64, - bytemuck::cast_slice(&[ObjectData { - xpos: c.pos.x, - ypos: c.pos.y, - zpos: c.pos.z, - angle: Rad::from(c.angle).0, - size: c.size, - parent: idx as u32, - is_child: 1, - _padding: Default::default(), - }]), - ); - - instances.push(ObjectInstance { - sprite_index: c.sprite.get_index(), - object_index: instances.len() as u32, - }); - } - } - } - + // TODO:remove /// Create a UiInstance for a ui sprite and add it to `instances` fn push_ui_sprite(&self, instances: &mut Vec, s: &UiSprite) { let logical_size: LogicalSize = @@ -427,6 +381,11 @@ impl GPUState { 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); @@ -440,7 +399,7 @@ impl GPUState { /// Make an instance for all the game's sprites /// (Objects and UI) /// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded. - fn update_sprite_instances(&self, state: &RenderState) -> (usize, usize) { + fn update_sprite_instances(&mut self, state: &RenderState) -> (usize, usize) { let mut object_instances: Vec = Vec::new(); // Game coordinates (relative to camera) of ne and sw corners of screen. @@ -448,8 +407,13 @@ impl GPUState { let clip_ne = Point2::from((-self.window_aspect, 1.0)) * state.camera_zoom; let clip_sw = Point2::from((self.window_aspect, -1.0)) * state.camera_zoom; - for s in &state.object_sprites { - self.push_object_sprite(state, &mut object_instances, clip_ne, clip_sw, &s); + // TODO:sort. Order matters. + for s in state.world.iter_ships() { + self.world_push_ship(state, (clip_ne, clip_sw), &s, &mut object_instances); + } + + for p in state.world.iter_projectiles() { + self.world_push_projectile(state, (clip_ne, clip_sw), &p, &mut object_instances); } // Enforce sprite limit @@ -464,11 +428,10 @@ impl GPUState { bytemuck::cast_slice(&object_instances), ); + // TODO: we don't need an array, just use a counter let mut ui_instances: Vec = Vec::new(); - for s in &state.ui_sprites { - self.push_ui_sprite(&mut ui_instances, &s); - } + self.hud_add_radar(state, &mut ui_instances); if ui_instances.len() as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT { panic!("Ui sprite limit exceeded!") @@ -515,9 +478,7 @@ impl GPUState { /// Main render function. Draws sprites on a window. pub fn render(&mut self, state: RenderState) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); + let view = output.texture.create_view(&Default::default()); let mut encoder = self .device @@ -560,6 +521,7 @@ impl GPUState { self.window_size.width as f32, self.window_size.height as f32, ], + window_scale: [self.window.scale_factor() as f32, 0.0], window_aspect: [self.window_aspect, 0.0], starfield_sprite: [s.get_index(), 0], starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0], @@ -572,8 +534,8 @@ impl GPUState { ); // Write all new particles to GPU buffer - state.new_particles.shuffle(&mut rand::thread_rng()); - for i in state.new_particles.iter() { + state.particles.shuffle(&mut rand::thread_rng()); + for i in state.particles.iter() { self.queue.write_buffer( &self.vertex_buffers.particle.instances, ParticleInstance::SIZE * self.vertex_buffers.particle_array_head, @@ -596,7 +558,7 @@ impl GPUState { self.vertex_buffers.particle_array_head = 0; } } - state.new_particles.clear(); + state.particles.clear(); // Create sprite instances let (n_object, n_ui) = self.update_sprite_instances(&state); @@ -634,6 +596,53 @@ impl GPUState { render_pass.set_pipeline(&self.ui_pipeline); render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..n_ui as _); + /* + let mut i = 0; + for b in &state.render_elements.radial_bars { + self.queue.write_buffer( + &self.vertex_buffers.radialbar.instances, + RadialBarInstance::SIZE * i, + bytemuck::cast_slice(&[RadialBarInstance { + position: b.pos.position().clone().into(), + anchor: b.pos.to_anchor_int(), + diameter: b.diameter, + stroke: b.stroke, + color: b.color, + angle: b.angle.0, + }]), + ); + i += 1; + } + */ + + /* + self.queue.write_buffer( + &self.vertex_buffers.radialbar.instances, + 0, + bytemuck::cast_slice(&[ + RadialBarInstance { + position: [-23.0, -23.0], + diameter: 274.0, + stroke: 10.0, + color: [0.3, 0.6, 0.8, 1.0], + angle: -state.current_time / 2.0, + }, + RadialBarInstance { + position: [-35.0, -35.0], + diameter: 250.0, + stroke: 10.0, + color: [0.8, 0.7, 0.5, 1.0], + angle: -state.current_time / 5.0, + }, + ]), + );*/ + + // Ui pipeline + // 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); + //render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..2); + // begin_render_pass borrows encoder mutably, so we can't call finish() // without dropping this variable. drop(render_pass); diff --git a/crates/render/src/gpustate/world.rs b/crates/render/src/gpustate/world.rs new file mode 100644 index 0000000..7662bc8 --- /dev/null +++ b/crates/render/src/gpustate/world.rs @@ -0,0 +1,162 @@ +//! GPUState routines for drawing the world + +use bytemuck; +use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; +use galactica_world::{ + objects::{ProjectileWorldObject, ShipWorldObject}, + util, +}; + +use crate::{ + globaluniform::ObjectData, vertexbuffer::types::ObjectInstance, GPUState, RenderState, +}; + +impl GPUState { + pub(super) fn world_push_ship( + &self, + state: &RenderState, + // NE and SW corners of screen + screen_clip: (Point2, Point2), + s: &ShipWorldObject, + instances: &mut Vec, + ) { + let (_, r) = state.world.get_ship_body(s.physics_handle).unwrap(); + let ship_pos = util::rigidbody_position(&r); + let ship_rot = util::rigidbody_rotation(r); + let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! + let ship_cnt = state.content.get_ship(s.ship.handle); + + // Position adjusted for parallax + // TODO: adjust parallax for zoom? + // 1.0 is z-coordinate, which is constant for ships + let pos: Point2 = (ship_pos - state.camera_pos.to_vec()) / 1.0; + + // Game dimensions of this sprite post-scale. + // Post-scale width or height, whichever is larger. + // This is in game units. + // + // We take the maximum to account for rotated sprites. + let m = (ship_cnt.size / 1.0) * ship_cnt.sprite.aspect.max(1.0); + + // Don't draw sprites that are off the screen + if pos.x < screen_clip.0.x - m + || pos.y > screen_clip.0.y + m + || pos.x > screen_clip.1.x + m + || pos.y < screen_clip.1.y - m + { + return; + } + + let idx = instances.len(); + // Write this object's location data + self.queue.write_buffer( + &self.global_uniform.object_buffer, + ObjectData::SIZE * idx as u64, + bytemuck::cast_slice(&[ObjectData { + xpos: ship_pos.x, + ypos: ship_pos.y, + zpos: 1.0, + angle: ship_ang.0, + size: ship_cnt.size, + parent: 0, + is_child: 0, + _padding: Default::default(), + }]), + ); + + // Push this object's instance + instances.push(ObjectInstance { + sprite_index: ship_cnt.sprite.get_index(), + object_index: idx as u32, + }); + + // Draw engine flares if necessary + if s.controls.thrust { + for f in s.ship.outfits.iter_enginepoints() { + let flare = match s.ship.outfits.get_flare_sprite() { + None => continue, + Some(s) => s, + }; + + self.queue.write_buffer( + &self.global_uniform.object_buffer, + ObjectData::SIZE * instances.len() as u64, + bytemuck::cast_slice(&[ObjectData { + xpos: f.pos.x, + ypos: f.pos.y - f.size / 2.0, + zpos: 1.0, + angle: 0.0, + size: f.size, + parent: idx as u32, + is_child: 1, + _padding: Default::default(), + }]), + ); + + instances.push(ObjectInstance { + sprite_index: flare.get_index(), + object_index: instances.len() as u32, + }); + } + } + } + + pub(super) fn world_push_projectile( + &self, + state: &RenderState, + // NE and SW corners of screen + screen_clip: (Point2, Point2), + p: &ProjectileWorldObject, + instances: &mut Vec, + ) { + let r = state.world.get_rigid_body(p.rigid_body).unwrap(); + let proj_pos = util::rigidbody_position(&r); + let proj_rot = util::rigidbody_rotation(r); + let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 }); + let proj_cnt = &p.projectile.content; // TODO: don't clone this? + + // Position adjusted for parallax + // TODO: adjust parallax for zoom? + // 1.0 is z-coordinate, which is constant for ships + let pos: Point2 = (proj_pos - state.camera_pos.to_vec()) / 1.0; + + // Game dimensions of this sprite post-scale. + // Post-scale width or height, whichever is larger. + // This is in game units. + // + // We take the maximum to account for rotated sprites. + let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0); + + // Don't draw sprites that are off the screen + if pos.x < screen_clip.0.x - m + || pos.y > screen_clip.0.y + m + || pos.x > screen_clip.1.x + m + || pos.y < screen_clip.1.y - m + { + return; + } + + let idx = instances.len(); + // Write this object's location data + self.queue.write_buffer( + &self.global_uniform.object_buffer, + ObjectData::SIZE * idx as u64, + bytemuck::cast_slice(&[ObjectData { + xpos: proj_pos.x, + ypos: proj_pos.y, + zpos: 1.0, + angle: proj_ang.0, + size: 0f32.max(proj_cnt.size + p.size_rng), + parent: 0, + is_child: 0, + _padding: Default::default(), + }]), + ); + + // Push this object's instance + instances.push(ObjectInstance { + sprite_index: proj_cnt.sprite.get_index(), + object_index: idx as u32, + }); + } +} diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index a341f5a..4b45039 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -19,7 +19,7 @@ mod vertexbuffer; use galactica_content as content; pub use gpustate::GPUState; pub use renderstate::RenderState; -pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite}; +pub use sprite::AnchoredUiPosition; use cgmath::Matrix4; @@ -28,6 +28,7 @@ pub(crate) const SHADER_MAIN_VERTEX: &'static str = "vertex_main"; pub(crate) const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main"; #[rustfmt::skip] +#[allow(dead_code)] pub(crate) const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/crates/render/src/renderstate.rs b/crates/render/src/renderstate.rs index 3daf995..61a4cdd 100644 --- a/crates/render/src/renderstate.rs +++ b/crates/render/src/renderstate.rs @@ -1,25 +1,20 @@ use cgmath::Point2; use galactica_content::Content; - -use crate::{ObjectSprite, ParticleBuilder, UiSprite}; +use galactica_world::{ParticleBuilder, ShipPhysicsHandle, World}; /// Bundles parameters passed to a single call to GPUState::render pub struct RenderState<'a> { /// Camera position, in world units pub camera_pos: Point2, + /// Player ship + pub player: &'a ShipPhysicsHandle, + /// Height of screen, in world units pub camera_zoom: f32, - /// World object sprites - pub object_sprites: Vec, - - /// UI sprites - pub ui_sprites: Vec, - - /// Particles to create during this frame. - /// this array will be cleared. - pub new_particles: &'a mut Vec, + /// The world state to render + pub world: &'a World, // TODO: handle overflow /// The current time, in seconds @@ -27,4 +22,7 @@ pub struct RenderState<'a> { /// Game content pub content: &'a Content, + + /// Particles to spawn during this frame + pub particles: &'a mut Vec, } diff --git a/crates/render/src/sprite.rs b/crates/render/src/sprite.rs index 8fd98c4..ada0dce 100644 --- a/crates/render/src/sprite.rs +++ b/crates/render/src/sprite.rs @@ -1,90 +1,5 @@ use crate::content; -use cgmath::{Deg, Matrix2, Point2, Point3, Rad, Vector2}; -use rand::Rng; - -/// Instructions to create a new particle -pub struct ParticleBuilder { - /// The sprite to use for this particle - pub sprite: content::SpriteHandle, - - /// This object's center, in world coordinates. - pub pos: Point2, - - /// This particle's velocity, in world coordinates - pub velocity: Vector2, - - /// This particle's angle - pub angle: Rad, - - /// This particle's angular velocity (rad/sec) - pub angvel: Rad, - - /// This particle's lifetime, in seconds - pub lifetime: f32, - - /// The size of this particle, - /// given as height in world units. - pub size: f32, - - /// Fade this particle out over this many seconds as it expires - pub fade: f32, -} - -impl ParticleBuilder { - /// Create a ParticleBuilder from an Effect - pub fn from_content( - effect: &content::Effect, - pos: Point2, - parent_angle: Rad, - parent_velocity: Vector2, - target_velocity: Vector2, - ) -> Self { - let mut rng = rand::thread_rng(); - - let velocity = { - let a = - rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng); - let b = - rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng); - - let velocity = ((effect.velocity_scale_parent + a) * parent_velocity) - + ((effect.velocity_scale_target + b) * target_velocity); - - Matrix2::from_angle(Rad( - rng.gen_range(-effect.direction_rng..=effect.direction_rng) - )) * velocity - }; - - // Rad has odd behavior when its angle is zero, so we need extra checks here - let angvel = if effect.angvel_rng == 0.0 { - effect.angvel - } else { - Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng)) - }; - let angle = if effect.angle_rng == 0.0 { - parent_angle + effect.angle - } else { - parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng)) - }; - - ParticleBuilder { - sprite: effect.sprite, - pos, - velocity, - - angle, - angvel, - - lifetime: 0f32 - .max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)), - - // Make sure size isn't negative. This check should be on EVERY rng! - size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)), - - fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)), - } - } -} +use cgmath::{Deg, Point2}; /// The location of a UI element, in one of a few /// possible coordinate systems. @@ -112,8 +27,37 @@ pub enum AnchoredUiPosition { /// Position of this sprite's se corner, /// relative to the nw corner of the window. NwSe(Point2), + + /// Position of this sprite's ne corner, + /// relative to the ne corner of the window. + NeNe(Point2), } +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 { + 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 { @@ -133,47 +77,3 @@ pub struct UiSprite { /// This sprite's rotation, measured ccw pub angle: Deg, } - -/// A sprite that represents a world object: -/// Ships, planets, debris, etc -#[derive(Debug, Clone)] -pub struct ObjectSprite { - /// The sprite to draw - pub sprite: content::SpriteHandle, - - /// This object's center, in world coordinates. - pub pos: Point3, - - /// The size of this sprite, - /// given as height in world units. - pub size: f32, - - /// This sprite's rotation - /// (relative to north, measured ccw) - /// Note that this is different from the angle used by our physics system. - pub angle: Deg, - - /// Sprites that should be drawn relative to this sprite. - pub children: Option>, -} - -/// A sprite that is drawn relative to an ObjectSprite. -#[derive(Debug, Clone)] -pub struct ObjectSubSprite { - /// The sprite to draw - pub sprite: content::SpriteHandle, - - /// This object's position, in world coordinates. - /// This is relative to this sprite's parent. - pub pos: Point3, - - /// The size of this sprite, - /// given as height in world units. - pub size: f32, - - /// This sprite's rotation - /// (relative to north, measured ccw) - /// Just as position, this is relative to this - /// subsprite's parent sprite. - pub angle: Deg, -} diff --git a/crates/render/src/vertexbuffer/types.rs b/crates/render/src/vertexbuffer/types.rs index ddf2982..3611b4f 100644 --- a/crates/render/src/vertexbuffer/types.rs +++ b/crates/render/src/vertexbuffer/types.rs @@ -122,6 +122,7 @@ impl BufferObject for ObjectInstance { pub struct UiInstance { /// Extra transformations this sprite /// (rotation, etc) + /// TODO: remove pub transform: [[f32; 4]; 4], /// This lets us color ui sprites dynamically. @@ -277,3 +278,75 @@ impl BufferObject for ParticleInstance { } } } + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct RadialBarInstance { + /// How to interpret this object's coordinates. + /// this should be generated from an AnchoredUiPosition + pub anchor: u32, + + /// Position of this particle in logical pixels + pub position: [f32; 2], + + /// The diameter of this bar, in logical pixels + /// No part of the bar will be outside this diameter. + /// Stroke is grown inwards. + pub diameter: f32, + + // The stroke width of this bar, in logical pixels + pub stroke: f32, + + /// Angle of this radial bar, in radians + pub angle: f32, + + /// Color of this bar + pub color: [f32; 4], +} + +impl BufferObject for RadialBarInstance { + fn layout() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: Self::SIZE, + step_mode: wgpu::VertexStepMode::Instance, + attributes: &[ + // Anchor + wgpu::VertexAttribute { + offset: 0, + shader_location: 2, + format: wgpu::VertexFormat::Uint32, + }, + // Position + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 1]>() as wgpu::BufferAddress, + shader_location: 3, + format: wgpu::VertexFormat::Float32x2, + }, + // Diameter + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 4, + format: wgpu::VertexFormat::Float32, + }, + // Width + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, + shader_location: 5, + format: wgpu::VertexFormat::Float32, + }, + // Angle + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, + shader_location: 6, + format: wgpu::VertexFormat::Float32, + }, + // Color + wgpu::VertexAttribute { + offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress, + shader_location: 7, + format: wgpu::VertexFormat::Float32x4, + }, + ], + } + } +} diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml deleted file mode 100644 index 2564171..0000000 --- a/crates/ui/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "galactica-ui" -description = "UI routines for Galactica" -categories = { workspace = true } -keywords = { workspace = true } -version = { workspace = true } -rust-version = { workspace = true } -authors = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -repository = { workspace = true } -license = { workspace = true } -documentation = { workspace = true } -readme = { workspace = true } - -[lints] -workspace = true - -[dependencies] -galactica-content = { workspace = true } -galactica-world = { workspace = true } -galactica-render = { workspace = true } -galactica-gameobject = { workspace = true } -cgmath = { workspace = true } diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs deleted file mode 100644 index 2a88cdf..0000000 --- a/crates/ui/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod radar; - -pub use radar::build_radar; diff --git a/crates/ui/src/radar.rs b/crates/ui/src/radar.rs deleted file mode 100644 index 81af430..0000000 --- a/crates/ui/src/radar.rs +++ /dev/null @@ -1,194 +0,0 @@ -use cgmath::{Deg, InnerSpace, Point2, Vector2}; -use galactica_content as content; -use galactica_gameobject as object; -use galactica_render::{AnchoredUiPosition, UiSprite}; -use galactica_world::{util, ShipPhysicsHandle, World}; - -// TODO: args as one unit -pub fn build_radar( - ct: &content::Content, - player: ShipPhysicsHandle, - physics: &World, - system: &object::System, - camera_zoom: f32, - camera_aspect: f32, -) -> Vec { - let mut out = Vec::new(); - - let radar_range = 4000.0; - let radar_size = 300.0; - 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 (_, player_body) = physics.get_ship_body(player).unwrap(); - let player_position = util::rigidbody_position(player_body); - let planet_sprite = ct.get_sprite_handle("ui::planetblip"); - let ship_sprite = ct.get_sprite_handle("ui::shipblip"); - let arrow_sprite = ct.get_sprite_handle("ui::centerarrow"); - - out.push(UiSprite { - sprite: ct.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, - }); - - // Draw system objects - for o in &system.bodies { - let size = (o.size / o.pos.z) / (radar_range * system_object_scale); - let p = Point2 { - x: o.pos.x, - y: o.pos.y, - }; - let d = (p - player_position) / radar_range; - // Add half the blip sprite's height to distance - let m = d.magnitude() + (size / (2.0 * radar_size)); - if m < hide_range { - // Shrink blips as they get closeto the edge - let size = size.min((hide_range - m) * size * shrink_distance); - if size <= 2.0 { - // Don't draw super tiny sprites, they flicker - continue; - } - out.push(UiSprite { - sprite: planet_sprite, - pos: AnchoredUiPosition::NwC( - Point2 { - x: radar_size / 2.0 + 10.0, - y: radar_size / -2.0 - 10.0, - } + (d * (radar_size / 2.0)), - ), - dimensions: Point2 { - x: planet_sprite.aspect, - y: 1.0, - } * size, - angle: o.angle, - color: Some([0.5, 0.5, 0.5, 1.0]), - }); - } - } - - // Draw ships - for (s, r) in physics.iter_ship_body() { - let ship = ct.get_ship(s.ship.handle); - let size = (ship.size * ship.sprite.aspect) * ship_scale; - let p = util::rigidbody_position(r); - let d = (p - player_position) / radar_range; - let m = d.magnitude() + (size / (2.0 * radar_size)); - if m < hide_range { - let size = size.min((hide_range - m) * size * shrink_distance); - if size < 2.0 { - continue; - } - let angle: Deg = util::rigidbody_rotation(r) - .angle(Vector2 { x: 0.0, y: 1.0 }) - .into(); - let f = ct.get_faction(s.ship.faction).color; - let f = [f[0], f[1], f[2], 1.0]; - out.push(UiSprite { - sprite: ship_sprite, - pos: AnchoredUiPosition::NwC( - 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), - }); - } - } - - // Draw viewport frame - let d = Vector2 { - x: (camera_zoom / 2.0) * camera_aspect, - y: camera_zoom / 2.0, - } / radar_range; - let m = d.magnitude(); - let d = d * (radar_size / 2.0); - let color = Some([0.3, 0.3, 0.3, 1.0]); - if m < 0.8 { - let sprite = ct.get_sprite_handle("ui::radarframe"); - let dimensions = Point2 { - x: sprite.aspect, - y: 1.0, - } * 7.0f32.min((0.8 - m) * 70.0); - out.push(UiSprite { - sprite, - pos: AnchoredUiPosition::NwNw(Point2 { - x: (radar_size / 2.0 + 10.0) - d.x, - y: (radar_size / -2.0 - 10.0) + d.y, - }), - dimensions, - angle: Deg(0.0), - color, - }); - - out.push(UiSprite { - sprite, - pos: AnchoredUiPosition::NwSw(Point2 { - x: (radar_size / 2.0 + 10.0) - d.x, - y: (radar_size / -2.0 - 10.0) - d.y, - }), - dimensions, - angle: Deg(90.0), - color, - }); - - out.push(UiSprite { - sprite, - pos: AnchoredUiPosition::NwSe(Point2 { - x: (radar_size / 2.0 + 10.0) + d.x, - y: (radar_size / -2.0 - 10.0) - d.y, - }), - dimensions, - angle: Deg(180.0), - color, - }); - - out.push(UiSprite { - sprite, - pos: AnchoredUiPosition::NwNe(Point2 { - x: (radar_size / 2.0 + 10.0) + d.x, - y: (radar_size / -2.0 - 10.0) + d.y, - }), - dimensions, - angle: Deg(270.0), - color, - }); - } - - // 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 = q.angle(Vector2 { x: 0.0, y: 1.0 }).into(); - out.push(UiSprite { - sprite: arrow_sprite, - pos: AnchoredUiPosition::NwC( - 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)]), - }); - } - - return out; -} diff --git a/crates/world/Cargo.toml b/crates/world/Cargo.toml index 171f106..f242e92 100644 --- a/crates/world/Cargo.toml +++ b/crates/world/Cargo.toml @@ -17,7 +17,6 @@ readme = { workspace = true } workspace = true [dependencies] -galactica-render = { workspace = true } galactica-content = { workspace = true } galactica-gameobject = { workspace = true } diff --git a/crates/world/src/lib.rs b/crates/world/src/lib.rs index 887004f..b1e7f96 100644 --- a/crates/world/src/lib.rs +++ b/crates/world/src/lib.rs @@ -8,6 +8,9 @@ pub mod util; mod world; mod wrapper; +use cgmath::{Matrix2, Point2, Rad, Vector2}; +use galactica_content as content; +use rand::Rng; pub use world::World; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; @@ -15,3 +18,87 @@ use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; /// A lightweight handle for a specific ship in our physics system #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ShipPhysicsHandle(pub RigidBodyHandle, ColliderHandle); + +/// Instructions to create a new particle +pub struct ParticleBuilder { + /// The sprite to use for this particle + pub sprite: content::SpriteHandle, + + /// This object's center, in world coordinates. + pub pos: Point2, + + /// This particle's velocity, in world coordinates + pub velocity: Vector2, + + /// This particle's angle + pub angle: Rad, + + /// This particle's angular velocity (rad/sec) + pub angvel: Rad, + + /// This particle's lifetime, in seconds + pub lifetime: f32, + + /// The size of this particle, + /// given as height in world units. + pub size: f32, + + /// Fade this particle out over this many seconds as it expires + pub fade: f32, +} + +impl ParticleBuilder { + /// Create a ParticleBuilder from an Effect + pub fn from_content( + effect: &content::Effect, + pos: Point2, + parent_angle: Rad, + parent_velocity: Vector2, + target_velocity: Vector2, + ) -> Self { + let mut rng = rand::thread_rng(); + + let velocity = { + let a = + rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng); + let b = + rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng); + + let velocity = ((effect.velocity_scale_parent + a) * parent_velocity) + + ((effect.velocity_scale_target + b) * target_velocity); + + Matrix2::from_angle(Rad( + rng.gen_range(-effect.direction_rng..=effect.direction_rng) + )) * velocity + }; + + // Rad has odd behavior when its angle is zero, so we need extra checks here + let angvel = if effect.angvel_rng == 0.0 { + effect.angvel + } else { + Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng)) + }; + let angle = if effect.angle_rng == 0.0 { + parent_angle + effect.angle + } else { + parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng)) + }; + + ParticleBuilder { + sprite: effect.sprite, + pos, + velocity, + + angle, + angvel, + + lifetime: 0f32 + .max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)), + + // Make sure size isn't negative. This check should be on EVERY rng! + size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)), + + fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)), + } + } +} diff --git a/crates/world/src/objects/projectile.rs b/crates/world/src/objects/projectile.rs index f5d0472..e25f64e 100644 --- a/crates/world/src/objects/projectile.rs +++ b/crates/world/src/objects/projectile.rs @@ -1,13 +1,7 @@ -use cgmath::{Deg, InnerSpace, Point3, Vector2}; use rand::Rng; -use rapier2d::{ - dynamics::{RigidBody, RigidBodyHandle}, - geometry::ColliderHandle, -}; +use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; -use crate::util; use galactica_gameobject as object; -use galactica_render::ObjectSprite; /// A single projectile in the world #[derive(Debug)] @@ -41,25 +35,4 @@ impl ProjectileWorldObject { size_rng: rng.gen_range(-size_rng..=size_rng), } } - - /// Get this projectiles' sprite - pub fn get_sprite(&self, r: &RigidBody) -> ObjectSprite { - let pos = util::rigidbody_position(r); - let rot = util::rigidbody_rotation(r); - - // Sprites point north at 0 degrees - let ang: Deg = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into(); - - ObjectSprite { - sprite: self.projectile.content.sprite, - pos: Point3 { - x: pos.x, - y: pos.y, - z: 1.0, - }, - size: 0f32.max(self.projectile.content.size + self.size_rng), - angle: -ang, - children: None, - } - } } diff --git a/crates/world/src/objects/ship.rs b/crates/world/src/objects/ship.rs index 3e8bce8..a5386a3 100644 --- a/crates/world/src/objects/ship.rs +++ b/crates/world/src/objects/ship.rs @@ -4,10 +4,9 @@ use nalgebra::{point, vector}; use rand::{rngs::ThreadRng, Rng}; use rapier2d::{dynamics::RigidBody, geometry::Collider}; -use crate::{util, ShipPhysicsHandle}; +use crate::{util, ParticleBuilder, ShipPhysicsHandle}; use galactica_content as content; use galactica_gameobject as object; -use galactica_render::{ObjectSprite, ParticleBuilder}; pub struct ShipControls { pub left: bool, @@ -289,28 +288,4 @@ impl ShipWorldObject { i.cooldown -= t; } } - - /// Get this ship's sprite - pub fn get_sprite(&self, ct: &content::Content, r: &RigidBody) -> ObjectSprite { - let ship_pos = util::rigidbody_position(r); - let ship_rot = util::rigidbody_rotation(r); - - // Sprites point north at 0 degrees - let ship_ang: Deg = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into(); - - let s = ct.get_ship(self.ship.handle); - - ObjectSprite { - pos: (ship_pos.x, ship_pos.y, 1.0).into(), - sprite: s.sprite, - angle: -ship_ang, - size: s.size, - - children: if self.controls.thrust { - Some(self.ship.outfits.get_engine_flares()) - } else { - None - }, - } - } } diff --git a/crates/world/src/world.rs b/crates/world/src/world.rs index 3eece65..45662c0 100644 --- a/crates/world/src/world.rs +++ b/crates/world/src/world.rs @@ -9,10 +9,15 @@ use rapier2d::{ }; use std::{collections::HashMap, f32::consts::PI}; -use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle}; +use crate::{ + objects, + objects::{ProjectileWorldObject, ShipWorldObject}, + util, + wrapper::Wrapper, + ParticleBuilder, ShipPhysicsHandle, +}; use galactica_content as content; use galactica_gameobject as object; -use galactica_render::{ObjectSprite, ParticleBuilder}; /// Keeps track of all objects in the world that we can interact with. /// Also wraps our physics engine @@ -379,20 +384,22 @@ impl<'a> World { }) } - /// Iterate over all ship sprites in this physics system - pub fn get_ship_sprites( - &'a self, - ct: &'a content::Content, - ) -> impl Iterator + '_ { - self.ships - .values() - .map(|x| x.get_sprite(ct, &self.wrapper.rigid_body_set[x.physics_handle.0])) + /// Iterate over all ships in this physics system + pub fn iter_ships(&'a self) -> impl Iterator + '_ { + self.ships.values() } + /// Iterate over all ships in this physics system + pub fn iter_projectiles(&'a self) -> impl Iterator + '_ { + self.projectiles.values() + } + + /* /// Iterate over all projectile sprites in this physics system pub fn get_projectile_sprites(&self) -> impl Iterator + '_ { self.projectiles .values() .map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body])) } + */ }