Compare commits
No commits in common. "f1dba0978e45b199f69df87a4a4133f9828aaec4" and "6b5588b0617bae43f26ce79739398eacd887da25" have entirely different histories.
f1dba0978e
...
6b5588b061
|
@ -584,6 +584,7 @@ dependencies = [
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
"galactica-gameobject",
|
"galactica-gameobject",
|
||||||
"galactica-render",
|
"galactica-render",
|
||||||
|
"galactica-ui",
|
||||||
"galactica-world",
|
"galactica-world",
|
||||||
"pollster",
|
"pollster",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
|
@ -623,6 +624,7 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cgmath",
|
"cgmath",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
|
"galactica-render",
|
||||||
"rand",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -648,13 +650,23 @@ dependencies = [
|
||||||
"galactica-constants",
|
"galactica-constants",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
"galactica-packer",
|
"galactica-packer",
|
||||||
"galactica-world",
|
|
||||||
"image",
|
"image",
|
||||||
"rand",
|
"rand",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "galactica-ui"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cgmath",
|
||||||
|
"galactica-content",
|
||||||
|
"galactica-gameobject",
|
||||||
|
"galactica-render",
|
||||||
|
"galactica-world",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "galactica-world"
|
name = "galactica-world"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -663,6 +675,7 @@ dependencies = [
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
"galactica-gameobject",
|
"galactica-gameobject",
|
||||||
|
"galactica-render",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"rand",
|
"rand",
|
||||||
"rapier2d",
|
"rapier2d",
|
||||||
|
|
|
@ -48,12 +48,12 @@ galactica-render = { path = "crates/render" }
|
||||||
galactica-world = { path = "crates/world" }
|
galactica-world = { path = "crates/world" }
|
||||||
galactica-behavior = { path = "crates/behavior" }
|
galactica-behavior = { path = "crates/behavior" }
|
||||||
galactica-gameobject = { path = "crates/gameobject" }
|
galactica-gameobject = { path = "crates/gameobject" }
|
||||||
|
galactica-ui = { path = "crates/ui" }
|
||||||
galactica-packer = { path = "crates/packer" }
|
galactica-packer = { path = "crates/packer" }
|
||||||
galactica = { path = "crates/galactica" }
|
galactica = { path = "crates/galactica" }
|
||||||
|
|
||||||
image = { version = "0.24", features = ["png"] }
|
image = { version = "0.24", features = ["png"] }
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
# TODO: update winit, but wgpu seems to be tied to this version
|
|
||||||
winit = "0.28"
|
winit = "0.28"
|
||||||
wgpu = "0.18"
|
wgpu = "0.18"
|
||||||
bytemuck = { version = "1.12", features = ["derive"] }
|
bytemuck = { version = "1.12", features = ["derive"] }
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 1674e86c1edcbd119d94516950d2d274b46a19d4
|
Subproject commit 95558076be1819b10d5a56c62274cdf7f61ea9a8
|
|
@ -43,9 +43,6 @@ frames = [
|
||||||
"ship/peregrine/11.png",
|
"ship/peregrine/11.png",
|
||||||
]
|
]
|
||||||
|
|
||||||
[sprite."ui::status"]
|
|
||||||
file = "ui/status.png"
|
|
||||||
|
|
||||||
[sprite."ui::radar"]
|
[sprite."ui::radar"]
|
||||||
file = "ui/radar.png"
|
file = "ui/radar.png"
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ galactica-constants = { workspace = true }
|
||||||
galactica-world = { workspace = true }
|
galactica-world = { workspace = true }
|
||||||
galactica-behavior = { workspace = true }
|
galactica-behavior = { workspace = true }
|
||||||
galactica-gameobject = { workspace = true }
|
galactica-gameobject = { workspace = true }
|
||||||
|
galactica-ui = { workspace = true }
|
||||||
|
|
||||||
winit = { workspace = true }
|
winit = { workspace = true }
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
|
use content::SystemHandle;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||||
|
|
||||||
|
@ -9,8 +10,9 @@ use galactica_behavior::{behavior, ShipBehavior};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::RenderState;
|
use galactica_render::{ObjectSprite, ParticleBuilder, RenderState, UiSprite};
|
||||||
use galactica_world::{util, ParticleBuilder, ShipPhysicsHandle, World};
|
use galactica_ui as ui;
|
||||||
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
input: InputStatus,
|
input: InputStatus,
|
||||||
|
@ -21,8 +23,7 @@ pub struct Game {
|
||||||
start_instant: Instant,
|
start_instant: Instant,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
|
|
||||||
// TODO: include system in world
|
system: object::System,
|
||||||
//system: object::System,
|
|
||||||
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
|
||||||
playerbehavior: behavior::Player,
|
playerbehavior: behavior::Player,
|
||||||
|
|
||||||
|
@ -97,7 +98,8 @@ impl Game {
|
||||||
zoom: 500.0,
|
zoom: 500.0,
|
||||||
aspect: 1.0,
|
aspect: 1.0,
|
||||||
},
|
},
|
||||||
//system: object::System::new(&ct, SystemHandle { index: 0 }),
|
system: object::System::new(&ct, SystemHandle { index: 0 }),
|
||||||
|
|
||||||
paused: false,
|
paused: false,
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
world: physics,
|
world: physics,
|
||||||
|
@ -177,11 +179,41 @@ impl Game {
|
||||||
RenderState {
|
RenderState {
|
||||||
camera_pos: self.camera.pos,
|
camera_pos: self.camera.pos,
|
||||||
camera_zoom: self.camera.zoom,
|
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(),
|
current_time: self.start_instant.elapsed().as_secs_f32(),
|
||||||
content: &self.content,
|
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,6 +17,7 @@ readme = { workspace = true }
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
galactica-render = { workspace = true }
|
||||||
galactica-content = { workspace = true }
|
galactica-content = { workspace = true }
|
||||||
|
|
||||||
cgmath = { workspace = true }
|
cgmath = { workspace = true }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use content::{EnginePoint, SpriteHandle};
|
use cgmath::{Deg, Point3};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
use galactica_render::ObjectSubSprite;
|
||||||
|
|
||||||
/// Represents a gun attached to a specific ship at a certain gunpoint.
|
/// Represents a gun attached to a specific ship at a certain gunpoint.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -79,6 +80,10 @@ pub struct OutfitSet {
|
||||||
guns: Vec<ShipGun>,
|
guns: Vec<ShipGun>,
|
||||||
enginepoints: Vec<content::EnginePoint>,
|
enginepoints: Vec<content::EnginePoint>,
|
||||||
gunpoints: Vec<content::GunPoint>,
|
gunpoints: Vec<content::GunPoint>,
|
||||||
|
|
||||||
|
// Minor performance optimization, since we
|
||||||
|
// rarely need to re-compute these.
|
||||||
|
engine_flare_sprites: Vec<ObjectSubSprite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> OutfitSet {
|
impl<'a> OutfitSet {
|
||||||
|
@ -92,6 +97,7 @@ impl<'a> OutfitSet {
|
||||||
//total_space: content.space.clone(),
|
//total_space: content.space.clone(),
|
||||||
enginepoints: content.engines.clone(),
|
enginepoints: content.engines.clone(),
|
||||||
gunpoints: content.guns.clone(),
|
gunpoints: content.guns.clone(),
|
||||||
|
engine_flare_sprites: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +124,7 @@ impl<'a> OutfitSet {
|
||||||
self.available_space.occupy(&outfit.space);
|
self.available_space.occupy(&outfit.space);
|
||||||
self.stats.add(&outfit);
|
self.stats.add(&outfit);
|
||||||
self.outfits.push(o);
|
self.outfits.push(o);
|
||||||
//self.update_engine_flares();
|
self.update_engine_flares();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +138,7 @@ impl<'a> OutfitSet {
|
||||||
self.available_space.free(&outfit.space);
|
self.available_space.free(&outfit.space);
|
||||||
self.outfits.remove(i);
|
self.outfits.remove(i);
|
||||||
self.stats.remove(&outfit);
|
self.stats.remove(&outfit);
|
||||||
//self.update_engine_flares();
|
self.update_engine_flares();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a gun to this outfit set.
|
/// Add a gun to this outfit set.
|
||||||
|
@ -175,13 +181,34 @@ impl<'a> OutfitSet {
|
||||||
.map(|(a, b)| (b, a))
|
.map(|(a, b)| (b, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to ship
|
/// Update engine flare sprites
|
||||||
/// Iterate over all ships in this physics system
|
pub fn update_engine_flares(&mut self) {
|
||||||
pub fn iter_enginepoints(&self) -> impl Iterator<Item = &EnginePoint> + '_ {
|
// TODO: better way to pick flare texture
|
||||||
self.enginepoints.iter()
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_flare_sprite(&self) -> Option<SpriteHandle> {
|
/// Get the sprites we should show if this ship is firing its engines
|
||||||
self.stats.engine_flare_sprites.iter().next().map(|x| *x)
|
pub fn get_engine_flares(&self) -> Vec<ObjectSubSprite> {
|
||||||
|
return self.engine_flare_sprites.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::SystemObject;
|
use crate::SystemObject;
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
pub struct System {
|
pub struct System {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -26,7 +27,7 @@ impl System {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
//pub fn get_sprites(&self) -> Vec<ObjectSprite> {
|
pub fn get_sprites(&self) -> Vec<ObjectSprite> {
|
||||||
// return self.bodies.iter().map(|x| x.get_sprite()).collect();
|
return self.bodies.iter().map(|x| x.get_sprite()).collect();
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use cgmath::{Deg, Point3};
|
use cgmath::{Deg, Point3};
|
||||||
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
pub struct SystemObject {
|
pub struct SystemObject {
|
||||||
pub sprite: content::SpriteHandle,
|
pub sprite: content::SpriteHandle,
|
||||||
|
@ -10,13 +11,13 @@ pub struct SystemObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemObject {
|
impl SystemObject {
|
||||||
//pub(crate) fn get_sprite(&self) -> ObjectSprite {
|
pub(crate) fn get_sprite(&self) -> ObjectSprite {
|
||||||
// return ObjectSprite {
|
return ObjectSprite {
|
||||||
// sprite: self.sprite,
|
sprite: self.sprite,
|
||||||
// pos: self.pos,
|
pos: self.pos,
|
||||||
// angle: self.angle,
|
angle: self.angle,
|
||||||
// size: self.size,
|
size: self.size,
|
||||||
// children: None,
|
children: None,
|
||||||
// };
|
};
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ workspace = true
|
||||||
galactica-content = { workspace = true }
|
galactica-content = { workspace = true }
|
||||||
galactica-constants = { workspace = true }
|
galactica-constants = { workspace = true }
|
||||||
galactica-packer = { workspace = true }
|
galactica-packer = { workspace = true }
|
||||||
galactica-world = { workspace = true }
|
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
cgmath = { workspace = true }
|
cgmath = { workspace = true }
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
// 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,10 +23,6 @@ pub struct GlobalDataContent {
|
||||||
/// Size ratio of window, in physical pixels
|
/// Size ratio of window, in physical pixels
|
||||||
pub window_size: [f32; 2],
|
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
|
/// Aspect ratio of window
|
||||||
/// Second component is ignored.
|
/// Second component is ignored.
|
||||||
pub window_aspect: [f32; 2],
|
pub window_aspect: [f32; 2],
|
||||||
|
|
|
@ -27,7 +27,6 @@ impl GlobalUniform {
|
||||||
camera_zoom: vec2<f32>,
|
camera_zoom: vec2<f32>,
|
||||||
camera_zoom_limits: vec2<f32>,
|
camera_zoom_limits: vec2<f32>,
|
||||||
window_size: vec2<f32>,
|
window_size: vec2<f32>,
|
||||||
window_scale: vec2<f32>,
|
|
||||||
window_aspect: vec2<f32>,
|
window_aspect: vec2<f32>,
|
||||||
starfield_sprite: vec2<u32>,
|
starfield_sprite: vec2<u32>,
|
||||||
starfield_tile_size: vec2<f32>,
|
starfield_tile_size: vec2<f32>,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{Matrix4, Point2, Vector3};
|
use cgmath::{EuclideanSpace, Matrix4, Point2, Rad, Vector3};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
|
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use std::{iter, rc::Rc};
|
use std::{iter, rc::Rc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
|
@ -10,26 +9,18 @@ use winit::{self, dpi::LogicalSize, window::Window};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
content,
|
content,
|
||||||
globaluniform::{GlobalDataContent, GlobalUniform},
|
globaluniform::{GlobalDataContent, GlobalUniform, ObjectData},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
sprite::UiSprite,
|
|
||||||
starfield::Starfield,
|
starfield::Starfield,
|
||||||
texturearray::TextureArray,
|
texturearray::TextureArray,
|
||||||
vertexbuffer::{
|
vertexbuffer::{
|
||||||
consts::{SPRITE_INDICES, SPRITE_VERTICES},
|
consts::{SPRITE_INDICES, SPRITE_VERTICES},
|
||||||
types::{
|
types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
||||||
ObjectInstance, ParticleInstance, RadialBarInstance, StarfieldInstance, TexturedVertex,
|
|
||||||
UiInstance,
|
|
||||||
},
|
|
||||||
BufferObject, VertexBuffer,
|
BufferObject, VertexBuffer,
|
||||||
},
|
},
|
||||||
RenderState, OPENGL_TO_WGPU_MATRIX,
|
ObjectSprite, RenderState, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Additional implementaitons for GPUState
|
|
||||||
mod hud;
|
|
||||||
mod world;
|
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Consumes game state,
|
/// A high-level GPU wrapper. Consumes game state,
|
||||||
/// produces pretty pictures.
|
/// produces pretty pictures.
|
||||||
pub struct GPUState {
|
pub struct GPUState {
|
||||||
|
@ -50,7 +41,6 @@ pub struct GPUState {
|
||||||
starfield_pipeline: wgpu::RenderPipeline,
|
starfield_pipeline: wgpu::RenderPipeline,
|
||||||
particle_pipeline: wgpu::RenderPipeline,
|
particle_pipeline: wgpu::RenderPipeline,
|
||||||
ui_pipeline: wgpu::RenderPipeline,
|
ui_pipeline: wgpu::RenderPipeline,
|
||||||
radialbar_pipeline: wgpu::RenderPipeline,
|
|
||||||
|
|
||||||
starfield: Starfield,
|
starfield: Starfield,
|
||||||
texture_array: TextureArray,
|
texture_array: TextureArray,
|
||||||
|
@ -68,8 +58,6 @@ struct VertexBuffers {
|
||||||
/// of the particle instance array.
|
/// of the particle instance array.
|
||||||
particle_array_head: u64,
|
particle_array_head: u64,
|
||||||
particle: Rc<VertexBuffer>,
|
particle: Rc<VertexBuffer>,
|
||||||
|
|
||||||
radialbar: Rc<VertexBuffer>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic wgsl preprocesser
|
/// Basic wgsl preprocesser
|
||||||
|
@ -195,14 +183,6 @@ impl GPUState {
|
||||||
Some(SPRITE_INDICES),
|
Some(SPRITE_INDICES),
|
||||||
galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT,
|
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
|
// Load uniforms
|
||||||
|
@ -276,22 +256,6 @@ impl GPUState {
|
||||||
.set_bind_group_layouts(bind_group_layouts)
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.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();
|
let mut starfield = Starfield::new();
|
||||||
starfield.regenerate();
|
starfield.regenerate();
|
||||||
|
|
||||||
|
@ -309,7 +273,6 @@ impl GPUState {
|
||||||
starfield_pipeline,
|
starfield_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
particle_pipeline,
|
particle_pipeline,
|
||||||
radialbar_pipeline,
|
|
||||||
|
|
||||||
starfield,
|
starfield,
|
||||||
texture_array,
|
texture_array,
|
||||||
|
@ -337,7 +300,90 @@ impl GPUState {
|
||||||
self.update_starfield_buffer()
|
self.update_starfield_buffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:remove
|
/// 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a UiInstance for a ui sprite and add it to `instances`
|
/// Create a UiInstance for a ui sprite and add it to `instances`
|
||||||
fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) {
|
fn push_ui_sprite(&self, instances: &mut Vec<UiInstance>, s: &UiSprite) {
|
||||||
let logical_size: LogicalSize<f32> =
|
let logical_size: LogicalSize<f32> =
|
||||||
|
@ -381,11 +427,6 @@ impl GPUState {
|
||||||
y: 1.0 + (height / 2.0 + p.y) / (logical_size.height / 2.0),
|
y: 1.0 + (height / 2.0 + p.y) / (logical_size.height / 2.0),
|
||||||
z: 0.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);
|
let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0);
|
||||||
|
|
||||||
|
@ -399,7 +440,7 @@ impl GPUState {
|
||||||
/// Make an instance for all the game's sprites
|
/// Make an instance for all the game's sprites
|
||||||
/// (Objects and UI)
|
/// (Objects and UI)
|
||||||
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
|
/// This will Will panic if any X_SPRITE_INSTANCE_LIMIT is exceeded.
|
||||||
fn update_sprite_instances(&mut self, state: &RenderState) -> (usize, usize) {
|
fn update_sprite_instances(&self, state: &RenderState) -> (usize, usize) {
|
||||||
let mut object_instances: Vec<ObjectInstance> = Vec::new();
|
let mut object_instances: Vec<ObjectInstance> = Vec::new();
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
|
@ -407,13 +448,8 @@ impl GPUState {
|
||||||
let clip_ne = Point2::from((-self.window_aspect, 1.0)) * state.camera_zoom;
|
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;
|
let clip_sw = Point2::from((self.window_aspect, -1.0)) * state.camera_zoom;
|
||||||
|
|
||||||
// TODO:sort. Order matters.
|
for s in &state.object_sprites {
|
||||||
for s in state.world.iter_ships() {
|
self.push_object_sprite(state, &mut object_instances, clip_ne, clip_sw, &s);
|
||||||
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
|
// Enforce sprite limit
|
||||||
|
@ -428,10 +464,11 @@ impl GPUState {
|
||||||
bytemuck::cast_slice(&object_instances),
|
bytemuck::cast_slice(&object_instances),
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: we don't need an array, just use a counter
|
|
||||||
let mut ui_instances: Vec<UiInstance> = Vec::new();
|
let mut ui_instances: Vec<UiInstance> = Vec::new();
|
||||||
|
|
||||||
self.hud_add_radar(state, &mut ui_instances);
|
for s in &state.ui_sprites {
|
||||||
|
self.push_ui_sprite(&mut ui_instances, &s);
|
||||||
|
}
|
||||||
|
|
||||||
if ui_instances.len() as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT {
|
if ui_instances.len() as u64 > galactica_constants::UI_SPRITE_INSTANCE_LIMIT {
|
||||||
panic!("Ui sprite limit exceeded!")
|
panic!("Ui sprite limit exceeded!")
|
||||||
|
@ -478,7 +515,9 @@ impl GPUState {
|
||||||
/// Main render function. Draws sprites on a window.
|
/// Main render function. Draws sprites on a window.
|
||||||
pub fn render(&mut self, state: RenderState) -> Result<(), wgpu::SurfaceError> {
|
pub fn render(&mut self, state: RenderState) -> Result<(), wgpu::SurfaceError> {
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
let view = output.texture.create_view(&Default::default());
|
let view = output
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
let mut encoder = self
|
let mut encoder = self
|
||||||
.device
|
.device
|
||||||
|
@ -521,7 +560,6 @@ impl GPUState {
|
||||||
self.window_size.width as f32,
|
self.window_size.width as f32,
|
||||||
self.window_size.height 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],
|
window_aspect: [self.window_aspect, 0.0],
|
||||||
starfield_sprite: [s.get_index(), 0],
|
starfield_sprite: [s.get_index(), 0],
|
||||||
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
||||||
|
@ -534,8 +572,8 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write all new particles to GPU buffer
|
// Write all new particles to GPU buffer
|
||||||
state.particles.shuffle(&mut rand::thread_rng());
|
state.new_particles.shuffle(&mut rand::thread_rng());
|
||||||
for i in state.particles.iter() {
|
for i in state.new_particles.iter() {
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.vertex_buffers.particle.instances,
|
&self.vertex_buffers.particle.instances,
|
||||||
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
|
||||||
|
@ -558,7 +596,7 @@ impl GPUState {
|
||||||
self.vertex_buffers.particle_array_head = 0;
|
self.vertex_buffers.particle_array_head = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.particles.clear();
|
state.new_particles.clear();
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
let (n_object, n_ui) = self.update_sprite_instances(&state);
|
let (n_object, n_ui) = self.update_sprite_instances(&state);
|
||||||
|
@ -596,53 +634,6 @@ impl GPUState {
|
||||||
render_pass.set_pipeline(&self.ui_pipeline);
|
render_pass.set_pipeline(&self.ui_pipeline);
|
||||||
render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..n_ui as _);
|
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()
|
// begin_render_pass borrows encoder mutably, so we can't call finish()
|
||||||
// without dropping this variable.
|
// without dropping this variable.
|
||||||
drop(render_pass);
|
drop(render_pass);
|
|
@ -1,224 +0,0 @@
|
||||||
//! 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,162 +0,0 @@
|
||||||
//! 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;
|
use galactica_content as content;
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
pub use renderstate::RenderState;
|
pub use renderstate::RenderState;
|
||||||
pub use sprite::AnchoredUiPosition;
|
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
||||||
use cgmath::Matrix4;
|
use cgmath::Matrix4;
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ pub(crate) const SHADER_MAIN_VERTEX: &'static str = "vertex_main";
|
||||||
pub(crate) const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";
|
pub(crate) const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
pub(crate) const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
1.0, 0.0, 0.0, 0.0,
|
1.0, 0.0, 0.0, 0.0,
|
||||||
0.0, 1.0, 0.0, 0.0,
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_world::{ParticleBuilder, ShipPhysicsHandle, World};
|
|
||||||
|
use crate::{ObjectSprite, ParticleBuilder, UiSprite};
|
||||||
|
|
||||||
/// Bundles parameters passed to a single call to GPUState::render
|
/// Bundles parameters passed to a single call to GPUState::render
|
||||||
pub struct RenderState<'a> {
|
pub struct RenderState<'a> {
|
||||||
/// Camera position, in world units
|
/// Camera position, in world units
|
||||||
pub camera_pos: Point2<f32>,
|
pub camera_pos: Point2<f32>,
|
||||||
|
|
||||||
/// Player ship
|
|
||||||
pub player: &'a ShipPhysicsHandle,
|
|
||||||
|
|
||||||
/// Height of screen, in world units
|
/// Height of screen, in world units
|
||||||
pub camera_zoom: f32,
|
pub camera_zoom: f32,
|
||||||
|
|
||||||
/// The world state to render
|
/// World object sprites
|
||||||
pub world: &'a World,
|
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>,
|
||||||
|
|
||||||
// TODO: handle overflow
|
// TODO: handle overflow
|
||||||
/// The current time, in seconds
|
/// The current time, in seconds
|
||||||
|
@ -22,7 +27,4 @@ pub struct RenderState<'a> {
|
||||||
|
|
||||||
/// Game content
|
/// Game content
|
||||||
pub content: &'a Content,
|
pub content: &'a Content,
|
||||||
|
|
||||||
/// Particles to spawn during this frame
|
|
||||||
pub particles: &'a mut Vec<ParticleBuilder>,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,90 @@
|
||||||
use crate::content;
|
use crate::content;
|
||||||
use cgmath::{Deg, Point2};
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The location of a UI element, in one of a few
|
/// The location of a UI element, in one of a few
|
||||||
/// possible coordinate systems.
|
/// possible coordinate systems.
|
||||||
|
@ -27,37 +112,8 @@ pub enum AnchoredUiPosition {
|
||||||
/// Position of this sprite's se corner,
|
/// Position of this sprite's se corner,
|
||||||
/// relative to the nw corner of the window.
|
/// relative to the nw corner of the window.
|
||||||
NwSe(Point2<f32>),
|
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
|
/// A sprite that represents a ui element
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UiSprite {
|
pub struct UiSprite {
|
||||||
|
@ -77,3 +133,47 @@ pub struct UiSprite {
|
||||||
/// This sprite's rotation, measured ccw
|
/// This sprite's rotation, measured ccw
|
||||||
pub angle: Deg<f32>,
|
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,7 +122,6 @@ impl BufferObject for ObjectInstance {
|
||||||
pub struct UiInstance {
|
pub struct UiInstance {
|
||||||
/// Extra transformations this sprite
|
/// Extra transformations this sprite
|
||||||
/// (rotation, etc)
|
/// (rotation, etc)
|
||||||
/// TODO: remove
|
|
||||||
pub transform: [[f32; 4]; 4],
|
pub transform: [[f32; 4]; 4],
|
||||||
|
|
||||||
/// This lets us color ui sprites dynamically.
|
/// This lets us color ui sprites dynamically.
|
||||||
|
@ -278,75 +277,3 @@ 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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
[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 }
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod radar;
|
||||||
|
|
||||||
|
pub use radar::build_radar;
|
|
@ -0,0 +1,194 @@
|
||||||
|
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,6 +17,7 @@ readme = { workspace = true }
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
galactica-render = { workspace = true }
|
||||||
galactica-content = { workspace = true }
|
galactica-content = { workspace = true }
|
||||||
galactica-gameobject = { workspace = true }
|
galactica-gameobject = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,6 @@ pub mod util;
|
||||||
mod world;
|
mod world;
|
||||||
mod wrapper;
|
mod wrapper;
|
||||||
|
|
||||||
use cgmath::{Matrix2, Point2, Rad, Vector2};
|
|
||||||
use galactica_content as content;
|
|
||||||
use rand::Rng;
|
|
||||||
pub use world::World;
|
pub use world::World;
|
||||||
|
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
||||||
|
@ -18,87 +15,3 @@ use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
||||||
/// A lightweight handle for a specific ship in our physics system
|
/// A lightweight handle for a specific ship in our physics system
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct ShipPhysicsHandle(pub RigidBodyHandle, ColliderHandle);
|
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,7 +1,13 @@
|
||||||
|
use cgmath::{Deg, InnerSpace, Point3, Vector2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
use rapier2d::{
|
||||||
|
dynamics::{RigidBody, RigidBodyHandle},
|
||||||
|
geometry::ColliderHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::util;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
/// A single projectile in the world
|
/// A single projectile in the world
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -35,4 +41,25 @@ impl ProjectileWorldObject {
|
||||||
size_rng: rng.gen_range(-size_rng..=size_rng),
|
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,9 +4,10 @@ use nalgebra::{point, vector};
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rapier2d::{dynamics::RigidBody, geometry::Collider};
|
use rapier2d::{dynamics::RigidBody, geometry::Collider};
|
||||||
|
|
||||||
use crate::{util, ParticleBuilder, ShipPhysicsHandle};
|
use crate::{util, ShipPhysicsHandle};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
|
use galactica_render::{ObjectSprite, ParticleBuilder};
|
||||||
|
|
||||||
pub struct ShipControls {
|
pub struct ShipControls {
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
|
@ -288,4 +289,28 @@ impl ShipWorldObject {
|
||||||
i.cooldown -= t;
|
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,15 +9,10 @@ use rapier2d::{
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, f32::consts::PI};
|
use std::{collections::HashMap, f32::consts::PI};
|
||||||
|
|
||||||
use crate::{
|
use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle};
|
||||||
objects,
|
|
||||||
objects::{ProjectileWorldObject, ShipWorldObject},
|
|
||||||
util,
|
|
||||||
wrapper::Wrapper,
|
|
||||||
ParticleBuilder, ShipPhysicsHandle,
|
|
||||||
};
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
|
use galactica_render::{ObjectSprite, ParticleBuilder};
|
||||||
|
|
||||||
/// Keeps track of all objects in the world that we can interact with.
|
/// Keeps track of all objects in the world that we can interact with.
|
||||||
/// Also wraps our physics engine
|
/// Also wraps our physics engine
|
||||||
|
@ -384,22 +379,20 @@ impl<'a> World {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
/// Iterate over all ship sprites in this physics system
|
||||||
pub fn iter_ships(&'a self) -> impl Iterator<Item = &ShipWorldObject> + '_ {
|
pub fn get_ship_sprites(
|
||||||
self.ships.values()
|
&'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_projectiles(&'a self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ {
|
|
||||||
self.projectiles.values()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
/// Iterate over all projectile sprites in this physics system
|
/// Iterate over all projectile sprites in this physics system
|
||||||
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
||||||
self.projectiles
|
self.projectiles
|
||||||
.values()
|
.values()
|
||||||
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
|
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue