Compare commits
3 Commits
6b5588b061
...
f1dba0978e
Author | SHA1 | Date |
---|---|---|
Mark | f1dba0978e | |
Mark | 34c0065c2d | |
Mark | d294ca4974 |
|
@ -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",
|
||||
|
|
|
@ -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"] }
|
||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
|||
Subproject commit 95558076be1819b10d5a56c62274cdf7f61ea9a8
|
||||
Subproject commit 1674e86c1edcbd119d94516950d2d274b46a19d4
|
|
@ -43,6 +43,9 @@ frames = [
|
|||
"ship/peregrine/11.png",
|
||||
]
|
||||
|
||||
[sprite."ui::status"]
|
||||
file = "ui/status.png"
|
||||
|
||||
[sprite."ui::radar"]
|
||||
file = "ui/radar.png"
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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<Box<dyn ShipBehavior>>,
|
||||
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<ObjectSprite> {
|
||||
let mut sprites: Vec<ObjectSprite> = 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<UiSprite> {
|
||||
return ui::build_radar(
|
||||
&self.content,
|
||||
self.player,
|
||||
&self.world,
|
||||
&self.system,
|
||||
self.camera.zoom,
|
||||
self.camera.aspect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ readme = { workspace = true }
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
galactica-render = { workspace = true }
|
||||
galactica-content = { workspace = true }
|
||||
|
||||
cgmath = { workspace = true }
|
||||
|
|
|
@ -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<ShipGun>,
|
||||
enginepoints: Vec<content::EnginePoint>,
|
||||
gunpoints: Vec<content::GunPoint>,
|
||||
|
||||
// Minor performance optimization, since we
|
||||
// rarely need to re-compute these.
|
||||
engine_flare_sprites: Vec<ObjectSubSprite>,
|
||||
}
|
||||
|
||||
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<Item = &EnginePoint> + '_ {
|
||||
self.enginepoints.iter()
|
||||
}
|
||||
|
||||
/// Get the sprites we should show if this ship is firing its engines
|
||||
pub fn get_engine_flares(&self) -> Vec<ObjectSubSprite> {
|
||||
return self.engine_flare_sprites.clone();
|
||||
pub fn get_flare_sprite(&self) -> Option<SpriteHandle> {
|
||||
self.stats.engine_flare_sprites.iter().next().map(|x| *x)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ObjectSprite> {
|
||||
return self.bodies.iter().map(|x| x.get_sprite()).collect();
|
||||
}
|
||||
//pub fn get_sprites(&self) -> Vec<ObjectSprite> {
|
||||
// return self.bodies.iter().map(|x| x.get_sprite()).collect();
|
||||
//}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
// };
|
||||
//}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
// INCLUDE: global uniform header
|
||||
|
||||
struct InstanceInput {
|
||||
@location(2) anchor: u32,
|
||||
@location(3) position: vec2<f32>,
|
||||
@location(4) diameter: f32,
|
||||
@location(5) stroke: f32,
|
||||
@location(6) angle: f32,
|
||||
@location(7) color: vec4<f32>,
|
||||
};
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(1) texture_coords: vec2<f32>,
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(2) center: vec2<f32>,
|
||||
@location(3) diameter: f32,
|
||||
@location(4) stroke: f32,
|
||||
@location(5) angle: f32,
|
||||
@location(6) color: vec4<f32>,
|
||||
};
|
||||
|
||||
|
||||
@group(0) @binding(0)
|
||||
var texture_array: binding_array<texture_2d<f32>>;
|
||||
@group(0) @binding(1)
|
||||
var sampler_array: binding_array<sampler>;
|
||||
|
||||
@vertex
|
||||
fn vertex_main(
|
||||
vertex: VertexInput,
|
||||
instance: InstanceInput,
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.position = vec4(vertex.position, 1.0);
|
||||
out.diameter = instance.diameter;
|
||||
out.stroke = instance.stroke;
|
||||
out.color = instance.color;
|
||||
out.angle = instance.angle;
|
||||
|
||||
// Center of this radial bar, in logical pixels,
|
||||
// with (0, 0) at the center of the screen.
|
||||
if instance.anchor == u32(0) {
|
||||
out.center = instance.position + (
|
||||
(global_data.window_size / global_data.window_scale.x)
|
||||
- vec2(instance.diameter, instance.diameter)
|
||||
) / 2.0;
|
||||
} else {
|
||||
out.center = vec2(0.0, 0.0);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// Fragment position in logical pixels, relative to arc center
|
||||
let p = (
|
||||
vec2(1.0, -1.0) *
|
||||
(in.position.xy - global_data.window_size / 2.0) /
|
||||
global_data.window_scale.x
|
||||
) - in.center;
|
||||
|
||||
let bar_width = in.stroke; // Width of filled bar
|
||||
let bar_radius = in.diameter / 2.0 - bar_width / 2.0; // Middle radius of the bar
|
||||
let angle = in.angle - floor(in.angle / 6.283) * 6.28318; // Sanely handle large angles (fmod(angle, 2pi))
|
||||
let zero_vector = vec2(0.0, 1.0); // Draw bar clockwise from this vector
|
||||
let frag_radius = distance(vec2(0.0, 0.0), p); // Radius of this fragment
|
||||
let feather = 2.0; // Size of feather, in logical pixels
|
||||
|
||||
// Clockwise angle between zero_vector and fragment location
|
||||
let frag_angle = atan2(
|
||||
p.y*zero_vector.x - p.x*zero_vector.y,
|
||||
-dot(p, zero_vector)
|
||||
) + 3.14159;
|
||||
|
||||
|
||||
// Line fill & feather
|
||||
if abs(frag_radius - bar_radius) <= bar_width / 2.0 && frag_angle <= angle {
|
||||
let x = (abs(frag_radius - bar_radius) - (bar_width/2.0 - feather)) / feather;
|
||||
return in.color * vec4(1.0, 1.0, 1.0, clamp(1.0 - x, 0.0, 1.0));
|
||||
}
|
||||
|
||||
// Round cap centers
|
||||
let cap_start_center = zero_vector * (in.diameter / 2.0 - (bar_width / 2.0));
|
||||
let cap_end_center = mat2x2(
|
||||
vec2(cos(-angle), sin(-angle)),
|
||||
vec2(-sin(-angle), cos(-angle))
|
||||
) * cap_start_center;
|
||||
|
||||
// Cap fill & feather
|
||||
let cap_start_d = distance(p, cap_start_center);
|
||||
let cap_end_d = distance(p, cap_end_center);
|
||||
if (
|
||||
cap_start_d <= bar_width / 2.0 ||
|
||||
cap_end_d <= bar_width / 2.0
|
||||
) {
|
||||
let x = (
|
||||
min(cap_start_d, cap_end_d)
|
||||
- (bar_width/2.0 - feather)
|
||||
) / feather;
|
||||
return in.color * vec4(1.0, 1.0, 1.0, clamp(1.0 - x, 0.0, 1.0));
|
||||
}
|
||||
|
||||
discard;
|
||||
}
|
|
@ -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],
|
||||
|
|
|
@ -27,6 +27,7 @@ impl GlobalUniform {
|
|||
camera_zoom: vec2<f32>,
|
||||
camera_zoom_limits: vec2<f32>,
|
||||
window_size: vec2<f32>,
|
||||
window_scale: vec2<f32>,
|
||||
window_aspect: vec2<f32>,
|
||||
starfield_sprite: vec2<u32>,
|
||||
starfield_tile_size: vec2<f32>,
|
||||
|
|
|
@ -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<UiInstance>) {
|
||||
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<f32> = 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<f32> = 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)]),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<VertexBuffer>,
|
||||
|
||||
radialbar: Rc<VertexBuffer>,
|
||||
}
|
||||
|
||||
/// Basic wgsl preprocesser
|
||||
|
@ -183,6 +195,14 @@ impl GPUState {
|
|||
Some(SPRITE_INDICES),
|
||||
galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT,
|
||||
)),
|
||||
|
||||
radialbar: Rc::new(VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
|
||||
"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<ObjectInstance>,
|
||||
clip_ne: Point2<f32>,
|
||||
clip_sw: Point2<f32>,
|
||||
s: &ObjectSprite,
|
||||
) {
|
||||
// Position adjusted for parallax
|
||||
// TODO: adjust parallax for zoom?
|
||||
let pos: Point2<f32> = {
|
||||
(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<UiInstance>, s: &UiSprite) {
|
||||
let logical_size: LogicalSize<f32> =
|
||||
|
@ -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<ObjectInstance> = 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<UiInstance> = 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);
|
|
@ -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<f32>, Point2<f32>),
|
||||
s: &ShipWorldObject,
|
||||
instances: &mut Vec<ObjectInstance>,
|
||||
) {
|
||||
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<f32> = (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<f32>, Point2<f32>),
|
||||
p: &ProjectileWorldObject,
|
||||
instances: &mut Vec<ObjectInstance>,
|
||||
) {
|
||||
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<f32> = (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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<f32> = Matrix4::new(
|
||||
1.0, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0,
|
||||
|
|
|
@ -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<f32>,
|
||||
|
||||
/// Player ship
|
||||
pub player: &'a ShipPhysicsHandle,
|
||||
|
||||
/// Height of screen, in world units
|
||||
pub camera_zoom: f32,
|
||||
|
||||
/// World object sprites
|
||||
pub object_sprites: Vec<ObjectSprite>,
|
||||
|
||||
/// UI sprites
|
||||
pub ui_sprites: Vec<UiSprite>,
|
||||
|
||||
/// Particles to create during this frame.
|
||||
/// this array will be cleared.
|
||||
pub new_particles: &'a mut Vec<ParticleBuilder>,
|
||||
/// 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<ParticleBuilder>,
|
||||
}
|
||||
|
|
|
@ -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<f32>,
|
||||
|
||||
/// This particle's velocity, in world coordinates
|
||||
pub velocity: Vector2<f32>,
|
||||
|
||||
/// This particle's angle
|
||||
pub angle: Rad<f32>,
|
||||
|
||||
/// This particle's angular velocity (rad/sec)
|
||||
pub angvel: Rad<f32>,
|
||||
|
||||
/// 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<f32>,
|
||||
parent_angle: Rad<f32>,
|
||||
parent_velocity: Vector2<f32>,
|
||||
target_velocity: Vector2<f32>,
|
||||
) -> 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<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 {
|
||||
|
@ -133,47 +77,3 @@ pub struct UiSprite {
|
|||
/// This sprite's rotation, measured ccw
|
||||
pub angle: Deg<f32>,
|
||||
}
|
||||
|
||||
/// 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<f32>,
|
||||
|
||||
/// 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<f32>,
|
||||
|
||||
/// Sprites that should be drawn relative to this sprite.
|
||||
pub children: Option<Vec<ObjectSubSprite>>,
|
||||
}
|
||||
|
||||
/// 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<f32>,
|
||||
|
||||
/// 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<f32>,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
|
@ -1,3 +0,0 @@
|
|||
mod radar;
|
||||
|
||||
pub use radar::build_radar;
|
|
@ -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<UiSprite> {
|
||||
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<f32> = 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<f32> = 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;
|
||||
}
|
|
@ -17,7 +17,6 @@ readme = { workspace = true }
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
galactica-render = { workspace = true }
|
||||
galactica-content = { workspace = true }
|
||||
galactica-gameobject = { workspace = true }
|
||||
|
||||
|
|
|
@ -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<f32>,
|
||||
|
||||
/// This particle's velocity, in world coordinates
|
||||
pub velocity: Vector2<f32>,
|
||||
|
||||
/// This particle's angle
|
||||
pub angle: Rad<f32>,
|
||||
|
||||
/// This particle's angular velocity (rad/sec)
|
||||
pub angvel: Rad<f32>,
|
||||
|
||||
/// 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<f32>,
|
||||
parent_angle: Rad<f32>,
|
||||
parent_velocity: Vector2<f32>,
|
||||
target_velocity: Vector2<f32>,
|
||||
) -> 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<f32> = 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<f32> = 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
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Item = ObjectSprite> + '_ {
|
||||
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<Item = &ShipWorldObject> + '_ {
|
||||
self.ships.values()
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_projectiles(&'a self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ {
|
||||
self.projectiles.values()
|
||||
}
|
||||
|
||||
/*
|
||||
/// Iterate over all projectile sprites in this physics system
|
||||
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
||||
self.projectiles
|
||||
.values()
|
||||
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue