Compare commits
No commits in common. "67123efa6c1eac17af9debc486000b91c7da39c2" and "d09158a324b2542510b7d73610150737f33343a2" have entirely different histories.
67123efa6c
...
d09158a324
|
@ -10,12 +10,6 @@ incremental = true
|
||||||
codegen-units = 256
|
codegen-units = 256
|
||||||
rpath = false
|
rpath = false
|
||||||
|
|
||||||
# Rapier is a LOT faster with optimizations,
|
|
||||||
# make sure they're always enabled
|
|
||||||
[profile.dev.package.rapier2d]
|
|
||||||
opt-level = 3
|
|
||||||
codegen-units = 1
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
debug = false
|
debug = false
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -1,8 +1,7 @@
|
||||||
# Specific projects
|
# Specific projects
|
||||||
|
|
||||||
## Currently working on:
|
## Currently working on:
|
||||||
- first: remove object array
|
- first: fix particles & physics
|
||||||
- first: sticky particles
|
|
||||||
- clickable buttons
|
- clickable buttons
|
||||||
- planet outfitter
|
- planet outfitter
|
||||||
|
|
||||||
|
|
|
@ -303,11 +303,6 @@ impl Content {
|
||||||
return &self.sprite_atlas.atlas_list;
|
return &self.sprite_atlas.atlas_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get sprite atlas metadata
|
|
||||||
pub fn get_atlas(&self) -> &SpriteAtlas {
|
|
||||||
return &self.sprite_atlas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a texture by its index
|
/// Get a texture by its index
|
||||||
pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
|
pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
|
||||||
&self.sprite_atlas.get_by_idx(idx)
|
&self.sprite_atlas.get_by_idx(idx)
|
||||||
|
|
|
@ -313,9 +313,4 @@ impl SpriteAutomaton {
|
||||||
fade: self.current_edge_progress / self.current_edge_duration,
|
fade: self.current_edge_progress / self.current_edge_duration,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the sprite this automaton is using
|
|
||||||
pub fn get_sprite(&self) -> SpriteHandle {
|
|
||||||
self.sprite
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,45 @@
|
||||||
use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle};
|
use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle};
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_system::data::ShipPersonality;
|
use galactica_system::data::ShipPersonality;
|
||||||
use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources};
|
use galactica_system::phys::{
|
||||||
|
ParticleBuilder, PhysSim, PhysSimShipHandle, PhysStepResources, Wrapper,
|
||||||
|
};
|
||||||
use galactica_util::timing::Timing;
|
use galactica_util::timing::Timing;
|
||||||
use nalgebra::Point2;
|
use nalgebra::Point2;
|
||||||
|
use rand::seq::SliceRandom;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GameState {
|
||||||
|
pub systemsim: PhysSim,
|
||||||
|
pub timing: Timing,
|
||||||
|
pub start_instant: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for GameState {}
|
||||||
|
unsafe impl Sync for GameState {}
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
// Core game data
|
// Core game data
|
||||||
ct: Content,
|
ct: Content,
|
||||||
systemsim: PhysSim,
|
state: GameState,
|
||||||
timing: Timing,
|
|
||||||
start_instant: Instant,
|
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
wrapper: Wrapper, // Physics computer
|
||||||
time_scale: f32,
|
time_scale: f32,
|
||||||
last_update: Instant,
|
last_update: Instant,
|
||||||
|
|
||||||
|
/// Particles to create this frame.
|
||||||
|
/// Must be cleared at the start of every frame
|
||||||
|
/// TODO: better way to handle this?
|
||||||
|
new_particles: Vec<ParticleBuilder>,
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe impl<'a> Send for Game {}
|
unsafe impl<'a> Send for Game {}
|
||||||
|
|
||||||
impl<'a> Game {
|
impl<'a> Game {
|
||||||
pub fn make_player(&mut self) -> PhysSimShipHandle {
|
pub fn make_player(&mut self) -> PhysSimShipHandle {
|
||||||
let player = self.systemsim.add_ship(
|
let player = self.state.systemsim.add_ship(
|
||||||
&self.ct,
|
&self.ct,
|
||||||
ShipHandle { index: 0 },
|
ShipHandle { index: 0 },
|
||||||
FactionHandle { index: 0 },
|
FactionHandle { index: 0 },
|
||||||
|
@ -30,7 +47,7 @@ impl<'a> Game {
|
||||||
Point2::new(0.0, 4000.0),
|
Point2::new(0.0, 4000.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
let s = self.systemsim.get_ship_mut(&player).unwrap();
|
let s = self.state.systemsim.get_ship_mut(&player).unwrap();
|
||||||
s.add_outfits(
|
s.add_outfits(
|
||||||
&self.ct,
|
&self.ct,
|
||||||
[
|
[
|
||||||
|
@ -82,46 +99,52 @@ impl<'a> Game {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Game {
|
let state = GameState {
|
||||||
ct,
|
|
||||||
systemsim,
|
systemsim,
|
||||||
timing: Timing::new(),
|
timing: Timing::new(),
|
||||||
start_instant: Instant::now(),
|
start_instant: Instant::now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Game {
|
||||||
|
ct,
|
||||||
|
state,
|
||||||
|
|
||||||
|
wrapper: Wrapper::new(),
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
|
new_particles: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_player_controls(&mut self, player: &mut PlayerAgent) {
|
pub fn update_player_controls(&mut self, player: &mut PlayerAgent) {
|
||||||
self.systemsim.update_player_controls(&self.ct, player)
|
self.state
|
||||||
|
.systemsim
|
||||||
|
.update_player_controls(&self.ct, player)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, phys_img: &PhysImage) {
|
pub fn get_state(&self) -> &GameState {
|
||||||
self.timing.start_frame();
|
&self.state
|
||||||
self.systemsim.step(
|
}
|
||||||
PhysStepResources {
|
|
||||||
|
pub fn get_particles(&self) -> &Vec<ParticleBuilder> {
|
||||||
|
&self.new_particles
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) {
|
||||||
|
self.state.timing.start_frame();
|
||||||
|
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
||||||
|
self.new_particles.clear();
|
||||||
|
|
||||||
|
self.state.systemsim.step(PhysStepResources {
|
||||||
ct: &self.ct,
|
ct: &self.ct,
|
||||||
t: self.last_update.elapsed().as_secs_f32() * self.time_scale,
|
particles: &mut self.new_particles,
|
||||||
timing: &mut self.timing,
|
timing: &mut self.state.timing,
|
||||||
},
|
wrapper: &mut self.wrapper,
|
||||||
phys_img,
|
t,
|
||||||
);
|
});
|
||||||
|
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
self.timing.mark_frame();
|
self.new_particles.shuffle(&mut rand::thread_rng());
|
||||||
}
|
self.state.timing.mark_frame();
|
||||||
|
|
||||||
pub fn update_image(&self, phys_img: &mut PhysImage) {
|
|
||||||
self.systemsim.update_image(phys_img);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Game {
|
|
||||||
pub fn get_timing(&self) -> &Timing {
|
|
||||||
&self.timing
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current_time(&self) -> f32 {
|
|
||||||
self.start_instant.elapsed().as_secs_f32()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,7 @@ use anyhow::{bail, Result};
|
||||||
use galactica_content::{Content, SystemHandle};
|
use galactica_content::{Content, SystemHandle};
|
||||||
use galactica_playeragent::{PlayerAgent, PlayerStatus};
|
use galactica_playeragent::{PlayerAgent, PlayerStatus};
|
||||||
use galactica_render::RenderInput;
|
use galactica_render::RenderInput;
|
||||||
use galactica_system::{
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
||||||
data::ShipState,
|
|
||||||
phys::{PhysImage, PhysSimShipHandle},
|
|
||||||
};
|
|
||||||
use galactica_util::constants::ASSET_CACHE;
|
use galactica_util::constants::ASSET_CACHE;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -64,20 +61,19 @@ fn try_main() -> Result<()> {
|
||||||
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut phys_img = PhysImage::new();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
match event {
|
match event {
|
||||||
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
Event::RedrawRequested(window_id) if window_id == gpu.window().id() => {
|
||||||
let render_input = RenderInput {
|
let render_input = RenderInput {
|
||||||
camera_pos: player.camera.pos,
|
camera_pos: player.camera.pos,
|
||||||
camera_zoom: player.camera.zoom,
|
camera_zoom: player.camera.zoom,
|
||||||
current_time: game.get_current_time(),
|
current_time: game.get_state().start_instant.elapsed().as_secs_f32(),
|
||||||
ct: &content,
|
ct: &content,
|
||||||
phys_img: &phys_img,
|
systemsim: &game.get_state().systemsim,
|
||||||
|
particles: game.get_particles(),
|
||||||
player: &player,
|
player: &player,
|
||||||
current_system: SystemHandle { index: 0 },
|
current_system: SystemHandle { index: 0 },
|
||||||
timing: game.get_timing().clone(),
|
timing: game.get_state().timing.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match gpu.render(render_input) {
|
match gpu.render(render_input) {
|
||||||
|
@ -91,19 +87,29 @@ fn try_main() -> Result<()> {
|
||||||
|
|
||||||
Event::MainEventsCleared => {
|
Event::MainEventsCleared => {
|
||||||
game.update_player_controls(&mut player);
|
game.update_player_controls(&mut player);
|
||||||
game.step(&phys_img);
|
game.update();
|
||||||
game.update_image(&mut phys_img);
|
|
||||||
|
|
||||||
// TODO: clean up
|
// TODO: clean up
|
||||||
let player_status = {
|
let player_status = {
|
||||||
let pos = {
|
let pos = {
|
||||||
let o = phys_img.get_ship(&PhysSimShipHandle(player.ship.unwrap()));
|
let o = &game
|
||||||
|
.get_state()
|
||||||
|
.systemsim
|
||||||
|
.get_ship(&PhysSimShipHandle(player.ship.unwrap()));
|
||||||
if let Some(o) = o {
|
if let Some(o) = o {
|
||||||
match o.ship.get_data().get_state() {
|
match o.get_data().get_state() {
|
||||||
ShipState::Landing { .. }
|
ShipState::Landing { .. }
|
||||||
| ShipState::UnLanding { .. }
|
| ShipState::UnLanding { .. }
|
||||||
| ShipState::Collapsing { .. }
|
| ShipState::Collapsing { .. }
|
||||||
| ShipState::Flying { .. } => Some(*o.rigidbody.translation()),
|
| ShipState::Flying { .. } => {
|
||||||
|
let r =
|
||||||
|
&game.get_state().systemsim.get_rigid_body(o.rigid_body);
|
||||||
|
if let Some(r) = r {
|
||||||
|
Some(*r.translation())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ShipState::Landed { target } => {
|
ShipState::Landed { target } => {
|
||||||
let b = content.get_system_object(*target);
|
let b = content.get_system_object(*target);
|
||||||
|
|
|
@ -76,11 +76,6 @@ impl SpriteAtlas {
|
||||||
self.path_map.get(path).map(|x| *x)
|
self.path_map.get(path).map(|x| *x)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate all images in this atlas
|
|
||||||
pub fn iter_images(&self) -> impl Iterator<Item = &SpriteAtlasImage> {
|
|
||||||
self.index.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the number of images in this atlas
|
/// Get the number of images in this atlas
|
||||||
pub fn len(&self) -> u32 {
|
pub fn len(&self) -> u32 {
|
||||||
self.index.len() as u32
|
self.index.len() as u32
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
// INCLUDE: global uniform header
|
||||||
|
|
||||||
|
struct InstanceInput {
|
||||||
|
@location(2) position: vec2<f32>,
|
||||||
|
@location(3) velocity: vec2<f32>,
|
||||||
|
@location(4) angle: f32,
|
||||||
|
@location(5) angvel: f32,
|
||||||
|
@location(6) size: f32,
|
||||||
|
@location(7) created: f32,
|
||||||
|
@location(8) expires: f32,
|
||||||
|
@location(9) fade: f32,
|
||||||
|
@location(10) texture_index: vec2<u32>,
|
||||||
|
@location(11) texture_fade: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) texture_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) position: vec4<f32>,
|
||||||
|
@location(0) tween: f32,
|
||||||
|
@location(1) fade: f32,
|
||||||
|
@location(2) texture_index_a: u32,
|
||||||
|
@location(3) texture_coords_a: vec2<f32>,
|
||||||
|
@location(4) texture_index_b: u32,
|
||||||
|
@location(5) texture_coords_b: vec2<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;
|
||||||
|
|
||||||
|
// Skip expired particles
|
||||||
|
if instance.expires < global_data.current_time.x {
|
||||||
|
out.tween = 0.0;
|
||||||
|
out.texture_index_a = u32(0);
|
||||||
|
out.texture_index_b = u32(0);
|
||||||
|
out.texture_coords_a = vec2(0.0, 0.0);
|
||||||
|
out.texture_coords_b = vec2(0.0, 0.0);
|
||||||
|
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
let age = global_data.current_time.x - instance.created;
|
||||||
|
|
||||||
|
|
||||||
|
// Apply transformations
|
||||||
|
let angle = instance.angle + age * instance.angvel;
|
||||||
|
|
||||||
|
var scale: f32 = instance.size / global_data.camera_zoom.x;
|
||||||
|
var pos: vec2<f32> = vec2(
|
||||||
|
vertex.position.x * scale * global_sprites[instance.sprite_index].aspect,
|
||||||
|
vertex.position.y * scale
|
||||||
|
);
|
||||||
|
|
||||||
|
// Correct angle, since sprites point north and zero degrees is east
|
||||||
|
pos = mat2x2(
|
||||||
|
vec2(cos(angle - 1.5708), sin(angle - 1.5708)),
|
||||||
|
vec2(-sin(angle - 1.5708), cos(angle - 1.5708))
|
||||||
|
) * pos;
|
||||||
|
|
||||||
|
var trans: vec2<f32> = (instance.position + (instance.velocity * age) - global_data.camera_position);
|
||||||
|
|
||||||
|
pos = vec2(
|
||||||
|
pos.x / global_data.window_aspect.x,
|
||||||
|
pos.y
|
||||||
|
);
|
||||||
|
|
||||||
|
pos = pos + vec2(
|
||||||
|
trans.x / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
|
||||||
|
trans.y / (global_data.camera_zoom.x / 2.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
out.position = vec4(pos, 0.0, 1.0);
|
||||||
|
|
||||||
|
if instance.expires - global_data.current_time.x <= instance.fade {
|
||||||
|
out.fade = (instance.expires - global_data.current_time.x) / instance.fade;
|
||||||
|
} else {
|
||||||
|
out.fade = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute texture coordinates
|
||||||
|
let frame = animate(instance.sprite_index, age, 0.0);
|
||||||
|
out.tween = instance.texture_fade;
|
||||||
|
|
||||||
|
let t = global_atlas[instance.texture_index.x];
|
||||||
|
out.texture_index_a = u32(t.atlas_texture);
|
||||||
|
out.texture_coords_a = vec2(t.xpos, t.ypos);
|
||||||
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
out.texture_coords_a = out.texture_coords_a + vec2(t.width, 0.0);
|
||||||
|
}
|
||||||
|
if vertex.texture_coords.y == 1.0 {
|
||||||
|
out.texture_coords_a = out.texture_coords_a + vec2(0.0, t.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = global_atlas[instance.texture_index.y];
|
||||||
|
out.texture_index_b = u32(b.atlas_texture);
|
||||||
|
out.texture_coords_b = vec2(b.xpos, b.ypos);
|
||||||
|
if vertex.texture_coords.x == 1.0 {
|
||||||
|
out.texture_coords_b = out.texture_coords_b + vec2(b.width, 0.0);
|
||||||
|
}
|
||||||
|
if vertex.texture_coords.y == 1.0 {
|
||||||
|
out.texture_coords_b = out.texture_coords_b + vec2(0.0, b.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
|
||||||
|
return mix(
|
||||||
|
textureSampleLevel(
|
||||||
|
texture_array[in.texture_index_a],
|
||||||
|
sampler_array[0],
|
||||||
|
in.texture_coords_a,
|
||||||
|
0.0
|
||||||
|
).rgba,
|
||||||
|
textureSampleLevel(
|
||||||
|
texture_array[in.texture_index_b],
|
||||||
|
sampler_array[0],
|
||||||
|
in.texture_coords_b,
|
||||||
|
0.0
|
||||||
|
).rgba,
|
||||||
|
in.tween
|
||||||
|
) * vec4(1.0, 1.0, 1.0, in.fade);
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ pub struct GPUState {
|
||||||
|
|
||||||
object_pipeline: wgpu::RenderPipeline,
|
object_pipeline: wgpu::RenderPipeline,
|
||||||
starfield_pipeline: wgpu::RenderPipeline,
|
starfield_pipeline: wgpu::RenderPipeline,
|
||||||
|
//particle_pipeline: wgpu::RenderPipeline,
|
||||||
ui_pipeline: wgpu::RenderPipeline,
|
ui_pipeline: wgpu::RenderPipeline,
|
||||||
radialbar_pipeline: wgpu::RenderPipeline,
|
radialbar_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,23 @@ impl GPUState {
|
||||||
.set_bind_group_layouts(bind_group_layouts)
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
/*
|
||||||
|
let particle_pipeline = PipelineBuilder::new("particle", &device)
|
||||||
|
.set_shader(&preprocess_shader(
|
||||||
|
&include_str!(concat!(
|
||||||
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
|
"/shaders/",
|
||||||
|
"particle.wgsl"
|
||||||
|
)),
|
||||||
|
&global_uniform,
|
||||||
|
1,
|
||||||
|
))
|
||||||
|
.set_format(config.format)
|
||||||
|
.set_triangle(true)
|
||||||
|
.set_vertex_buffer(vertex_buffers.get_particle())
|
||||||
|
.set_bind_group_layouts(bind_group_layouts)
|
||||||
|
.build();*/
|
||||||
|
|
||||||
let radialbar_pipeline = PipelineBuilder::new("radialbar", &device)
|
let radialbar_pipeline = PipelineBuilder::new("radialbar", &device)
|
||||||
.set_shader(&preprocess_shader(
|
.set_shader(&preprocess_shader(
|
||||||
&include_str!(concat!(
|
&include_str!(concat!(
|
||||||
|
@ -213,6 +230,7 @@ impl GPUState {
|
||||||
object_pipeline,
|
object_pipeline,
|
||||||
starfield_pipeline,
|
starfield_pipeline,
|
||||||
ui_pipeline,
|
ui_pipeline,
|
||||||
|
//particle_pipeline,
|
||||||
radialbar_pipeline,
|
radialbar_pipeline,
|
||||||
|
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -10,35 +10,37 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl GPUState {
|
impl GPUState {
|
||||||
pub(super) fn phys_push_ships(
|
pub(super) fn phys_push_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &RenderInput,
|
input: &RenderInput,
|
||||||
// NE and SW corners of screen
|
// NE and SW corners of screen
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
) {
|
) {
|
||||||
for s in input.phys_img.iter_ships() {
|
for ship in input.systemsim.iter_ships() {
|
||||||
|
// TODO: move collapse sequence here?
|
||||||
|
|
||||||
let ship_pos;
|
let ship_pos;
|
||||||
let ship_ang;
|
let ship_ang;
|
||||||
let ship_cnt;
|
let ship_cnt;
|
||||||
match s.ship.get_data().get_state() {
|
match ship.get_data().get_state() {
|
||||||
ShipState::Dead | ShipState::Landed { .. } => continue,
|
ShipState::Dead | ShipState::Landed { .. } => continue,
|
||||||
|
|
||||||
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
ShipState::Collapsing { .. } | ShipState::Flying { .. } => {
|
||||||
let r = &s.rigidbody;
|
let r = input.systemsim.get_rigid_body(ship.rigid_body).unwrap();
|
||||||
let pos = *r.translation();
|
let pos = *r.translation();
|
||||||
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
ship_pos = Point3::new(pos.x, pos.y, 1.0);
|
||||||
let ship_rot = r.rotation();
|
let ship_rot = r.rotation();
|
||||||
ship_ang = ship_rot.angle();
|
ship_ang = ship_rot.angle();
|
||||||
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
ship_cnt = input.ct.get_ship(ship.get_data().get_content());
|
||||||
}
|
}
|
||||||
|
|
||||||
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
|
||||||
let r = &s.rigidbody;
|
let r = input.systemsim.get_rigid_body(ship.rigid_body).unwrap();
|
||||||
let pos = *r.translation();
|
let pos = *r.translation();
|
||||||
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
ship_pos = Point3::new(pos.x, pos.y, *current_z);
|
||||||
let ship_rot = r.rotation();
|
let ship_rot = r.rotation();
|
||||||
ship_ang = ship_rot.angle();
|
ship_ang = ship_rot.angle();
|
||||||
ship_cnt = input.ct.get_ship(s.ship.get_data().get_content());
|
ship_cnt = input.ct.get_ship(ship.get_data().get_content());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +85,7 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Push this object's instance
|
// Push this object's instance
|
||||||
let anim_state = s.ship.get_anim_state();
|
let anim_state = ship.get_anim_state();
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
texture_index: anim_state.texture_index(),
|
texture_index: anim_state.texture_index(),
|
||||||
texture_fade: anim_state.fade,
|
texture_fade: anim_state.fade,
|
||||||
|
@ -91,7 +93,7 @@ impl GPUState {
|
||||||
});
|
});
|
||||||
|
|
||||||
if {
|
if {
|
||||||
let is_flying = match s.ship.get_data().get_state() {
|
let is_flying = match ship.get_data().get_state() {
|
||||||
ShipState::Flying { .. }
|
ShipState::Flying { .. }
|
||||||
| ShipState::UnLanding { .. }
|
| ShipState::UnLanding { .. }
|
||||||
| ShipState::Landing { .. } => true,
|
| ShipState::Landing { .. } => true,
|
||||||
|
@ -99,7 +101,7 @@ impl GPUState {
|
||||||
};
|
};
|
||||||
is_flying
|
is_flying
|
||||||
} {
|
} {
|
||||||
for (engine_point, anim) in s.ship.iter_engine_anim() {
|
for (engine_point, anim) in ship.iter_engine_anim() {
|
||||||
self.state.queue.write_buffer(
|
self.state.queue.write_buffer(
|
||||||
&self.state.global_uniform.object_buffer,
|
&self.state.global_uniform.object_buffer,
|
||||||
ObjectData::SIZE * self.state.get_object_counter() as u64,
|
ObjectData::SIZE * self.state.get_object_counter() as u64,
|
||||||
|
@ -133,18 +135,18 @@ impl GPUState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn phys_push_projectiles(
|
pub(super) fn phys_push_projectile(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: &RenderInput,
|
input: &RenderInput,
|
||||||
// NE and SW corners of screen
|
// NE and SW corners of screen
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
screen_clip: (Point2<f32>, Point2<f32>),
|
||||||
) {
|
) {
|
||||||
for p in input.phys_img.iter_projectiles() {
|
for p in input.systemsim.iter_projectiles() {
|
||||||
let r = &p.rigidbody;
|
let r = input.systemsim.get_rigid_body(p.rigid_body).unwrap();
|
||||||
let proj_pos = *r.translation();
|
let proj_pos = *r.translation();
|
||||||
let proj_rot = r.rotation();
|
let proj_rot = r.rotation();
|
||||||
let proj_ang = proj_rot.angle();
|
let proj_ang = proj_rot.angle();
|
||||||
let proj_cnt = &p.projectile.content; // TODO: don't clone this?
|
let proj_cnt = &p.content; // TODO: don't clone this?
|
||||||
|
|
||||||
// Position adjusted for parallax
|
// Position adjusted for parallax
|
||||||
// TODO: adjust parallax for zoom?
|
// TODO: adjust parallax for zoom?
|
||||||
|
@ -177,14 +179,14 @@ impl GPUState {
|
||||||
ypos: proj_pos.y,
|
ypos: proj_pos.y,
|
||||||
zpos: 1.0,
|
zpos: 1.0,
|
||||||
angle: proj_ang,
|
angle: proj_ang,
|
||||||
size: 0f32.max(proj_cnt.size + p.projectile.size_rng),
|
size: 0f32.max(proj_cnt.size + p.size_rng),
|
||||||
parent: 0,
|
parent: 0,
|
||||||
is_child: 0,
|
is_child: 0,
|
||||||
_padding: Default::default(),
|
_padding: Default::default(),
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
||||||
let anim_state = p.projectile.get_anim_state();
|
let anim_state = p.get_anim_state();
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
self.state.push_object_buffer(ObjectInstance {
|
||||||
texture_index: anim_state.texture_index(),
|
texture_index: anim_state.texture_index(),
|
||||||
texture_fade: anim_state.fade,
|
texture_fade: anim_state.fade,
|
||||||
|
@ -249,68 +251,4 @@ impl GPUState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn phys_push_effects(
|
|
||||||
&mut self,
|
|
||||||
input: &RenderInput,
|
|
||||||
// NE and SW corners of screen
|
|
||||||
screen_clip: (Point2<f32>, Point2<f32>),
|
|
||||||
) {
|
|
||||||
for p in input.phys_img.iter_effects() {
|
|
||||||
let r = &p.rigidbody;
|
|
||||||
let pos = *r.translation();
|
|
||||||
let rot = r.rotation();
|
|
||||||
let ang = rot.angle();
|
|
||||||
|
|
||||||
// Position adjusted for parallax
|
|
||||||
// TODO: adjust parallax for zoom?
|
|
||||||
// 1.0 is z-coordinate, which is constant for projectiles
|
|
||||||
let adjusted_pos = (pos - input.camera_pos) / 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 = (p.effect.size / 1.0)
|
|
||||||
* input
|
|
||||||
.ct
|
|
||||||
.get_sprite(p.effect.anim.get_sprite())
|
|
||||||
.aspect
|
|
||||||
.max(1.0);
|
|
||||||
|
|
||||||
// Don't draw sprites that are off the screen
|
|
||||||
if adjusted_pos.x < screen_clip.0.x - m
|
|
||||||
|| adjusted_pos.y > screen_clip.0.y + m
|
|
||||||
|| adjusted_pos.x > screen_clip.1.x + m
|
|
||||||
|| adjusted_pos.y < screen_clip.1.y - m
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = self.state.get_object_counter();
|
|
||||||
// Write this object's location data
|
|
||||||
self.state.queue.write_buffer(
|
|
||||||
&self.state.global_uniform.object_buffer,
|
|
||||||
ObjectData::SIZE * idx as u64,
|
|
||||||
bytemuck::cast_slice(&[ObjectData {
|
|
||||||
xpos: pos.x,
|
|
||||||
ypos: pos.y,
|
|
||||||
zpos: 1.0,
|
|
||||||
angle: ang,
|
|
||||||
size: p.effect.size,
|
|
||||||
parent: 0,
|
|
||||||
is_child: 0,
|
|
||||||
_padding: Default::default(),
|
|
||||||
}]),
|
|
||||||
);
|
|
||||||
|
|
||||||
let anim_state = p.effect.anim.get_texture_idx();
|
|
||||||
self.state.push_object_buffer(ObjectInstance {
|
|
||||||
texture_index: anim_state.texture_index(),
|
|
||||||
texture_fade: anim_state.fade,
|
|
||||||
object_index: idx as u32,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
|
||||||
|
use galactica_util::constants::PARTICLE_SPRITE_INSTANCE_LIMIT;
|
||||||
use glyphon::Resolution;
|
use glyphon::Resolution;
|
||||||
use nalgebra::Point2;
|
use nalgebra::Point2;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use wgpu;
|
use wgpu;
|
||||||
|
|
||||||
use crate::{globaluniform::GlobalDataContent, vertexbuffer::consts::SPRITE_INDICES, RenderInput};
|
use crate::{
|
||||||
|
globaluniform::GlobalDataContent,
|
||||||
|
vertexbuffer::{consts::SPRITE_INDICES, types::ParticleInstance},
|
||||||
|
RenderInput,
|
||||||
|
};
|
||||||
|
|
||||||
impl<'a> super::GPUState {
|
impl<'a> super::GPUState {
|
||||||
/// Render routines while player is flying
|
/// Render routines while player is flying
|
||||||
|
@ -41,6 +46,25 @@ impl<'a> super::GPUState {
|
||||||
timestamp_writes: None,
|
timestamp_writes: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Write all new particles to GPU buffer
|
||||||
|
for i in input.particles.iter() {
|
||||||
|
let sprite = input.ct.get_sprite(i.sprite);
|
||||||
|
let texture_a = sprite.get_first_frame(); // ANIMATE
|
||||||
|
|
||||||
|
self.state.push_particle_buffer(ParticleInstance {
|
||||||
|
position: [i.pos.x, i.pos.y],
|
||||||
|
velocity: i.velocity.into(),
|
||||||
|
angle: i.angle,
|
||||||
|
angvel: i.angvel,
|
||||||
|
size: i.size,
|
||||||
|
texture_index: [texture_a, texture_a],
|
||||||
|
texture_fade: 1.0,
|
||||||
|
created: input.current_time,
|
||||||
|
expires: input.current_time + i.lifetime,
|
||||||
|
fade: i.fade,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Create sprite instances
|
// Create sprite instances
|
||||||
|
|
||||||
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
// Game coordinates (relative to camera) of ne and sw corners of screen.
|
||||||
|
@ -52,9 +76,8 @@ impl<'a> super::GPUState {
|
||||||
// The order inside ships and projectiles doesn't matter,
|
// The order inside ships and projectiles doesn't matter,
|
||||||
// but ships should always be under projectiles.
|
// but ships should always be under projectiles.
|
||||||
self.phys_push_system(&input, (clip_ne, clip_sw));
|
self.phys_push_system(&input, (clip_ne, clip_sw));
|
||||||
self.phys_push_ships(&input, (clip_ne, clip_sw));
|
self.phys_push_ship(&input, (clip_ne, clip_sw));
|
||||||
self.phys_push_projectiles(&input, (clip_ne, clip_sw));
|
self.phys_push_projectile(&input, (clip_ne, clip_sw));
|
||||||
self.phys_push_effects(&input, (clip_ne, clip_sw));
|
|
||||||
self.ui.draw(&input, &mut self.state);
|
self.ui.draw(&input, &mut self.state);
|
||||||
|
|
||||||
// These should match the indices in each shader,
|
// These should match the indices in each shader,
|
||||||
|
@ -86,6 +109,19 @@ impl<'a> super::GPUState {
|
||||||
0..self.state.get_object_counter(),
|
0..self.state.get_object_counter(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Particle pipeline
|
||||||
|
self.state
|
||||||
|
.vertex_buffers
|
||||||
|
.get_particle()
|
||||||
|
.set_in_pass(&mut render_pass);
|
||||||
|
render_pass.set_pipeline(&self.particle_pipeline);
|
||||||
|
render_pass.draw_indexed(
|
||||||
|
0..SPRITE_INDICES.len() as u32,
|
||||||
|
0,
|
||||||
|
0..PARTICLE_SPRITE_INSTANCE_LIMIT as _,
|
||||||
|
); */
|
||||||
|
|
||||||
// Ui pipeline
|
// Ui pipeline
|
||||||
self.state
|
self.state
|
||||||
.vertex_buffers
|
.vertex_buffers
|
||||||
|
@ -283,10 +319,9 @@ impl<'a> super::GPUState {
|
||||||
self.state.frame_reset();
|
self.state.frame_reset();
|
||||||
|
|
||||||
match input
|
match input
|
||||||
.phys_img
|
.systemsim
|
||||||
.get_ship(&PhysSimShipHandle(input.player.ship.unwrap()))
|
.get_ship(&PhysSimShipHandle(input.player.ship.unwrap()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.ship
|
|
||||||
.get_data()
|
.get_data()
|
||||||
.get_state()
|
.get_state()
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use galactica_content::{Content, SystemHandle};
|
use galactica_content::{Content, SystemHandle};
|
||||||
use galactica_playeragent::PlayerAgent;
|
use galactica_playeragent::PlayerAgent;
|
||||||
use galactica_system::phys::PhysImage;
|
use galactica_system::phys::{ParticleBuilder, PhysSim};
|
||||||
use galactica_util::timing::Timing;
|
use galactica_util::timing::Timing;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ pub struct RenderInput<'a> {
|
||||||
pub camera_zoom: f32,
|
pub camera_zoom: f32,
|
||||||
|
|
||||||
/// The world state to render
|
/// The world state to render
|
||||||
pub phys_img: &'a PhysImage,
|
pub systemsim: &'a PhysSim,
|
||||||
|
|
||||||
// TODO: handle overflow. is it a problem?
|
// TODO: handle overflow. is it a problem?
|
||||||
/// The current time, in seconds
|
/// The current time, in seconds
|
||||||
|
@ -28,6 +28,9 @@ pub struct RenderInput<'a> {
|
||||||
/// Game content
|
/// Game content
|
||||||
pub ct: &'a Content,
|
pub ct: &'a Content,
|
||||||
|
|
||||||
|
/// Particles to spawn during this frame
|
||||||
|
pub particles: &'a Vec<ParticleBuilder>,
|
||||||
|
|
||||||
/// Time we spent in each part of the game loop
|
/// Time we spent in each part of the game loop
|
||||||
pub timing: Timing,
|
pub timing: Timing,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_util::constants::{
|
use galactica_util::constants::{
|
||||||
OBJECT_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT,
|
OBJECT_SPRITE_INSTANCE_LIMIT, PARTICLE_SPRITE_INSTANCE_LIMIT, RADIALBAR_SPRITE_INSTANCE_LIMIT,
|
||||||
|
UI_SPRITE_INSTANCE_LIMIT,
|
||||||
};
|
};
|
||||||
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
@ -11,7 +12,10 @@ use crate::{
|
||||||
globaluniform::GlobalUniform,
|
globaluniform::GlobalUniform,
|
||||||
vertexbuffer::{
|
vertexbuffer::{
|
||||||
consts::{SPRITE_INDICES, SPRITE_VERTICES},
|
consts::{SPRITE_INDICES, SPRITE_VERTICES},
|
||||||
types::{ObjectInstance, RadialBarInstance, StarfieldInstance, TexturedVertex, UiInstance},
|
types::{
|
||||||
|
ObjectInstance, ParticleInstance, RadialBarInstance, StarfieldInstance, TexturedVertex,
|
||||||
|
UiInstance,
|
||||||
|
},
|
||||||
BufferObject, VertexBuffer,
|
BufferObject, VertexBuffer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -20,8 +24,13 @@ use crate::{
|
||||||
pub(crate) struct VertexBuffers {
|
pub(crate) struct VertexBuffers {
|
||||||
// Keeps track of length of each buffer
|
// Keeps track of length of each buffer
|
||||||
// Most of these are reset on each frame.
|
// Most of these are reset on each frame.
|
||||||
|
//
|
||||||
|
// The exception is particle_counter, which
|
||||||
|
// is never reset, and loops back to zero once
|
||||||
|
// it exceeds buffer length
|
||||||
object_counter: BufferAddress,
|
object_counter: BufferAddress,
|
||||||
ui_counter: BufferAddress,
|
ui_counter: BufferAddress,
|
||||||
|
particle_counter: BufferAddress,
|
||||||
radialbar_counter: BufferAddress,
|
radialbar_counter: BufferAddress,
|
||||||
starfield_counter: BufferAddress,
|
starfield_counter: BufferAddress,
|
||||||
starfield_limit: BufferAddress,
|
starfield_limit: BufferAddress,
|
||||||
|
@ -29,6 +38,7 @@ pub(crate) struct VertexBuffers {
|
||||||
object: Rc<VertexBuffer>,
|
object: Rc<VertexBuffer>,
|
||||||
starfield: Rc<VertexBuffer>,
|
starfield: Rc<VertexBuffer>,
|
||||||
ui: Rc<VertexBuffer>,
|
ui: Rc<VertexBuffer>,
|
||||||
|
particle: Rc<VertexBuffer>,
|
||||||
radialbar: Rc<VertexBuffer>,
|
radialbar: Rc<VertexBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +47,7 @@ impl<'a> VertexBuffers {
|
||||||
Self {
|
Self {
|
||||||
object_counter: 0,
|
object_counter: 0,
|
||||||
ui_counter: 0,
|
ui_counter: 0,
|
||||||
|
particle_counter: 0,
|
||||||
radialbar_counter: 0,
|
radialbar_counter: 0,
|
||||||
starfield_counter: 0,
|
starfield_counter: 0,
|
||||||
starfield_limit: ct.get_config().starfield_instance_limit,
|
starfield_limit: ct.get_config().starfield_instance_limit,
|
||||||
|
@ -65,6 +76,14 @@ impl<'a> VertexBuffers {
|
||||||
UI_SPRITE_INSTANCE_LIMIT,
|
UI_SPRITE_INSTANCE_LIMIT,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
particle: Rc::new(VertexBuffer::new::<TexturedVertex, ParticleInstance>(
|
||||||
|
"particle",
|
||||||
|
&device,
|
||||||
|
Some(SPRITE_VERTICES),
|
||||||
|
Some(SPRITE_INDICES),
|
||||||
|
PARTICLE_SPRITE_INSTANCE_LIMIT,
|
||||||
|
)),
|
||||||
|
|
||||||
radialbar: Rc::new(VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
|
radialbar: Rc::new(VertexBuffer::new::<TexturedVertex, RadialBarInstance>(
|
||||||
"radial bar",
|
"radial bar",
|
||||||
&device,
|
&device,
|
||||||
|
@ -83,6 +102,10 @@ impl<'a> VertexBuffers {
|
||||||
&self.object
|
&self.object
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_particle(&'a self) -> &'a Rc<VertexBuffer> {
|
||||||
|
&self.particle
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_radialbar(&'a self) -> &'a Rc<VertexBuffer> {
|
pub fn get_radialbar(&'a self) -> &'a Rc<VertexBuffer> {
|
||||||
&self.radialbar
|
&self.radialbar
|
||||||
}
|
}
|
||||||
|
@ -195,4 +218,20 @@ impl RenderState {
|
||||||
pub fn get_starfield_counter(&self) -> u32 {
|
pub fn get_starfield_counter(&self) -> u32 {
|
||||||
self.vertex_buffers.starfield_counter as u32
|
self.vertex_buffers.starfield_counter as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push_particle_buffer(&mut self, instance: ParticleInstance) {
|
||||||
|
self.queue.write_buffer(
|
||||||
|
&self.vertex_buffers.particle.instances,
|
||||||
|
ParticleInstance::SIZE * self.vertex_buffers.particle_counter,
|
||||||
|
bytemuck::cast_slice(&[instance]),
|
||||||
|
);
|
||||||
|
self.vertex_buffers.particle_counter += 1;
|
||||||
|
if self.vertex_buffers.particle_counter == PARTICLE_SPRITE_INSTANCE_LIMIT {
|
||||||
|
self.vertex_buffers.particle_counter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//pub fn get_particle_counter(&self) -> u32 {
|
||||||
|
// self.vertex_buffers.particle_counter as u32
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,8 +109,18 @@ impl TextureArray {
|
||||||
let mut image_locations = AtlasArray::zeroed();
|
let mut image_locations = AtlasArray::zeroed();
|
||||||
|
|
||||||
println!("sending to gpu");
|
println!("sending to gpu");
|
||||||
for image in ct.get_atlas().iter_images() {
|
for sprite in &ct.sprites {
|
||||||
image_locations.data[image.idx.get() as usize] = AtlasImageLocation {
|
for section in sprite.iter_sections() {
|
||||||
|
for idx in §ion.frames {
|
||||||
|
// Some atlas entries may be written twice here,
|
||||||
|
// but that's not really a problem. They're all the same!
|
||||||
|
//
|
||||||
|
// This happens rarely---only when two different sections
|
||||||
|
// use the same frame.
|
||||||
|
let idx = NonZeroU32::new(*idx);
|
||||||
|
if idx.is_some() {
|
||||||
|
let image = ct.get_image(idx.unwrap());
|
||||||
|
image_locations.data[idx.unwrap().get() as usize] = AtlasImageLocation {
|
||||||
xpos: image.x,
|
xpos: image.x,
|
||||||
ypos: image.y,
|
ypos: image.y,
|
||||||
width: image.w,
|
width: image.w,
|
||||||
|
@ -119,6 +129,9 @@ impl TextureArray {
|
||||||
_padding: Default::default(),
|
_padding: Default::default(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
|
|
@ -25,11 +25,10 @@ impl UiManager {
|
||||||
/// Draw all ui elements
|
/// Draw all ui elements
|
||||||
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
let ship_handle = input.player.ship.unwrap();
|
let ship_handle = input.player.ship.unwrap();
|
||||||
let ship = &input
|
let ship = input
|
||||||
.phys_img
|
.systemsim
|
||||||
.get_ship(&PhysSimShipHandle(ship_handle))
|
.get_ship(&PhysSimShipHandle(ship_handle))
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.ship;
|
|
||||||
|
|
||||||
self.fps.update(input, state);
|
self.fps.update(input, state);
|
||||||
|
|
||||||
|
|
|
@ -114,11 +114,10 @@ impl Planet {
|
||||||
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
pub fn draw(&mut self, input: &RenderInput, state: &mut RenderState) {
|
||||||
// Get required data
|
// Get required data
|
||||||
let ship_handle = input.player.ship.unwrap();
|
let ship_handle = input.player.ship.unwrap();
|
||||||
let ship_data = &input
|
let ship_data = input
|
||||||
.phys_img
|
.systemsim
|
||||||
.get_ship(&PhysSimShipHandle(ship_handle))
|
.get_ship(&PhysSimShipHandle(ship_handle))
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.ship;
|
|
||||||
let planet_handle = match ship_data.get_data().get_state() {
|
let planet_handle = match ship_data.get_data().get_state() {
|
||||||
ShipState::Landed { target } => *target,
|
ShipState::Landed { target } => *target,
|
||||||
_ => unreachable!("tried to draw planet interface while not landed!"),
|
_ => unreachable!("tried to draw planet interface while not landed!"),
|
||||||
|
|
|
@ -28,13 +28,13 @@ impl Radar {
|
||||||
// TODO: maybe a cleaner solution for last posititon?
|
// TODO: maybe a cleaner solution for last posititon?
|
||||||
// This is necessary because the player may be dead or landed
|
// This is necessary because the player may be dead or landed
|
||||||
let player_ship = input
|
let player_ship = input
|
||||||
.phys_img
|
.systemsim
|
||||||
.get_ship(&galactica_system::phys::PhysSimShipHandle(
|
.get_ship(&galactica_system::phys::PhysSimShipHandle(
|
||||||
input.player.ship.unwrap(),
|
input.player.ship.unwrap(),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match player_ship.ship.get_data().get_state() {
|
match player_ship.get_data().get_state() {
|
||||||
ShipState::Dead => {}
|
ShipState::Dead => {}
|
||||||
|
|
||||||
ShipState::Landed { target } => {
|
ShipState::Landed { target } => {
|
||||||
|
@ -46,7 +46,12 @@ impl Radar {
|
||||||
| ShipState::Landing { .. }
|
| ShipState::Landing { .. }
|
||||||
| ShipState::Flying { .. }
|
| ShipState::Flying { .. }
|
||||||
| ShipState::Collapsing { .. } => {
|
| ShipState::Collapsing { .. } => {
|
||||||
self.last_player_position = (*player_ship.rigidbody.translation()).into();
|
let player_body = input
|
||||||
|
.systemsim
|
||||||
|
.get_rigid_body(player_ship.rigid_body)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.last_player_position = (*player_body.translation()).into();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,9 +111,9 @@ impl Radar {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw ships
|
// Draw ships
|
||||||
for s in input.phys_img.iter_ships() {
|
for (s, r) in input.systemsim.iter_ship_body() {
|
||||||
let ship = input.ct.get_ship(s.ship.get_data().get_content());
|
let ship = input.ct.get_ship(s.get_data().get_content());
|
||||||
let (color, z_scale) = match s.ship.get_data().get_state() {
|
let (color, z_scale) = match s.get_data().get_state() {
|
||||||
ShipState::Dead | ShipState::Landed { .. } => {
|
ShipState::Dead | ShipState::Landed { .. } => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -124,16 +129,16 @@ impl Radar {
|
||||||
([0.2, 0.2, 0.2, 1.0], 1.0)
|
([0.2, 0.2, 0.2, 1.0], 1.0)
|
||||||
}
|
}
|
||||||
ShipState::Flying { .. } => {
|
ShipState::Flying { .. } => {
|
||||||
let c = input.ct.get_faction(s.ship.get_data().get_faction()).color;
|
let c = input.ct.get_faction(s.get_data().get_faction()).color;
|
||||||
([c[0], c[1], c[2], 1.0], 1.0)
|
([c[0], c[1], c[2], 1.0], 1.0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let size = (ship.size * input.ct.get_sprite(ship.sprite).aspect) * ship_scale * z_scale;
|
let size = (ship.size * input.ct.get_sprite(ship.sprite).aspect) * ship_scale * z_scale;
|
||||||
let p: Point2<f32> = {
|
let p: Point2<f32> = {
|
||||||
if s.ship.collider == input.player.ship.unwrap() {
|
if s.collider == input.player.ship.unwrap() {
|
||||||
self.last_player_position
|
self.last_player_position
|
||||||
} else {
|
} else {
|
||||||
(*s.rigidbody.translation()).into()
|
(*r.translation()).into()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let d = (p - self.last_player_position) / radar_range;
|
let d = (p - self.last_player_position) / radar_range;
|
||||||
|
@ -153,7 +158,7 @@ impl Radar {
|
||||||
state.push_ui_buffer(UiInstance {
|
state.push_ui_buffer(UiInstance {
|
||||||
anchor: PositionAnchor::NwC.to_int(),
|
anchor: PositionAnchor::NwC.to_int(),
|
||||||
position: position.into(),
|
position: position.into(),
|
||||||
angle: player_ship.rigidbody.rotation().angle(),
|
angle: r.rotation().angle(),
|
||||||
size,
|
size,
|
||||||
color,
|
color,
|
||||||
texture_index: [texture_a, texture_a],
|
texture_index: [texture_a, texture_a],
|
||||||
|
|
|
@ -20,42 +20,28 @@ impl Status {
|
||||||
let current_hull;
|
let current_hull;
|
||||||
let max_hull;
|
let max_hull;
|
||||||
let player_ship = input
|
let player_ship = input
|
||||||
.phys_img
|
.systemsim
|
||||||
.get_ship(&galactica_system::phys::PhysSimShipHandle(
|
.get_ship(&galactica_system::phys::PhysSimShipHandle(
|
||||||
input.player.ship.unwrap(),
|
input.player.ship.unwrap(),
|
||||||
))
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
match player_ship.ship.get_data().get_state() {
|
match player_ship.get_data().get_state() {
|
||||||
ShipState::Dead => {
|
ShipState::Dead => {
|
||||||
current_shields = 0.0;
|
current_shields = 0.0;
|
||||||
current_hull = 0.0;
|
current_hull = 0.0;
|
||||||
max_shields = player_ship
|
max_shields = player_ship.get_data().get_outfits().get_shield_strength();
|
||||||
.ship
|
max_hull = input.ct.get_ship(player_ship.get_data().get_content()).hull;
|
||||||
.get_data()
|
|
||||||
.get_outfits()
|
|
||||||
.get_shield_strength();
|
|
||||||
max_hull = input
|
|
||||||
.ct
|
|
||||||
.get_ship(player_ship.ship.get_data().get_content())
|
|
||||||
.hull;
|
|
||||||
}
|
}
|
||||||
ShipState::UnLanding { .. }
|
ShipState::UnLanding { .. }
|
||||||
| ShipState::Landing { .. }
|
| ShipState::Landing { .. }
|
||||||
| ShipState::Landed { .. }
|
| ShipState::Landed { .. }
|
||||||
| ShipState::Collapsing { .. }
|
| ShipState::Collapsing { .. }
|
||||||
| ShipState::Flying { .. } => {
|
| ShipState::Flying { .. } => {
|
||||||
current_shields = player_ship.ship.get_data().get_shields();
|
current_shields = player_ship.get_data().get_shields();
|
||||||
current_hull = player_ship.ship.get_data().get_hull();
|
current_hull = player_ship.get_data().get_hull();
|
||||||
max_shields = player_ship
|
max_shields = player_ship.get_data().get_outfits().get_shield_strength();
|
||||||
.ship
|
max_hull = input.ct.get_ship(player_ship.get_data().get_content()).hull;
|
||||||
.get_data()
|
|
||||||
.get_outfits()
|
|
||||||
.get_shield_strength();
|
|
||||||
max_hull = input
|
|
||||||
.ct
|
|
||||||
.get_ship(player_ship.ship.get_data().get_content())
|
|
||||||
.hull;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,6 +224,112 @@ impl BufferObject for UiInstance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct ParticleInstance {
|
||||||
|
/// World position of this particle
|
||||||
|
pub position: [f32; 2],
|
||||||
|
|
||||||
|
/// Velocity of this particle, in world coordinates
|
||||||
|
pub velocity: [f32; 2],
|
||||||
|
|
||||||
|
/// Angle of this particle, in radians
|
||||||
|
pub angle: f32,
|
||||||
|
|
||||||
|
/// Angular velocity of this particle, rad/sec
|
||||||
|
pub angvel: f32,
|
||||||
|
|
||||||
|
/// The height of this particle, in world units
|
||||||
|
pub size: f32,
|
||||||
|
|
||||||
|
/// The time, in seconds, at which this particle was created.
|
||||||
|
/// Time is kept by a variable in the global uniform.
|
||||||
|
pub created: f32,
|
||||||
|
|
||||||
|
/// The time, in seconds, at which this particle expires.
|
||||||
|
/// Time is kept by a variable in the global uniform.
|
||||||
|
pub expires: f32,
|
||||||
|
|
||||||
|
/// Fade this particle out over this many seconds as it expires
|
||||||
|
pub fade: f32,
|
||||||
|
|
||||||
|
/// What texture to use for this particle
|
||||||
|
pub texture_index: [u32; 2],
|
||||||
|
/// Fade parameter for texture index
|
||||||
|
pub texture_fade: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferObject for ParticleInstance {
|
||||||
|
fn layout() -> wgpu::VertexBufferLayout<'static> {
|
||||||
|
wgpu::VertexBufferLayout {
|
||||||
|
array_stride: Self::SIZE,
|
||||||
|
step_mode: wgpu::VertexStepMode::Instance,
|
||||||
|
attributes: &[
|
||||||
|
// Position
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: 0,
|
||||||
|
shader_location: 2,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
},
|
||||||
|
// Velocity
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 2]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 3,
|
||||||
|
format: wgpu::VertexFormat::Float32x2,
|
||||||
|
},
|
||||||
|
// Angle
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 4,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Angvel
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 5,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Size
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 6,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Created
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 7]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 7,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Expires
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 8]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 8,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Fade
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 9,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
// Texture
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 10,
|
||||||
|
format: wgpu::VertexFormat::Uint32x2,
|
||||||
|
},
|
||||||
|
// Texture fade
|
||||||
|
wgpu::VertexAttribute {
|
||||||
|
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
|
||||||
|
shader_location: 11,
|
||||||
|
format: wgpu::VertexFormat::Float32,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct RadialBarInstance {
|
pub struct RadialBarInstance {
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use galactica_util::{clockwise_angle, to_radians};
|
use galactica_util::{clockwise_angle, to_radians};
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
|
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle};
|
||||||
|
|
||||||
use crate::phys::{objects::ShipControls, PhysImage, PhysSimShipHandle, PhysStepResources};
|
use crate::phys::{
|
||||||
|
objects::{PhysSimShip, ShipControls},
|
||||||
|
PhysStepResources,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: no wobble
|
// TODO: no wobble
|
||||||
// TODO: slow down when near planet
|
// TODO: slow down when near planet
|
||||||
|
@ -10,11 +16,13 @@ use crate::phys::{objects::ShipControls, PhysImage, PhysSimShipHandle, PhysStepR
|
||||||
/// Land this ship on the given object
|
/// Land this ship on the given object
|
||||||
pub fn auto_landing(
|
pub fn auto_landing(
|
||||||
_res: &PhysStepResources,
|
_res: &PhysStepResources,
|
||||||
img: &PhysImage,
|
rigid_bodies: &RigidBodySet,
|
||||||
this_ship: PhysSimShipHandle,
|
ships: &HashMap<ColliderHandle, PhysSimShip>,
|
||||||
|
this_ship: ColliderHandle,
|
||||||
target_pos: Vector2<f32>,
|
target_pos: Vector2<f32>,
|
||||||
) -> Option<ShipControls> {
|
) -> Option<ShipControls> {
|
||||||
let rigid_body = &img.get_ship(&this_ship).unwrap().rigidbody;
|
let rigid_body_handle = ships.get(&this_ship).unwrap().rigid_body;
|
||||||
|
let rigid_body = rigid_bodies.get(rigid_body_handle).unwrap();
|
||||||
let my_pos = *rigid_body.translation();
|
let my_pos = *rigid_body.translation();
|
||||||
let my_rot = rigid_body.rotation() * Vector2::new(1.0, 0.0);
|
let my_rot = rigid_body.rotation() * Vector2::new(1.0, 0.0);
|
||||||
let my_vel = rigid_body.linvel();
|
let my_vel = rigid_body.linvel();
|
|
@ -1,15 +1,19 @@
|
||||||
//! Various implementations of [`crate::ShipBehavior`]
|
//! Various implementations of [`crate::ShipBehavior`]
|
||||||
|
|
||||||
|
pub(crate) mod autopilot;
|
||||||
mod null;
|
mod null;
|
||||||
mod point;
|
mod point;
|
||||||
|
|
||||||
use null::*;
|
use null::*;
|
||||||
use point::PointShipController;
|
use point::PointShipController;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle};
|
||||||
|
use std::{collections::HashMap, fmt::Debug};
|
||||||
|
|
||||||
use super::ShipControls;
|
use super::{
|
||||||
use crate::phys::{PhysImage, PhysSimShipHandle, PhysStepResources};
|
objects::{PhysSimShip, ShipControls},
|
||||||
|
PhysStepResources,
|
||||||
|
};
|
||||||
|
|
||||||
/// Represents a ship controller
|
/// Represents a ship controller
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -36,12 +40,13 @@ impl ShipController {
|
||||||
pub fn update_controls(
|
pub fn update_controls(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &PhysStepResources,
|
res: &PhysStepResources,
|
||||||
img: &PhysImage,
|
rigid_bodies: &RigidBodySet,
|
||||||
this_ship: PhysSimShipHandle,
|
ships: &HashMap<ColliderHandle, PhysSimShip>,
|
||||||
|
this_ship: ColliderHandle,
|
||||||
) -> Option<ShipControls> {
|
) -> Option<ShipControls> {
|
||||||
match self {
|
match self {
|
||||||
Self::Null(n) => n.update_controls(res, img, this_ship),
|
Self::Null(n) => n.update_controls(res, rigid_bodies, ships, this_ship),
|
||||||
Self::Point(p) => p.update_controls(res, img, this_ship),
|
Self::Point(p) => p.update_controls(res, rigid_bodies, ships, this_ship),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +61,8 @@ where
|
||||||
fn update_controls(
|
fn update_controls(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &PhysStepResources,
|
res: &PhysStepResources,
|
||||||
img: &PhysImage,
|
rigid_bodies: &RigidBodySet,
|
||||||
this_ship: PhysSimShipHandle,
|
ships: &HashMap<ColliderHandle, PhysSimShip>,
|
||||||
|
this_ship: ColliderHandle,
|
||||||
) -> Option<ShipControls>;
|
) -> Option<ShipControls>;
|
||||||
}
|
}
|
|
@ -1,5 +1,13 @@
|
||||||
use super::{ShipControllerStruct, ShipControls};
|
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle};
|
||||||
use crate::phys::{PhysImage, PhysSimShipHandle, PhysStepResources};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{
|
||||||
|
objects::{PhysSimShip, ShipControls},
|
||||||
|
PhysStepResources,
|
||||||
|
},
|
||||||
|
ShipControllerStruct,
|
||||||
|
};
|
||||||
|
|
||||||
/// The Null controller is assigned to objects that are static or not controlled by the computer.
|
/// The Null controller is assigned to objects that are static or not controlled by the computer.
|
||||||
/// Most notably, the player's ship has a Null controller.
|
/// Most notably, the player's ship has a Null controller.
|
||||||
|
@ -17,8 +25,9 @@ impl ShipControllerStruct for NullShipController {
|
||||||
fn update_controls(
|
fn update_controls(
|
||||||
&mut self,
|
&mut self,
|
||||||
_res: &PhysStepResources,
|
_res: &PhysStepResources,
|
||||||
_img: &PhysImage,
|
_rigid_bodies: &RigidBodySet,
|
||||||
_this_ship: PhysSimShipHandle,
|
_ships: &HashMap<ColliderHandle, PhysSimShip>,
|
||||||
|
_this_ship: ColliderHandle,
|
||||||
) -> Option<ShipControls> {
|
) -> Option<ShipControls> {
|
||||||
None
|
None
|
||||||
}
|
}
|
|
@ -1,15 +1,19 @@
|
||||||
use galactica_content::Relationship;
|
use galactica_content::Relationship;
|
||||||
use galactica_util::clockwise_angle;
|
use galactica_util::clockwise_angle;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
|
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::data::ShipState;
|
||||||
data::ShipState,
|
|
||||||
phys::{PhysImage, PhysSimShipHandle},
|
use super::{
|
||||||
|
super::{
|
||||||
|
objects::{PhysSimShip, ShipControls},
|
||||||
|
PhysStepResources,
|
||||||
|
},
|
||||||
|
ShipControllerStruct,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ShipControllerStruct, ShipControls};
|
|
||||||
use crate::phys::PhysStepResources;
|
|
||||||
|
|
||||||
/// "Point" ship controller.
|
/// "Point" ship controller.
|
||||||
/// Point and shoot towards the nearest enemy.
|
/// Point and shoot towards the nearest enemy.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -26,47 +30,43 @@ impl ShipControllerStruct for PointShipController {
|
||||||
fn update_controls(
|
fn update_controls(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &PhysStepResources,
|
res: &PhysStepResources,
|
||||||
img: &PhysImage,
|
rigid_bodies: &RigidBodySet,
|
||||||
this_ship: PhysSimShipHandle,
|
ships: &HashMap<ColliderHandle, PhysSimShip>,
|
||||||
|
this_ship: ColliderHandle,
|
||||||
) -> Option<ShipControls> {
|
) -> Option<ShipControls> {
|
||||||
let mut controls = ShipControls::new();
|
let mut controls = ShipControls::new();
|
||||||
|
|
||||||
let my_ship = match img.get_ship(&this_ship) {
|
let my_ship = ships.get(&this_ship).unwrap();
|
||||||
None => return None,
|
let this_rigidbody = rigid_bodies.get(my_ship.rigid_body).unwrap();
|
||||||
Some(s) => s,
|
|
||||||
};
|
|
||||||
|
|
||||||
let this_rigidbody = &my_ship.rigidbody;
|
|
||||||
let my_position = this_rigidbody.translation();
|
let my_position = this_rigidbody.translation();
|
||||||
let my_rotation = this_rigidbody.rotation();
|
let my_rotation = this_rigidbody.rotation();
|
||||||
let my_angvel = this_rigidbody.angvel();
|
let my_angvel = this_rigidbody.angvel();
|
||||||
let my_faction = res.ct.get_faction(my_ship.ship.data.get_faction());
|
let my_faction = res.ct.get_faction(my_ship.data.get_faction());
|
||||||
|
|
||||||
// Iterate all possible targets
|
// Iterate all possible targets
|
||||||
let mut hostile_ships = img.iter_ships().filter(
|
let mut hostile_ships = ships
|
||||||
|
.values()
|
||||||
|
.filter(
|
||||||
// Target only flying ships we're hostile towards
|
// Target only flying ships we're hostile towards
|
||||||
|s| match my_faction
|
|s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() {
|
||||||
.relationships
|
Relationship::Hostile => match s.data.get_state() {
|
||||||
.get(&s.ship.data.get_faction())
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
Relationship::Hostile => match s.ship.data.get_state() {
|
|
||||||
ShipState::Flying { .. } => true,
|
ShipState::Flying { .. } => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.map(|s| rigid_bodies.get(s.rigid_body).unwrap());
|
||||||
|
|
||||||
// Find the closest target
|
// Find the closest target
|
||||||
let mut closest_enemy_position = match hostile_ships.next() {
|
let mut closest_enemy_position = match hostile_ships.next() {
|
||||||
Some(s) => s.rigidbody.translation(),
|
Some(c) => c.translation(),
|
||||||
None => return Some(controls), // Do nothing if no targets are available
|
None => return Some(controls), // Do nothing if no targets are available
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut d = (my_position - closest_enemy_position).magnitude();
|
let mut d = (my_position - closest_enemy_position).magnitude();
|
||||||
for s in hostile_ships {
|
for r in hostile_ships {
|
||||||
let p = s.rigidbody.translation();
|
let p = r.translation();
|
||||||
let new_d = (my_position - p).magnitude();
|
let new_d = (my_position - p).magnitude();
|
||||||
if new_d < d {
|
if new_d < d {
|
||||||
d = new_d;
|
d = new_d;
|
|
@ -1,12 +1,13 @@
|
||||||
//! This module provides a physics-based simulation of one system.
|
//! This module provides a physics-based simulation of one system.
|
||||||
|
|
||||||
|
pub mod controller;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
pub mod physimage;
|
mod particlebuilder;
|
||||||
mod physsim;
|
|
||||||
mod physwrapper;
|
|
||||||
mod stepresources;
|
mod stepresources;
|
||||||
|
mod systemsim;
|
||||||
|
mod wrapper;
|
||||||
|
|
||||||
pub use physimage::*;
|
pub use particlebuilder::*;
|
||||||
pub use physsim::{PhysSim, PhysSimShipHandle};
|
|
||||||
pub use physwrapper::PhysWrapper;
|
|
||||||
pub use stepresources::*;
|
pub use stepresources::*;
|
||||||
|
pub use systemsim::{PhysSim, PhysSimShipHandle};
|
||||||
|
pub use wrapper::Wrapper;
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
use galactica_content::{CollapseEvent, Content, Ship};
|
use galactica_content::{CollapseEvent, Content, Ship};
|
||||||
use nalgebra::{Point2, Vector2};
|
use nalgebra::{Point2, Vector2};
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rand::{rngs::ThreadRng, Rng};
|
||||||
use rapier2d::{
|
use rapier2d::{dynamics::RigidBody, geometry::Collider};
|
||||||
dynamics::RigidBodyHandle,
|
|
||||||
geometry::{Collider, ColliderHandle},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use super::super::{ParticleBuilder, PhysStepResources};
|
||||||
data::ShipData,
|
use crate::data::ShipData;
|
||||||
phys::{objects::PhysEffect, physsim::NewObjects, PhysStepResources, PhysWrapper},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct ShipCollapseSequence {
|
pub(super) struct ShipCollapseSequence {
|
||||||
|
@ -54,14 +49,10 @@ impl ShipCollapseSequence {
|
||||||
pub(super) fn step(
|
pub(super) fn step(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &mut PhysStepResources,
|
res: &mut PhysStepResources,
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
new: &mut NewObjects,
|
|
||||||
ship_data: &ShipData,
|
ship_data: &ShipData,
|
||||||
rigid_body_handle: RigidBodyHandle,
|
rigid_body: &mut RigidBody,
|
||||||
collider_handle: ColliderHandle,
|
collider: &mut Collider,
|
||||||
) {
|
) {
|
||||||
let rigid_body = wrapper.get_rigid_body(rigid_body_handle).unwrap().clone();
|
|
||||||
let collider = wrapper.get_collider(collider_handle).unwrap().clone();
|
|
||||||
let ship_content = res.ct.get_ship(ship_data.get_content());
|
let ship_content = res.ct.get_ship(ship_data.get_content());
|
||||||
let ship_pos = rigid_body.translation();
|
let ship_pos = rigid_body.translation();
|
||||||
let ship_rot = rigid_body.rotation();
|
let ship_rot = rigid_body.rotation();
|
||||||
|
@ -80,26 +71,26 @@ impl ShipCollapseSequence {
|
||||||
// ^^ Don't miss events scheduled at the very start of the sequence!
|
// ^^ Don't miss events scheduled at the very start of the sequence!
|
||||||
{
|
{
|
||||||
for spawner in &event.effects {
|
for spawner in &event.effects {
|
||||||
|
let effect = res.ct.get_effect(spawner.effect);
|
||||||
|
|
||||||
for _ in 0..spawner.count as usize {
|
for _ in 0..spawner.count as usize {
|
||||||
let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
|
let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
|
||||||
Vector2::new(pos.x, pos.y)
|
Vector2::new(pos.x, pos.y)
|
||||||
} else {
|
} else {
|
||||||
self.random_in_ship(res.ct, ship_content, &collider)
|
self.random_in_ship(res.ct, ship_content, collider)
|
||||||
};
|
};
|
||||||
let pos = ship_pos + (ship_rot * pos);
|
let pos = ship_pos + (ship_rot * pos);
|
||||||
|
|
||||||
let velocity =
|
let velocity =
|
||||||
rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
|
rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
|
||||||
|
|
||||||
new.effects.push(PhysEffect::new(
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
res.ct,
|
effect,
|
||||||
wrapper,
|
pos.into(),
|
||||||
spawner.effect,
|
|
||||||
pos,
|
|
||||||
0.0,
|
0.0,
|
||||||
velocity,
|
velocity,
|
||||||
Vector2::new(0.0, 0.0),
|
Vector2::new(0.0, 0.0),
|
||||||
));
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,6 +100,8 @@ impl ShipCollapseSequence {
|
||||||
|
|
||||||
// Create collapse effects
|
// Create collapse effects
|
||||||
for spawner in &ship_content.collapse.effects {
|
for spawner in &ship_content.collapse.effects {
|
||||||
|
let effect = res.ct.get_effect(spawner.effect);
|
||||||
|
|
||||||
// Probability of adding a particle this frame.
|
// Probability of adding a particle this frame.
|
||||||
// The area of this function over [0, 1] should be 1.
|
// The area of this function over [0, 1] should be 1.
|
||||||
let pdf = |x: f32| {
|
let pdf = |x: f32| {
|
||||||
|
@ -128,22 +121,23 @@ impl ShipCollapseSequence {
|
||||||
let pos = if let Some(pos) = spawner.pos {
|
let pos = if let Some(pos) = spawner.pos {
|
||||||
Vector2::new(pos.x, pos.y)
|
Vector2::new(pos.x, pos.y)
|
||||||
} else {
|
} else {
|
||||||
self.random_in_ship(res.ct, ship_content, &collider)
|
self.random_in_ship(res.ct, ship_content, collider)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Position, adjusted for ship rotation
|
// Position, adjusted for ship rotation
|
||||||
let pos = ship_pos + (ship_rot * pos);
|
let pos = ship_pos + (ship_rot * pos);
|
||||||
let vel = rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
|
let vel = rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
|
||||||
|
|
||||||
new.effects.push(PhysEffect::new(
|
res.particles.push(ParticleBuilder {
|
||||||
res.ct,
|
sprite: effect.sprite,
|
||||||
wrapper,
|
pos: pos.into(),
|
||||||
spawner.effect,
|
velocity: vel,
|
||||||
pos,
|
angle: 0.0,
|
||||||
0.0,
|
angvel: 0.0,
|
||||||
vel,
|
lifetime: effect.lifetime,
|
||||||
Vector2::new(0.0, 0.0),
|
size: effect.size,
|
||||||
));
|
fade: 0.0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
//! This module contains game objects that may interact with the physics engine.
|
//! This module contains game objects that may interact with the physics engine.
|
||||||
|
mod collapse;
|
||||||
mod effect;
|
|
||||||
mod projectile;
|
mod projectile;
|
||||||
mod ship;
|
mod ship;
|
||||||
|
|
||||||
pub use effect::*;
|
|
||||||
pub use projectile::PhysProjectile;
|
pub use projectile::PhysProjectile;
|
||||||
pub use ship::*;
|
pub use ship::{PhysSimShip, ShipControls};
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton};
|
use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton};
|
||||||
use nalgebra::Vector2;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
||||||
|
|
||||||
use crate::phys::{physsim::NewObjects, PhysStepResources, PhysWrapper};
|
|
||||||
|
|
||||||
use super::PhysEffect;
|
|
||||||
|
|
||||||
/// A single projectile in this simulation
|
/// A single projectile in this simulation
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PhysProjectile {
|
pub struct PhysProjectile {
|
||||||
|
@ -17,7 +12,7 @@ pub struct PhysProjectile {
|
||||||
anim: SpriteAutomaton,
|
anim: SpriteAutomaton,
|
||||||
|
|
||||||
/// The remaining lifetime of this projectile, in seconds
|
/// The remaining lifetime of this projectile, in seconds
|
||||||
lifetime: f32,
|
pub lifetime: f32,
|
||||||
|
|
||||||
/// The faction this projectile belongs to
|
/// The faction this projectile belongs to
|
||||||
pub faction: FactionHandle,
|
pub faction: FactionHandle,
|
||||||
|
@ -30,10 +25,6 @@ pub struct PhysProjectile {
|
||||||
|
|
||||||
/// This projectile's size variation
|
/// This projectile's size variation
|
||||||
pub size_rng: f32,
|
pub size_rng: f32,
|
||||||
|
|
||||||
/// If true, this projectile has been destroyed
|
|
||||||
/// and is waiting to be deallocated.
|
|
||||||
is_destroyed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhysProjectile {
|
impl PhysProjectile {
|
||||||
|
@ -56,88 +47,18 @@ impl PhysProjectile {
|
||||||
lifetime,
|
lifetime,
|
||||||
faction,
|
faction,
|
||||||
size_rng: rng.gen_range(-size_rng..=size_rng),
|
size_rng: rng.gen_range(-size_rng..=size_rng),
|
||||||
is_destroyed: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process this projectile's state after `t` seconds
|
/// Process this projectile's state after `t` seconds
|
||||||
pub(in crate::phys) fn step(
|
pub fn tick(&mut self, ct: &Content, t: f32) {
|
||||||
&mut self,
|
self.lifetime -= t;
|
||||||
res: &PhysStepResources,
|
self.anim.step(ct, t)
|
||||||
new: &mut NewObjects,
|
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
) {
|
|
||||||
self.lifetime -= res.t;
|
|
||||||
self.anim.step(res.ct, res.t);
|
|
||||||
|
|
||||||
if self.lifetime <= 0.0 {
|
|
||||||
self.destroy(res, new, wrapper, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy this projectile without creating an expire effect
|
/// Has this projectile expired?
|
||||||
pub(in crate::phys) fn destroy_silent(
|
pub fn is_expired(&self) -> bool {
|
||||||
&mut self,
|
return self.lifetime < 0.0;
|
||||||
res: &PhysStepResources,
|
|
||||||
new: &mut NewObjects,
|
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
) {
|
|
||||||
self.destroy(res, new, wrapper, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroy this projectile
|
|
||||||
fn destroy(
|
|
||||||
&mut self,
|
|
||||||
res: &PhysStepResources,
|
|
||||||
new: &mut NewObjects,
|
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
expire: bool,
|
|
||||||
) {
|
|
||||||
if self.is_destroyed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rigid_body = wrapper.remove_rigid_body(self.rigid_body).unwrap();
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
if expire {
|
|
||||||
match &self.content.expire_effect {
|
|
||||||
None => {}
|
|
||||||
Some(handle) => {
|
|
||||||
let x = res.ct.get_effect(*handle);
|
|
||||||
let pos = *rigid_body.translation();
|
|
||||||
let vel = rigid_body.velocity_at_point(rigid_body.center_of_mass());
|
|
||||||
let angle = rigid_body.rotation().angle();
|
|
||||||
|
|
||||||
let velocity = {
|
|
||||||
let a = rng
|
|
||||||
.gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng);
|
|
||||||
|
|
||||||
let velocity = (x.velocity_scale_parent + a) * vel;
|
|
||||||
|
|
||||||
velocity
|
|
||||||
};
|
|
||||||
|
|
||||||
new.effects.push(PhysEffect::new(
|
|
||||||
res.ct,
|
|
||||||
wrapper,
|
|
||||||
*handle,
|
|
||||||
pos,
|
|
||||||
angle,
|
|
||||||
velocity,
|
|
||||||
Vector2::new(0.0, 0.0),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.is_destroyed = true;
|
|
||||||
wrapper.remove_rigid_body(self.rigid_body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Should this effect be deleted?
|
|
||||||
pub(in crate::phys) fn should_remove(&self) -> bool {
|
|
||||||
self.is_destroyed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,316 @@
|
||||||
|
use galactica_content::{
|
||||||
|
AnimationState, Content, EnginePoint, FactionHandle, OutfitHandle, ShipHandle, SpriteAutomaton,
|
||||||
|
};
|
||||||
|
use nalgebra::{point, vector, Rotation2, Vector2};
|
||||||
|
use rand::Rng;
|
||||||
|
use rapier2d::{
|
||||||
|
dynamics::{RigidBody, RigidBodyHandle},
|
||||||
|
geometry::{Collider, ColliderHandle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::data::{ShipData, ShipPersonality, ShipState};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
super::{ParticleBuilder, PhysStepResources},
|
||||||
|
collapse::ShipCollapseSequence,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A ship's controls
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ShipControls {
|
||||||
|
/// True if turning left
|
||||||
|
pub left: bool,
|
||||||
|
|
||||||
|
/// True if turning right
|
||||||
|
pub right: bool,
|
||||||
|
|
||||||
|
/// True if foward thrust
|
||||||
|
pub thrust: bool,
|
||||||
|
|
||||||
|
/// True if firing guns
|
||||||
|
pub guns: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipControls {
|
||||||
|
/// Create a new, empty ShipControls
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ShipControls {
|
||||||
|
left: false,
|
||||||
|
right: false,
|
||||||
|
thrust: false,
|
||||||
|
guns: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A ship instance in the physics system
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PhysSimShip {
|
||||||
|
/// This ship's physics handle
|
||||||
|
pub rigid_body: RigidBodyHandle,
|
||||||
|
|
||||||
|
/// This ship's collider
|
||||||
|
pub collider: ColliderHandle,
|
||||||
|
|
||||||
|
/// This ship's game data
|
||||||
|
pub(crate) data: ShipData,
|
||||||
|
|
||||||
|
/// This ship's sprite animation state
|
||||||
|
anim: SpriteAutomaton,
|
||||||
|
|
||||||
|
/// Animation state for each of this ship's engines
|
||||||
|
engine_anim: Vec<(EnginePoint, SpriteAutomaton)>,
|
||||||
|
|
||||||
|
/// This ship's controls
|
||||||
|
pub(crate) controls: ShipControls,
|
||||||
|
|
||||||
|
/// This ship's controls during the last frame
|
||||||
|
last_controls: ShipControls,
|
||||||
|
|
||||||
|
/// This ship's collapse sequence
|
||||||
|
collapse_sequence: Option<ShipCollapseSequence>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhysSimShip {
|
||||||
|
/// Make a new ship
|
||||||
|
pub(crate) fn new(
|
||||||
|
ct: &Content,
|
||||||
|
handle: ShipHandle,
|
||||||
|
personality: ShipPersonality,
|
||||||
|
faction: FactionHandle,
|
||||||
|
rigid_body: RigidBodyHandle,
|
||||||
|
collider: ColliderHandle,
|
||||||
|
) -> Self {
|
||||||
|
let ship_ct = ct.get_ship(handle);
|
||||||
|
PhysSimShip {
|
||||||
|
anim: SpriteAutomaton::new(ct, ship_ct.sprite),
|
||||||
|
rigid_body,
|
||||||
|
collider,
|
||||||
|
data: ShipData::new(ct, handle, faction, personality),
|
||||||
|
engine_anim: Vec::new(),
|
||||||
|
controls: ShipControls::new(),
|
||||||
|
last_controls: ShipControls::new(),
|
||||||
|
collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Step this ship's state by t seconds
|
||||||
|
pub fn step(
|
||||||
|
&mut self,
|
||||||
|
res: &mut PhysStepResources,
|
||||||
|
rigid_body: &mut RigidBody,
|
||||||
|
collider: &mut Collider,
|
||||||
|
) {
|
||||||
|
self.data.step(res.t);
|
||||||
|
self.anim.step(res.ct, res.t);
|
||||||
|
|
||||||
|
for (_, e) in &mut self.engine_anim {
|
||||||
|
e.step(res.ct, res.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.controls.thrust && self.last_controls.thrust {
|
||||||
|
let flare = self.get_flare(res.ct);
|
||||||
|
if flare.is_some() {
|
||||||
|
let flare_outfit = flare.unwrap();
|
||||||
|
let flare = res.ct.get_outfit(flare_outfit);
|
||||||
|
if flare.engine_flare_on_stop.is_some() {
|
||||||
|
for (_, e) in &mut self.engine_anim {
|
||||||
|
e.jump_to(res.ct, flare.engine_flare_on_stop.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else if self.controls.thrust && !self.last_controls.thrust {
|
||||||
|
let flare = self.get_flare(res.ct);
|
||||||
|
if flare.is_some() {
|
||||||
|
let flare_outfit = flare.unwrap();
|
||||||
|
let flare = res.ct.get_outfit(flare_outfit);
|
||||||
|
if flare.engine_flare_on_start.is_some() {
|
||||||
|
for (_, e) in &mut self.engine_anim {
|
||||||
|
e.jump_to(res.ct, flare.engine_flare_on_start.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.data.get_state() {
|
||||||
|
ShipState::Collapsing { .. } => {
|
||||||
|
// Borrow checker hack, so we may pass self.data
|
||||||
|
// to the collapse sequence
|
||||||
|
let mut seq = self.collapse_sequence.take().unwrap();
|
||||||
|
seq.step(res, &self.data, rigid_body, collider);
|
||||||
|
self.collapse_sequence = Some(seq);
|
||||||
|
|
||||||
|
if self.collapse_sequence.as_ref().unwrap().is_done() {
|
||||||
|
self.data.finish_collapse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShipState::Flying { .. } => {
|
||||||
|
self.step_physics(res, rigid_body, collider);
|
||||||
|
self.step_effects(res, rigid_body, collider);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::UnLanding { .. } | ShipState::Landing { .. } => {
|
||||||
|
self.step_physics(res, rigid_body, collider);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Dead | ShipState::Landed { .. } => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_controls = self.controls.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update this frame's physics
|
||||||
|
fn step_physics(
|
||||||
|
&mut self,
|
||||||
|
res: &mut PhysStepResources,
|
||||||
|
rigid_body: &mut RigidBody,
|
||||||
|
_collider: &mut Collider,
|
||||||
|
) {
|
||||||
|
let ship_rot = rigid_body.rotation();
|
||||||
|
let engine_force = ship_rot * (Vector2::new(1.0, 0.0) * res.t);
|
||||||
|
|
||||||
|
if self.controls.thrust {
|
||||||
|
rigid_body.apply_impulse(
|
||||||
|
vector![engine_force.x, engine_force.y]
|
||||||
|
* self.data.get_outfits().get_engine_thrust(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.controls.right {
|
||||||
|
rigid_body.apply_torque_impulse(
|
||||||
|
self.data.get_outfits().get_steer_power() * -100.0 * res.t,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.controls.left {
|
||||||
|
rigid_body.apply_torque_impulse(
|
||||||
|
self.data.get_outfits().get_steer_power() * 100.0 * res.t,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn this frame's particles
|
||||||
|
fn step_effects(
|
||||||
|
&mut self,
|
||||||
|
res: &mut PhysStepResources,
|
||||||
|
rigid_body: &mut RigidBody,
|
||||||
|
collider: &mut Collider,
|
||||||
|
) {
|
||||||
|
let ship_content = res.ct.get_ship(self.data.get_content());
|
||||||
|
let ship_pos = rigid_body.translation();
|
||||||
|
let ship_rot = rigid_body.rotation();
|
||||||
|
let ship_ang = ship_rot.angle();
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
if self.data.get_hull() <= ship_content.damage.hull {
|
||||||
|
for e in &ship_content.damage.effects {
|
||||||
|
if rng.gen_range(0.0..=1.0) <= res.t / e.frequency {
|
||||||
|
let effect = res.ct.get_effect(e.effect);
|
||||||
|
|
||||||
|
let pos = if let Some(pos) = e.pos {
|
||||||
|
Vector2::new(pos.x, pos.y)
|
||||||
|
} else {
|
||||||
|
// Pick a random point inside this ship's collider
|
||||||
|
let mut y = 0.0;
|
||||||
|
let mut x = 0.0;
|
||||||
|
let mut a = false;
|
||||||
|
while !a {
|
||||||
|
x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
|
||||||
|
y = rng.gen_range(-1.0..=1.0)
|
||||||
|
* ship_content.size * res.ct.get_sprite(ship_content.sprite).aspect
|
||||||
|
/ 2.0;
|
||||||
|
a = collider.shape().contains_local_point(&point![x, y]);
|
||||||
|
}
|
||||||
|
Vector2::new(x, y)
|
||||||
|
};
|
||||||
|
|
||||||
|
let pos = ship_pos + (Rotation2::new(ship_ang) * pos);
|
||||||
|
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
|
||||||
|
|
||||||
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
|
effect,
|
||||||
|
pos.into(),
|
||||||
|
0.0,
|
||||||
|
velocity,
|
||||||
|
Vector2::new(0.0, 0.0),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public mutable
|
||||||
|
impl PhysSimShip {
|
||||||
|
fn get_flare(&mut self, ct: &Content) -> Option<OutfitHandle> {
|
||||||
|
// TODO: better way to pick flare sprite
|
||||||
|
for (h, _) in self.data.get_outfits().iter_outfits() {
|
||||||
|
let c = ct.get_outfit(*h);
|
||||||
|
if c.engine_flare_sprite.is_some() {
|
||||||
|
return Some(*h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Re-create this ship's engine flare animations
|
||||||
|
/// Should be called whenever we change outfits
|
||||||
|
fn update_flares(&mut self, ct: &Content) {
|
||||||
|
let flare_outfit = self.get_flare(ct);
|
||||||
|
if flare_outfit.is_none() {
|
||||||
|
self.engine_anim = Vec::new();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let flare = ct
|
||||||
|
.get_outfit(flare_outfit.unwrap())
|
||||||
|
.engine_flare_sprite
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.engine_anim = ct
|
||||||
|
.get_ship(self.data.get_content())
|
||||||
|
.engines
|
||||||
|
.iter()
|
||||||
|
.map(|e| (e.clone(), SpriteAutomaton::new(ct, flare)))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add one outfit to this ship
|
||||||
|
pub fn add_outfit(&mut self, ct: &Content, o: OutfitHandle) {
|
||||||
|
self.data.add_outfit(ct.get_outfit(o));
|
||||||
|
self.update_flares(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add many outfits to this ship
|
||||||
|
pub fn add_outfits(&mut self, ct: &Content, outfits: impl IntoIterator<Item = OutfitHandle>) {
|
||||||
|
for o in outfits {
|
||||||
|
self.data.add_outfit(ct.get_outfit(o));
|
||||||
|
}
|
||||||
|
self.update_flares(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Public immutable
|
||||||
|
impl PhysSimShip {
|
||||||
|
/// Get this ship's control state
|
||||||
|
pub fn get_controls(&self) -> &ShipControls {
|
||||||
|
&self.controls
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's engine animations
|
||||||
|
pub fn iter_engine_anim(&self) -> impl Iterator<Item = &(EnginePoint, SpriteAutomaton)> {
|
||||||
|
self.engine_anim.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's animation state
|
||||||
|
pub fn get_anim_state(&self) -> AnimationState {
|
||||||
|
self.anim.get_texture_idx()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's game data struct
|
||||||
|
pub fn get_data(&self) -> &ShipData {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
/// A ship's controls
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ShipControls {
|
|
||||||
/// True if turning left
|
|
||||||
pub left: bool,
|
|
||||||
|
|
||||||
/// True if turning right
|
|
||||||
pub right: bool,
|
|
||||||
|
|
||||||
/// True if foward thrust
|
|
||||||
pub thrust: bool,
|
|
||||||
|
|
||||||
/// True if firing guns
|
|
||||||
pub guns: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipControls {
|
|
||||||
/// Create a new, empty ShipControls
|
|
||||||
pub fn new() -> Self {
|
|
||||||
ShipControls {
|
|
||||||
left: false,
|
|
||||||
right: false,
|
|
||||||
thrust: false,
|
|
||||||
guns: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
mod autopilot;
|
|
||||||
mod collapse;
|
|
||||||
mod controller;
|
|
||||||
mod controls;
|
|
||||||
mod ship;
|
|
||||||
|
|
||||||
pub use controls::ShipControls;
|
|
||||||
pub use ship::PhysShip;
|
|
|
@ -1,543 +0,0 @@
|
||||||
use galactica_content::{
|
|
||||||
AnimationState, Content, EnginePoint, FactionHandle, GunPoint, OutfitHandle,
|
|
||||||
ProjectileCollider, ShipHandle, SpriteAutomaton,
|
|
||||||
};
|
|
||||||
use nalgebra::{vector, Point2, Rotation2, Vector2};
|
|
||||||
use rand::Rng;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{RigidBodyBuilder, RigidBodyHandle},
|
|
||||||
geometry::{ColliderBuilder, ColliderHandle, Group, InteractionGroups},
|
|
||||||
pipeline::ActiveEvents,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{autopilot, collapse::ShipCollapseSequence, controller::ShipController, ShipControls};
|
|
||||||
use crate::{
|
|
||||||
data::{ShipAutoPilot, ShipData, ShipPersonality, ShipState},
|
|
||||||
phys::{
|
|
||||||
objects::{PhysEffect, PhysProjectile},
|
|
||||||
physsim::NewObjects,
|
|
||||||
PhysImage, PhysSimShipHandle, PhysStepResources, PhysWrapper,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A ship instance in the physics system
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PhysShip {
|
|
||||||
/// This ship's physics handle
|
|
||||||
pub rigid_body: RigidBodyHandle,
|
|
||||||
|
|
||||||
/// This ship's collider
|
|
||||||
pub collider: ColliderHandle,
|
|
||||||
|
|
||||||
/// This ship's game data
|
|
||||||
pub(crate) data: ShipData,
|
|
||||||
|
|
||||||
/// This ship's sprite animation state
|
|
||||||
anim: SpriteAutomaton,
|
|
||||||
|
|
||||||
/// Animation state for each of this ship's engines
|
|
||||||
engine_anim: Vec<(EnginePoint, SpriteAutomaton)>,
|
|
||||||
|
|
||||||
/// This ship's controls
|
|
||||||
pub(crate) controls: ShipControls,
|
|
||||||
|
|
||||||
/// This ship's controls during the last frame
|
|
||||||
last_controls: ShipControls,
|
|
||||||
|
|
||||||
/// This ship's collapse sequence
|
|
||||||
collapse_sequence: Option<ShipCollapseSequence>,
|
|
||||||
|
|
||||||
/// This ship's controller
|
|
||||||
controller: ShipController,
|
|
||||||
|
|
||||||
/// If true, this ship's collider has been destroyed,
|
|
||||||
/// and this ship is waiting to be destroyed.
|
|
||||||
///
|
|
||||||
/// Note that this is NOT "in-game" destroyed,
|
|
||||||
/// but rather "internal game logic" destroyed.
|
|
||||||
/// In-game destroyed corresponds to the "Dead" state.
|
|
||||||
is_destroyed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysShip {
|
|
||||||
/// Make a new ship
|
|
||||||
pub(crate) fn new(
|
|
||||||
ct: &Content,
|
|
||||||
handle: ShipHandle,
|
|
||||||
personality: ShipPersonality,
|
|
||||||
faction: FactionHandle,
|
|
||||||
rigid_body: RigidBodyHandle,
|
|
||||||
collider: ColliderHandle,
|
|
||||||
) -> Self {
|
|
||||||
let ship_ct = ct.get_ship(handle);
|
|
||||||
PhysShip {
|
|
||||||
anim: SpriteAutomaton::new(ct, ship_ct.sprite),
|
|
||||||
rigid_body,
|
|
||||||
collider,
|
|
||||||
data: ShipData::new(ct, handle, faction, personality),
|
|
||||||
engine_anim: Vec::new(),
|
|
||||||
controls: ShipControls::new(),
|
|
||||||
last_controls: ShipControls::new(),
|
|
||||||
collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)),
|
|
||||||
is_destroyed: false,
|
|
||||||
controller: match personality {
|
|
||||||
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
|
|
||||||
ShipPersonality::Point => ShipController::new_point(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Step this ship's state by t seconds
|
|
||||||
pub(in crate::phys) fn step(
|
|
||||||
&mut self,
|
|
||||||
res: &mut PhysStepResources,
|
|
||||||
img: &PhysImage,
|
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
new: &mut NewObjects,
|
|
||||||
) {
|
|
||||||
if self.is_destroyed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.data.step(res.t);
|
|
||||||
self.anim.step(res.ct, res.t);
|
|
||||||
|
|
||||||
for (_, e) in &mut self.engine_anim {
|
|
||||||
e.step(res.ct, res.t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flare animations
|
|
||||||
if !self.controls.thrust && self.last_controls.thrust {
|
|
||||||
let flare = self.get_flare(res.ct);
|
|
||||||
if flare.is_some() {
|
|
||||||
let flare_outfit = flare.unwrap();
|
|
||||||
let flare = res.ct.get_outfit(flare_outfit);
|
|
||||||
if flare.engine_flare_on_stop.is_some() {
|
|
||||||
for (_, e) in &mut self.engine_anim {
|
|
||||||
e.jump_to(res.ct, flare.engine_flare_on_stop.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} else if self.controls.thrust && !self.last_controls.thrust {
|
|
||||||
let flare = self.get_flare(res.ct);
|
|
||||||
if flare.is_some() {
|
|
||||||
let flare_outfit = flare.unwrap();
|
|
||||||
let flare = res.ct.get_outfit(flare_outfit);
|
|
||||||
if flare.engine_flare_on_start.is_some() {
|
|
||||||
for (_, e) in &mut self.engine_anim {
|
|
||||||
e.jump_to(res.ct, flare.engine_flare_on_start.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.data.get_state() {
|
|
||||||
ShipState::Collapsing { .. } => {
|
|
||||||
// Borrow checker hack, so we may pass self.data
|
|
||||||
// to the collapse sequence
|
|
||||||
let mut seq = self.collapse_sequence.take().unwrap();
|
|
||||||
seq.step(
|
|
||||||
res,
|
|
||||||
wrapper,
|
|
||||||
new,
|
|
||||||
&self.data,
|
|
||||||
self.rigid_body,
|
|
||||||
self.collider,
|
|
||||||
);
|
|
||||||
self.collapse_sequence = Some(seq);
|
|
||||||
|
|
||||||
if self.collapse_sequence.as_ref().unwrap().is_done() {
|
|
||||||
self.data.finish_collapse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ShipState::Flying { autopilot } => {
|
|
||||||
// Compute new controls
|
|
||||||
let controls = match autopilot {
|
|
||||||
ShipAutoPilot::Landing { target } => {
|
|
||||||
let target_obj = res.ct.get_system_object(*target);
|
|
||||||
let controls = autopilot::auto_landing(
|
|
||||||
res,
|
|
||||||
img,
|
|
||||||
PhysSimShipHandle(self.collider),
|
|
||||||
Vector2::new(target_obj.pos.x, target_obj.pos.y),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to land the ship.
|
|
||||||
// True if success, false if failure.
|
|
||||||
// Failure implies no state changes.
|
|
||||||
let landed = 'landed: {
|
|
||||||
let r = wrapper.get_rigid_body(self.rigid_body).unwrap();
|
|
||||||
|
|
||||||
let t_pos = Vector2::new(target_obj.pos.x, target_obj.pos.y);
|
|
||||||
let s_pos = Vector2::new(
|
|
||||||
r.position().translation.x,
|
|
||||||
r.position().translation.y,
|
|
||||||
);
|
|
||||||
|
|
||||||
// TODO: deactivate collider when landing.
|
|
||||||
// Can't just set_active(false), since we still need that collider's mass.
|
|
||||||
|
|
||||||
// We're in land range...
|
|
||||||
if (t_pos - s_pos).magnitude() > target_obj.size / 2.0 {
|
|
||||||
break 'landed false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// And we'll stay in land range long enough.
|
|
||||||
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0))
|
|
||||||
.magnitude() > target_obj.size / 2.0
|
|
||||||
{
|
|
||||||
break 'landed false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let collider = wrapper.get_collider_mut(self.collider).unwrap();
|
|
||||||
collider.set_collision_groups(InteractionGroups::new(
|
|
||||||
Group::GROUP_1,
|
|
||||||
Group::empty(),
|
|
||||||
));
|
|
||||||
self.data.start_land_on(*target);
|
|
||||||
break 'landed true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if landed {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
controls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ShipAutoPilot::None => {
|
|
||||||
self.controller
|
|
||||||
.update_controls(res, img, PhysSimShipHandle(self.collider))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(controls) = controls {
|
|
||||||
self.controls = controls;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.step_physics(res, wrapper);
|
|
||||||
self.step_effects(res, wrapper, new);
|
|
||||||
|
|
||||||
// If we're firing, try to fire each gun
|
|
||||||
if self.controls.guns {
|
|
||||||
// TODO: don't allocate here. This is a hack to satisfy the borrow checker,
|
|
||||||
// convert this to a refcell or do the replace dance.
|
|
||||||
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = self
|
|
||||||
.data
|
|
||||||
.get_outfits()
|
|
||||||
.iter_gun_points()
|
|
||||||
.map(|(p, o)| (p.clone(), o.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for (gun_point, outfit) in pairs {
|
|
||||||
if self.data.fire_gun(res.ct, &gun_point) {
|
|
||||||
let outfit = outfit.unwrap();
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
let rigid_body = wrapper.get_rigid_body(self.rigid_body).unwrap();
|
|
||||||
let ship_pos = rigid_body.translation();
|
|
||||||
let ship_rot = rigid_body.rotation();
|
|
||||||
let ship_ang = ship_rot.angle();
|
|
||||||
let ship_vel =
|
|
||||||
rigid_body.velocity_at_point(rigid_body.center_of_mass());
|
|
||||||
|
|
||||||
let pos = ship_pos + (ship_rot * gun_point.pos);
|
|
||||||
|
|
||||||
let outfit = res.ct.get_outfit(outfit);
|
|
||||||
let outfit = outfit.gun.as_ref().unwrap();
|
|
||||||
|
|
||||||
let spread = rng.gen_range(
|
|
||||||
-outfit.projectile.angle_rng..=outfit.projectile.angle_rng,
|
|
||||||
);
|
|
||||||
let vel = ship_vel
|
|
||||||
+ (Rotation2::new(ship_ang + spread)
|
|
||||||
* Vector2::new(
|
|
||||||
outfit.projectile.speed
|
|
||||||
+ rng.gen_range(
|
|
||||||
-outfit.projectile.speed_rng
|
|
||||||
..=outfit.projectile.speed_rng,
|
|
||||||
),
|
|
||||||
0.0,
|
|
||||||
));
|
|
||||||
|
|
||||||
let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
|
|
||||||
.translation(pos)
|
|
||||||
.rotation(ship_ang)
|
|
||||||
.linvel(vel)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let mut collider = match &outfit.projectile.collider {
|
|
||||||
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
|
||||||
.sensor(true)
|
|
||||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
|
||||||
.build(),
|
|
||||||
};
|
|
||||||
|
|
||||||
collider.set_collision_groups(InteractionGroups::new(
|
|
||||||
Group::GROUP_2,
|
|
||||||
Group::GROUP_1,
|
|
||||||
));
|
|
||||||
|
|
||||||
let rigid_body = wrapper.insert_rigid_body(rigid_body);
|
|
||||||
let collider = wrapper.insert_collider(collider, rigid_body);
|
|
||||||
|
|
||||||
new.projectiles.push(PhysProjectile::new(
|
|
||||||
res.ct,
|
|
||||||
outfit.projectile.clone(),
|
|
||||||
rigid_body,
|
|
||||||
self.data.get_faction(),
|
|
||||||
collider,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::UnLanding {
|
|
||||||
to_position,
|
|
||||||
current_z,
|
|
||||||
from,
|
|
||||||
} => {
|
|
||||||
let from_obj = res.ct.get_system_object(*from);
|
|
||||||
|
|
||||||
let controls = autopilot::auto_landing(
|
|
||||||
&res,
|
|
||||||
img,
|
|
||||||
PhysSimShipHandle(self.collider),
|
|
||||||
Vector2::new(to_position.translation.x, to_position.translation.y),
|
|
||||||
);
|
|
||||||
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
|
||||||
let max_d = (Vector2::new(from_obj.pos.x, from_obj.pos.y)
|
|
||||||
- Vector2::new(to_position.translation.x, to_position.translation.y))
|
|
||||||
.magnitude();
|
|
||||||
let now_d = (r.translation()
|
|
||||||
- Vector2::new(to_position.translation.x, to_position.translation.y))
|
|
||||||
.magnitude();
|
|
||||||
let f = now_d / max_d;
|
|
||||||
|
|
||||||
let current_z = *current_z;
|
|
||||||
let zdist = 1.0 - from_obj.pos.z;
|
|
||||||
|
|
||||||
if current_z <= 1.0 {
|
|
||||||
// Finish unlanding ship
|
|
||||||
self.data.finish_unland_to();
|
|
||||||
wrapper
|
|
||||||
.get_collider_mut(self.collider)
|
|
||||||
.unwrap()
|
|
||||||
.set_collision_groups(InteractionGroups::new(
|
|
||||||
Group::GROUP_1,
|
|
||||||
Group::GROUP_1 | Group::GROUP_2,
|
|
||||||
));
|
|
||||||
} else if current_z <= 1.5 {
|
|
||||||
self.data
|
|
||||||
.set_unlanding_z(1f32.max(current_z - (0.5 * res.t) / 0.5));
|
|
||||||
self.controls = ShipControls::new();
|
|
||||||
} else {
|
|
||||||
self.data.set_unlanding_z(1.0 - zdist * f);
|
|
||||||
|
|
||||||
if let Some(controls) = controls {
|
|
||||||
self.controls = controls;
|
|
||||||
}
|
|
||||||
self.step_physics(res, wrapper);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::Landing { target, current_z } => {
|
|
||||||
let target_obj = res.ct.get_system_object(*target);
|
|
||||||
let controls = autopilot::auto_landing(
|
|
||||||
&res,
|
|
||||||
img,
|
|
||||||
PhysSimShipHandle(self.collider),
|
|
||||||
Vector2::new(target_obj.pos.x, target_obj.pos.y),
|
|
||||||
);
|
|
||||||
|
|
||||||
let current_z = *current_z;
|
|
||||||
let zdist = target_obj.pos.z - 1.0;
|
|
||||||
|
|
||||||
if current_z >= target_obj.pos.z {
|
|
||||||
// Finish landing ship
|
|
||||||
self.data.finish_land_on();
|
|
||||||
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
|
||||||
r.set_enabled(false);
|
|
||||||
r.set_angvel(0.0, false);
|
|
||||||
r.set_linvel(nalgebra::Vector2::new(0.0, 0.0), false);
|
|
||||||
} else {
|
|
||||||
self.data.set_landing_z(current_z + zdist * res.t / 2.0);
|
|
||||||
|
|
||||||
if let Some(controls) = controls {
|
|
||||||
self.controls = controls;
|
|
||||||
}
|
|
||||||
self.step_physics(res, wrapper);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::Landed { .. } => {}
|
|
||||||
|
|
||||||
ShipState::Dead => {
|
|
||||||
wrapper.remove_rigid_body(self.rigid_body);
|
|
||||||
self.is_destroyed = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.last_controls = self.controls.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Should this ship's data be removed?
|
|
||||||
pub fn should_remove(&self) -> bool {
|
|
||||||
self.is_destroyed
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update this frame's physics
|
|
||||||
fn step_physics(&mut self, res: &mut PhysStepResources, wrapper: &mut PhysWrapper) {
|
|
||||||
let rigid_body = &mut wrapper.get_rigid_body_mut(self.rigid_body).unwrap();
|
|
||||||
let ship_rot = rigid_body.rotation();
|
|
||||||
let engine_force = ship_rot * (Vector2::new(1.0, 0.0) * res.t);
|
|
||||||
|
|
||||||
if self.controls.thrust {
|
|
||||||
rigid_body.apply_impulse(
|
|
||||||
vector![engine_force.x, engine_force.y]
|
|
||||||
* self.data.get_outfits().get_engine_thrust(),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.controls.right {
|
|
||||||
rigid_body.apply_torque_impulse(
|
|
||||||
self.data.get_outfits().get_steer_power() * -100.0 * res.t,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.controls.left {
|
|
||||||
rigid_body.apply_torque_impulse(
|
|
||||||
self.data.get_outfits().get_steer_power() * 100.0 * res.t,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spawn this frame's particles
|
|
||||||
fn step_effects(
|
|
||||||
&mut self,
|
|
||||||
res: &mut PhysStepResources,
|
|
||||||
wrapper: &mut PhysWrapper,
|
|
||||||
new: &mut NewObjects,
|
|
||||||
) {
|
|
||||||
let rigid_body = wrapper.get_rigid_body(self.rigid_body).unwrap().clone();
|
|
||||||
let collider = wrapper.get_collider(self.collider).unwrap().clone();
|
|
||||||
|
|
||||||
let ship_content = res.ct.get_ship(self.data.get_content());
|
|
||||||
let ship_pos = rigid_body.translation();
|
|
||||||
let ship_rot = rigid_body.rotation();
|
|
||||||
let ship_ang = ship_rot.angle();
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
|
|
||||||
if self.data.get_hull() <= ship_content.damage.hull {
|
|
||||||
for e in &ship_content.damage.effects {
|
|
||||||
if rng.gen_range(0.0..=1.0) <= res.t / e.frequency {
|
|
||||||
let pos = if let Some(pos) = e.pos {
|
|
||||||
Vector2::new(pos.x, pos.y)
|
|
||||||
} else {
|
|
||||||
// Pick a random point inside this ship's collider
|
|
||||||
let mut y = 0.0;
|
|
||||||
let mut x = 0.0;
|
|
||||||
let mut a = false;
|
|
||||||
while !a {
|
|
||||||
x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
|
|
||||||
y = rng.gen_range(-1.0..=1.0)
|
|
||||||
* ship_content.size * res.ct.get_sprite(ship_content.sprite).aspect
|
|
||||||
/ 2.0;
|
|
||||||
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
|
||||||
}
|
|
||||||
Vector2::new(x, y)
|
|
||||||
};
|
|
||||||
|
|
||||||
let pos = ship_pos + (Rotation2::new(ship_ang) * pos);
|
|
||||||
let velocity = rigid_body.velocity_at_point(&Point2::new(pos.x, pos.y));
|
|
||||||
|
|
||||||
new.effects.push(PhysEffect::new(
|
|
||||||
res.ct,
|
|
||||||
wrapper,
|
|
||||||
e.effect,
|
|
||||||
pos.into(),
|
|
||||||
0.0,
|
|
||||||
velocity,
|
|
||||||
Vector2::new(0.0, 0.0),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Public mutable
|
|
||||||
impl PhysShip {
|
|
||||||
fn get_flare(&mut self, ct: &Content) -> Option<OutfitHandle> {
|
|
||||||
// TODO: better way to pick flare sprite
|
|
||||||
for (h, _) in self.data.get_outfits().iter_outfits() {
|
|
||||||
let c = ct.get_outfit(*h);
|
|
||||||
if c.engine_flare_sprite.is_some() {
|
|
||||||
return Some(*h);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Re-create this ship's engine flare animations
|
|
||||||
/// Should be called whenever we change outfits
|
|
||||||
fn update_flares(&mut self, ct: &Content) {
|
|
||||||
let flare_outfit = self.get_flare(ct);
|
|
||||||
if flare_outfit.is_none() {
|
|
||||||
self.engine_anim = Vec::new();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let flare = ct
|
|
||||||
.get_outfit(flare_outfit.unwrap())
|
|
||||||
.engine_flare_sprite
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.engine_anim = ct
|
|
||||||
.get_ship(self.data.get_content())
|
|
||||||
.engines
|
|
||||||
.iter()
|
|
||||||
.map(|e| (e.clone(), SpriteAutomaton::new(ct, flare)))
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add one outfit to this ship
|
|
||||||
pub fn add_outfit(&mut self, ct: &Content, o: OutfitHandle) {
|
|
||||||
self.data.add_outfit(ct.get_outfit(o));
|
|
||||||
self.update_flares(ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add many outfits to this ship
|
|
||||||
pub fn add_outfits(&mut self, ct: &Content, outfits: impl IntoIterator<Item = OutfitHandle>) {
|
|
||||||
for o in outfits {
|
|
||||||
self.data.add_outfit(ct.get_outfit(o));
|
|
||||||
}
|
|
||||||
self.update_flares(ct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Public immutable
|
|
||||||
impl PhysShip {
|
|
||||||
/// Get this ship's control state
|
|
||||||
pub fn get_controls(&self) -> &ShipControls {
|
|
||||||
&self.controls
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this ship's engine animations
|
|
||||||
pub fn iter_engine_anim(&self) -> impl Iterator<Item = &(EnginePoint, SpriteAutomaton)> {
|
|
||||||
self.engine_anim.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this ship's animation state
|
|
||||||
pub fn get_anim_state(&self) -> AnimationState {
|
|
||||||
self.anim.get_texture_idx()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get this ship's game data struct
|
|
||||||
pub fn get_data(&self) -> &ShipData {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +1,45 @@
|
||||||
use galactica_content::{Content, EffectHandle, SpriteAutomaton};
|
use galactica_content::{Effect, SpriteHandle};
|
||||||
use nalgebra::{Rotation2, Vector2};
|
use nalgebra::{Point2, Rotation2, Vector2};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rapier2d::dynamics::{RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
|
|
||||||
|
|
||||||
use crate::phys::{PhysStepResources, PhysWrapper};
|
|
||||||
|
|
||||||
/// Instructions to create a new particle
|
/// Instructions to create a new particle
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct PhysEffect {
|
pub struct ParticleBuilder {
|
||||||
/// The sprite to use for this particle
|
/// The sprite to use for this particle
|
||||||
pub anim: SpriteAutomaton,
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
|
/// This object's center, in world coordinates.
|
||||||
|
pub pos: Point2<f32>,
|
||||||
|
|
||||||
/// This particle's velocity, in world coordinates
|
/// This particle's velocity, in world coordinates
|
||||||
pub rigid_body: RigidBodyHandle,
|
pub velocity: Vector2<f32>,
|
||||||
|
|
||||||
|
/// This particle's angle, in radians
|
||||||
|
pub angle: f32,
|
||||||
|
|
||||||
|
/// This particle's angular velocity (rad/sec)
|
||||||
|
pub angvel: f32,
|
||||||
|
|
||||||
/// This particle's lifetime, in seconds
|
/// This particle's lifetime, in seconds
|
||||||
lifetime: f32,
|
pub lifetime: f32,
|
||||||
|
|
||||||
/// The size of this particle,
|
/// The size of this particle,
|
||||||
/// given as height in world units.
|
/// given as height in world units.
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
|
||||||
/// Fade this particle over this many seconds as it expires
|
/// Fade this particle out over this many seconds as it expires
|
||||||
pub fade: f32,
|
pub fade: f32,
|
||||||
|
|
||||||
/// If true, this effect has been destroyed,
|
|
||||||
/// and needs to be removed from the game.
|
|
||||||
is_destroyed: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhysEffect {
|
impl ParticleBuilder {
|
||||||
/// Create a new particle inside `Wrapper`
|
/// Create a ParticleBuilder from an Effect
|
||||||
pub fn new(
|
pub fn from_content(
|
||||||
ct: &Content,
|
effect: &Effect,
|
||||||
wrapper: &mut PhysWrapper,
|
pos: Point2<f32>,
|
||||||
effect: EffectHandle,
|
|
||||||
pos: Vector2<f32>,
|
|
||||||
parent_angle: f32,
|
parent_angle: f32,
|
||||||
parent_velocity: Vector2<f32>,
|
parent_velocity: Vector2<f32>,
|
||||||
target_velocity: Vector2<f32>,
|
target_velocity: Vector2<f32>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let effect = ct.get_effect(effect);
|
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
let velocity = {
|
let velocity = {
|
||||||
|
@ -66,17 +65,13 @@ impl PhysEffect {
|
||||||
parent_angle + effect.angle + rng.gen_range(-effect.angle_rng..=effect.angle_rng)
|
parent_angle + effect.angle + rng.gen_range(-effect.angle_rng..=effect.angle_rng)
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("{:?}", effect.angvel_rng);
|
ParticleBuilder {
|
||||||
|
sprite: effect.sprite,
|
||||||
|
pos,
|
||||||
|
velocity,
|
||||||
|
|
||||||
let rb = RigidBodyBuilder::new(RigidBodyType::KinematicVelocityBased)
|
angle,
|
||||||
.position(pos.into())
|
angvel,
|
||||||
.rotation(angle)
|
|
||||||
.angvel(angvel)
|
|
||||||
.linvel(velocity);
|
|
||||||
|
|
||||||
PhysEffect {
|
|
||||||
anim: SpriteAutomaton::new(ct, effect.sprite),
|
|
||||||
rigid_body: wrapper.insert_rigid_body(rb.build()),
|
|
||||||
|
|
||||||
lifetime: 0f32
|
lifetime: 0f32
|
||||||
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
|
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
|
||||||
|
@ -85,28 +80,6 @@ impl PhysEffect {
|
||||||
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_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)),
|
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
|
||||||
|
|
||||||
is_destroyed: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Step this effect's state by `t` seconds
|
|
||||||
pub fn step(&mut self, res: &PhysStepResources, wrapper: &mut PhysWrapper) {
|
|
||||||
if self.is_destroyed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.anim.step(res.ct, res.t);
|
|
||||||
self.lifetime -= res.t;
|
|
||||||
|
|
||||||
if self.lifetime <= 0.0 {
|
|
||||||
wrapper.remove_rigid_body(self.rigid_body);
|
|
||||||
self.is_destroyed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Should this effect be deleted?
|
|
||||||
pub fn is_destroyed(&self) -> bool {
|
|
||||||
self.is_destroyed
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,95 +0,0 @@
|
||||||
//! Provides a snapshot of one frame of a physics simulation
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
objects::{PhysEffect, PhysProjectile, PhysShip},
|
|
||||||
PhysSimShipHandle,
|
|
||||||
};
|
|
||||||
use rapier2d::dynamics::RigidBody;
|
|
||||||
|
|
||||||
/// A snapshot of one frame of a physics simulation
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PhysImage {
|
|
||||||
/// The ships in this frame
|
|
||||||
pub(crate) ships: Vec<PhysShipImage>,
|
|
||||||
|
|
||||||
/// Map ship handles to indices in ships
|
|
||||||
pub(crate) ship_map: HashMap<PhysSimShipHandle, usize>,
|
|
||||||
|
|
||||||
/// The projectiles in this frame
|
|
||||||
pub(crate) projectiles: Vec<PhysProjectileImage>,
|
|
||||||
|
|
||||||
/// The effects in this frame
|
|
||||||
pub(crate) effects: Vec<PhysEffectImage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysImage {
|
|
||||||
/// Create an empty simulation image
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
ships: Vec::new(),
|
|
||||||
projectiles: Vec::new(),
|
|
||||||
ship_map: HashMap::new(),
|
|
||||||
effects: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all buffers in this image
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.ship_map.clear();
|
|
||||||
self.projectiles.clear();
|
|
||||||
self.effects.clear();
|
|
||||||
self.ships.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate ships in this image
|
|
||||||
pub fn iter_ships(&self) -> impl Iterator<Item = &PhysShipImage> {
|
|
||||||
self.ships.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a ship by its handle
|
|
||||||
pub fn get_ship(&self, handle: &PhysSimShipHandle) -> Option<&PhysShipImage> {
|
|
||||||
self.ship_map.get(handle).map(|x| &self.ships[*x])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate projectiles in this image
|
|
||||||
pub fn iter_projectiles(&self) -> impl Iterator<Item = &PhysProjectileImage> {
|
|
||||||
self.projectiles.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate effects in this image
|
|
||||||
pub fn iter_effects(&self) -> impl Iterator<Item = &PhysEffectImage> {
|
|
||||||
self.effects.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A snapshot of a ship's state
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PhysShipImage {
|
|
||||||
/// The ship's data
|
|
||||||
pub ship: PhysShip,
|
|
||||||
|
|
||||||
/// The ship's rigidbody
|
|
||||||
pub rigidbody: RigidBody,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a snapshot of a projectile's state
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PhysProjectileImage {
|
|
||||||
/// The projectile's data
|
|
||||||
pub projectile: PhysProjectile,
|
|
||||||
|
|
||||||
/// The projectile's rigidbody
|
|
||||||
pub rigidbody: RigidBody,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a snapshot of a projectile's state
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PhysEffectImage {
|
|
||||||
/// The effect's data
|
|
||||||
pub effect: PhysEffect,
|
|
||||||
|
|
||||||
/// The effect's rigidbody
|
|
||||||
pub rigidbody: RigidBody,
|
|
||||||
}
|
|
|
@ -1,372 +0,0 @@
|
||||||
use galactica_content::Relationship;
|
|
||||||
use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle};
|
|
||||||
use galactica_playeragent::PlayerAgent;
|
|
||||||
use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
|
|
||||||
use rand::Rng;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::RigidBodyBuilder,
|
|
||||||
geometry::{ColliderHandle, Group, InteractionGroups},
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::PhysEffectImage;
|
|
||||||
use super::{
|
|
||||||
objects::{PhysEffect, PhysProjectile, PhysShip},
|
|
||||||
PhysImage, PhysProjectileImage, PhysShipImage, PhysStepResources, PhysWrapper,
|
|
||||||
};
|
|
||||||
use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
|
|
||||||
|
|
||||||
// TODO: replace with a more generic handle
|
|
||||||
/// A handle for a ship in this simulation
|
|
||||||
/// This lets other crates reference ships
|
|
||||||
/// without including `rapier2d`.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct PhysSimShipHandle(pub ColliderHandle);
|
|
||||||
|
|
||||||
pub(super) struct NewObjects {
|
|
||||||
pub projectiles: Vec<PhysProjectile>,
|
|
||||||
pub effects: Vec<PhysEffect>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of new objects to create this frame
|
|
||||||
impl NewObjects {
|
|
||||||
/// Create an empty NewObjects
|
|
||||||
/// This should only be called once in each PhysSim
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
projectiles: Vec::new(),
|
|
||||||
effects: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all lists in this struct.
|
|
||||||
/// Call after saving all new objects.
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.projectiles.clear();
|
|
||||||
self.effects.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Manages the physics state of one system
|
|
||||||
pub struct PhysSim {
|
|
||||||
/// The system this sim is attached to
|
|
||||||
_system: SystemHandle,
|
|
||||||
|
|
||||||
wrapper: PhysWrapper,
|
|
||||||
|
|
||||||
new: NewObjects,
|
|
||||||
|
|
||||||
effects: Vec<PhysEffect>,
|
|
||||||
projectiles: HashMap<ColliderHandle, PhysProjectile>,
|
|
||||||
ships: HashMap<ColliderHandle, PhysShip>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private methods
|
|
||||||
impl PhysSim {
|
|
||||||
pub(super) fn start_unland_ship(&mut self, ct: &Content, collider: ColliderHandle) {
|
|
||||||
let ship = self.ships.get_mut(&collider).unwrap();
|
|
||||||
let obj = ship.data.get_state().landed_on().unwrap();
|
|
||||||
let obj = ct.get_system_object(obj);
|
|
||||||
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let radius = rng.gen_range(500.0..=1500.0);
|
|
||||||
let angle = rng.gen_range(0.0..std::f32::consts::TAU);
|
|
||||||
let target_offset = Rotation2::new(angle) * Vector2::new(radius, 0.0);
|
|
||||||
let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset;
|
|
||||||
let target_pos = Isometry2::new(target_trans, angle);
|
|
||||||
|
|
||||||
ship.data.start_unland_to(ct, target_pos);
|
|
||||||
|
|
||||||
let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap();
|
|
||||||
r.set_enabled(true);
|
|
||||||
r.set_position(
|
|
||||||
Isometry2::new(Vector2::new(obj.pos.x, obj.pos.y), angle),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn collide_projectile_ship(
|
|
||||||
&mut self,
|
|
||||||
res: &mut PhysStepResources,
|
|
||||||
projectile_h: ColliderHandle,
|
|
||||||
ship_h: ColliderHandle,
|
|
||||||
) {
|
|
||||||
let projectile = self.projectiles.get_mut(&projectile_h);
|
|
||||||
let ship = self.ships.get_mut(&ship_h);
|
|
||||||
if projectile.is_none() || ship.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let projectile = projectile.unwrap();
|
|
||||||
let ship = ship.unwrap();
|
|
||||||
|
|
||||||
let f = res.ct.get_faction(projectile.faction);
|
|
||||||
let r = f.relationships.get(&ship.data.get_faction()).unwrap();
|
|
||||||
let destory_projectile = match r {
|
|
||||||
Relationship::Hostile => match ship.data.get_state() {
|
|
||||||
ShipState::Flying { .. } => {
|
|
||||||
ship.data.apply_damage(projectile.content.damage);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
ShipState::Collapsing { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if destory_projectile {
|
|
||||||
let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap();
|
|
||||||
let v =
|
|
||||||
pr.velocity_at_point(pr.center_of_mass()).normalize() * projectile.content.force;
|
|
||||||
let pos = *pr.translation();
|
|
||||||
let _ = pr;
|
|
||||||
|
|
||||||
let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap();
|
|
||||||
r.apply_impulse_at_point(Vector2::new(v.x, v.y), Point2::new(pos.x, pos.y), true);
|
|
||||||
|
|
||||||
// Borrow again, we can only have one at a time
|
|
||||||
let pr = self.wrapper.get_rigid_body(projectile.rigid_body).unwrap();
|
|
||||||
let pos = *pr.translation();
|
|
||||||
let angle = pr.rotation().angle();
|
|
||||||
|
|
||||||
match &projectile.content.impact_effect {
|
|
||||||
None => {}
|
|
||||||
Some(x) => {
|
|
||||||
let r = ship.rigid_body;
|
|
||||||
let sr = self.wrapper.get_rigid_body(r).unwrap();
|
|
||||||
let parent_velocity = pr.velocity_at_point(pr.center_of_mass());
|
|
||||||
let target_velocity =
|
|
||||||
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
|
||||||
|
|
||||||
self.effects.push(PhysEffect::new(
|
|
||||||
res.ct,
|
|
||||||
&mut self.wrapper,
|
|
||||||
*x,
|
|
||||||
pos,
|
|
||||||
angle,
|
|
||||||
parent_velocity,
|
|
||||||
target_velocity,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
projectile.destroy_silent(res, &mut self.new, &mut self.wrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods
|
|
||||||
impl PhysSim {
|
|
||||||
/// Create a new physics system
|
|
||||||
pub fn new(_ct: &Content, system: SystemHandle) -> Self {
|
|
||||||
Self {
|
|
||||||
_system: system,
|
|
||||||
wrapper: PhysWrapper::new(),
|
|
||||||
|
|
||||||
new: NewObjects::new(),
|
|
||||||
effects: Vec::new(),
|
|
||||||
projectiles: HashMap::new(),
|
|
||||||
ships: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a ship to this physics system
|
|
||||||
pub fn add_ship(
|
|
||||||
&mut self,
|
|
||||||
ct: &Content,
|
|
||||||
handle: ShipHandle,
|
|
||||||
faction: FactionHandle,
|
|
||||||
personality: ShipPersonality,
|
|
||||||
position: Point2<f32>,
|
|
||||||
) -> PhysSimShipHandle {
|
|
||||||
let ship_content = ct.get_ship(handle);
|
|
||||||
let mut cl = ship_content.collider.0.clone();
|
|
||||||
// TODO: additonal ship mass from outfits and cargo
|
|
||||||
|
|
||||||
let rb = RigidBodyBuilder::dynamic()
|
|
||||||
.angular_damping(ship_content.angular_drag)
|
|
||||||
.linear_damping(ship_content.linear_drag)
|
|
||||||
.translation(Vector2::new(position.x, position.y))
|
|
||||||
.can_sleep(false);
|
|
||||||
|
|
||||||
cl.set_collision_groups(InteractionGroups::new(
|
|
||||||
Group::GROUP_1,
|
|
||||||
Group::GROUP_1 | Group::GROUP_2,
|
|
||||||
));
|
|
||||||
|
|
||||||
let ridid_body = self.wrapper.insert_rigid_body(rb.build());
|
|
||||||
let collider = self.wrapper.insert_collider(cl, ridid_body);
|
|
||||||
|
|
||||||
self.ships.insert(
|
|
||||||
collider,
|
|
||||||
PhysShip::new(ct, handle, personality, faction, ridid_body, collider),
|
|
||||||
);
|
|
||||||
|
|
||||||
return PhysSimShipHandle(collider);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a player ship's controls
|
|
||||||
pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) {
|
|
||||||
if player.ship.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let ship_object = self.ships.get_mut(&player.ship.unwrap());
|
|
||||||
if let Some(ship_object) = ship_object {
|
|
||||||
match ship_object.data.get_state() {
|
|
||||||
ShipState::Landing { .. }
|
|
||||||
| ShipState::UnLanding { .. }
|
|
||||||
| ShipState::Collapsing { .. }
|
|
||||||
| ShipState::Dead => {}
|
|
||||||
|
|
||||||
ShipState::Flying {
|
|
||||||
autopilot: ShipAutoPilot::None,
|
|
||||||
} => {
|
|
||||||
ship_object.controls.guns = player.input.pressed_guns();
|
|
||||||
ship_object.controls.left = player.input.pressed_left();
|
|
||||||
ship_object.controls.right = player.input.pressed_right();
|
|
||||||
ship_object.controls.thrust = player.input.pressed_thrust();
|
|
||||||
|
|
||||||
if player.input.pressed_land() {
|
|
||||||
ship_object.data.set_autopilot(ShipAutoPilot::Landing {
|
|
||||||
target: player.selection.get_planet().unwrap(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::Flying { .. } => {
|
|
||||||
// Any input automatically releases autopilot
|
|
||||||
if player.input.pressed_left()
|
|
||||||
|| player.input.pressed_right()
|
|
||||||
|| player.input.pressed_thrust()
|
|
||||||
|| player.input.pressed_guns()
|
|
||||||
{
|
|
||||||
ship_object.data.set_autopilot(ShipAutoPilot::None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipState::Landed { .. } => {
|
|
||||||
if player.input.pressed_land() {
|
|
||||||
self.start_unland_ship(ct, player.ship.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Step this simulation by `t` seconds
|
|
||||||
pub fn step(&mut self, mut res: PhysStepResources, img: &PhysImage) {
|
|
||||||
res.timing.start_physics_sim();
|
|
||||||
|
|
||||||
// Update physics
|
|
||||||
self.wrapper.step(res.t);
|
|
||||||
|
|
||||||
// Handle collision events
|
|
||||||
while let Ok(event) = &self.wrapper.collision_queue.try_recv() {
|
|
||||||
if event.started() {
|
|
||||||
let a = event.collider1();
|
|
||||||
let b = event.collider2();
|
|
||||||
|
|
||||||
// If projectiles are part of this collision, make sure
|
|
||||||
// `a` is one of them.
|
|
||||||
let (a, b) = if self.projectiles.contains_key(&b) {
|
|
||||||
(b, a)
|
|
||||||
} else {
|
|
||||||
(a, b)
|
|
||||||
};
|
|
||||||
|
|
||||||
let p = self.projectiles.get(&a);
|
|
||||||
let s = self.ships.get_mut(&b);
|
|
||||||
if p.is_none() || s.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
self.collide_projectile_ship(&mut res, a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step and garbage-collect projectiles
|
|
||||||
self.projectiles.retain(|_, proj| {
|
|
||||||
proj.step(&mut res, &mut self.new, &mut self.wrapper);
|
|
||||||
!proj.should_remove()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step and garbage-collect ships
|
|
||||||
res.timing.start_physics_ships();
|
|
||||||
self.ships.retain(|_, ship| {
|
|
||||||
ship.step(&mut res, img, &mut self.wrapper, &mut self.new);
|
|
||||||
!ship.should_remove()
|
|
||||||
});
|
|
||||||
res.timing.mark_physics_ships();
|
|
||||||
|
|
||||||
// Step and garbage-collect effects
|
|
||||||
self.effects.retain_mut(|x| {
|
|
||||||
x.step(&res, &mut self.wrapper);
|
|
||||||
!x.is_destroyed()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process new objects
|
|
||||||
for p in self.new.projectiles.iter() {
|
|
||||||
self.projectiles.insert(p.collider, p.clone());
|
|
||||||
}
|
|
||||||
for e in self.new.effects.iter() {
|
|
||||||
self.effects.push(e.clone());
|
|
||||||
}
|
|
||||||
self.new.clear();
|
|
||||||
|
|
||||||
res.timing.mark_physics_sim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update an image with the current simulation state
|
|
||||||
pub fn update_image(&self, img: &mut PhysImage) {
|
|
||||||
img.clear();
|
|
||||||
|
|
||||||
for s in self.ships.values() {
|
|
||||||
img.ship_map
|
|
||||||
.insert(PhysSimShipHandle(s.collider), img.ships.len());
|
|
||||||
img.ships.push(PhysShipImage {
|
|
||||||
ship: s.clone(),
|
|
||||||
rigidbody: self.wrapper.get_rigid_body(s.rigid_body).unwrap().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for p in self.projectiles.values() {
|
|
||||||
img.projectiles.push(PhysProjectileImage {
|
|
||||||
projectile: p.clone(),
|
|
||||||
rigidbody: self.wrapper.get_rigid_body(p.rigid_body).unwrap().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for e in self.effects.iter() {
|
|
||||||
img.effects.push(PhysEffectImage {
|
|
||||||
effect: e.clone(),
|
|
||||||
rigidbody: self.wrapper.get_rigid_body(e.rigid_body).unwrap().clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public getters
|
|
||||||
impl PhysSim {
|
|
||||||
/// Get a ship physics object
|
|
||||||
pub fn get_ship(&self, ship: &PhysSimShipHandle) -> Option<&PhysShip> {
|
|
||||||
self.ships.get(&ship.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a ship physics object
|
|
||||||
pub fn get_ship_mut(&mut self, ship: &PhysSimShipHandle) -> Option<&mut PhysShip> {
|
|
||||||
self.ships.get_mut(&ship.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
|
||||||
pub fn iter_ships(&self) -> impl Iterator<Item = &PhysShip> + '_ {
|
|
||||||
self.ships.values()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
|
||||||
pub fn iter_projectiles(&self) -> impl Iterator<Item = &PhysProjectile> + '_ {
|
|
||||||
self.projectiles.values()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all particles in this physics system
|
|
||||||
pub fn iter_particles(&self) -> impl Iterator<Item = &PhysEffect> + '_ {
|
|
||||||
self.effects.iter()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
use crossbeam::channel::Receiver;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{
|
|
||||||
CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
|
|
||||||
RigidBody, RigidBodyHandle, RigidBodySet,
|
|
||||||
},
|
|
||||||
geometry::{BroadPhase, Collider, ColliderHandle, ColliderSet, CollisionEvent, NarrowPhase},
|
|
||||||
na::vector,
|
|
||||||
pipeline::{ChannelEventCollector, PhysicsPipeline},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Wraps Rapier2d physics parameters
|
|
||||||
pub struct PhysWrapper {
|
|
||||||
ip: IntegrationParameters,
|
|
||||||
pp: PhysicsPipeline,
|
|
||||||
im: IslandManager,
|
|
||||||
bp: BroadPhase,
|
|
||||||
np: NarrowPhase,
|
|
||||||
ij: ImpulseJointSet,
|
|
||||||
mj: MultibodyJointSet,
|
|
||||||
ccd: CCDSolver,
|
|
||||||
|
|
||||||
rigid_body_set: RigidBodySet,
|
|
||||||
collider_set: ColliderSet,
|
|
||||||
|
|
||||||
collision_handler: ChannelEventCollector,
|
|
||||||
|
|
||||||
/// Collision event queue
|
|
||||||
/// this should be emptied after every frame.
|
|
||||||
pub collision_queue: Receiver<CollisionEvent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysWrapper {
|
|
||||||
/// Make a new physics wrapper
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
|
||||||
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
ip: IntegrationParameters::default(),
|
|
||||||
pp: PhysicsPipeline::new(),
|
|
||||||
im: IslandManager::new(),
|
|
||||||
bp: BroadPhase::new(),
|
|
||||||
np: NarrowPhase::new(),
|
|
||||||
ij: ImpulseJointSet::new(),
|
|
||||||
mj: MultibodyJointSet::new(),
|
|
||||||
ccd: CCDSolver::new(),
|
|
||||||
collision_queue,
|
|
||||||
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
|
|
||||||
|
|
||||||
rigid_body_set: RigidBodySet::new(),
|
|
||||||
collider_set: ColliderSet::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Step physics sim by `t` seconds
|
|
||||||
pub fn step(&mut self, t: f32) {
|
|
||||||
self.ip.dt = t;
|
|
||||||
self.pp.step(
|
|
||||||
&vector![0.0, 0.0],
|
|
||||||
&self.ip,
|
|
||||||
&mut self.im,
|
|
||||||
&mut self.bp,
|
|
||||||
&mut self.np,
|
|
||||||
&mut self.rigid_body_set,
|
|
||||||
&mut self.collider_set,
|
|
||||||
&mut self.ij,
|
|
||||||
&mut self.mj,
|
|
||||||
&mut self.ccd,
|
|
||||||
None,
|
|
||||||
&(),
|
|
||||||
&mut self.collision_handler,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove a rigid body and all its colliders from this physics wrapper
|
|
||||||
pub fn remove_rigid_body(&mut self, body: RigidBodyHandle) -> Option<RigidBody> {
|
|
||||||
return self.rigid_body_set.remove(
|
|
||||||
body,
|
|
||||||
&mut self.im,
|
|
||||||
&mut self.collider_set,
|
|
||||||
&mut self.ij,
|
|
||||||
&mut self.mj,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PhysWrapper {
|
|
||||||
/// Get a rigidbody from this physics wrapper
|
|
||||||
pub fn get_rigid_body(&self, h: RigidBodyHandle) -> Option<&RigidBody> {
|
|
||||||
self.rigid_body_set.get(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a rigidbody from this physics wrapper, mutably
|
|
||||||
pub fn get_rigid_body_mut(&mut self, h: RigidBodyHandle) -> Option<&mut RigidBody> {
|
|
||||||
self.rigid_body_set.get_mut(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a collider from this physics wrapper
|
|
||||||
pub fn get_collider(&self, h: ColliderHandle) -> Option<&Collider> {
|
|
||||||
self.collider_set.get(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a collider from this physics wrapper, mutably
|
|
||||||
pub fn get_collider_mut(&mut self, h: ColliderHandle) -> Option<&mut Collider> {
|
|
||||||
self.collider_set.get_mut(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a rigid body
|
|
||||||
pub fn insert_rigid_body(&mut self, rb: RigidBody) -> RigidBodyHandle {
|
|
||||||
self.rigid_body_set.insert(rb)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attach a collider to a rigid body
|
|
||||||
pub fn insert_collider(
|
|
||||||
&mut self,
|
|
||||||
collider: Collider,
|
|
||||||
parent_handle: RigidBodyHandle,
|
|
||||||
) -> ColliderHandle {
|
|
||||||
self.collider_set
|
|
||||||
.insert_with_parent(collider, parent_handle, &mut self.rigid_body_set)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::{wrapper::Wrapper, ParticleBuilder};
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_util::timing::Timing;
|
use galactica_util::timing::Timing;
|
||||||
|
|
||||||
|
@ -9,6 +10,12 @@ pub struct PhysStepResources<'a> {
|
||||||
/// Length of time step
|
/// Length of time step
|
||||||
pub t: f32,
|
pub t: f32,
|
||||||
|
|
||||||
|
/// Particles to create
|
||||||
|
pub particles: &'a mut Vec<ParticleBuilder>,
|
||||||
|
|
||||||
/// Record detailed frame timing breakdown
|
/// Record detailed frame timing breakdown
|
||||||
pub timing: &'a mut Timing,
|
pub timing: &'a mut Timing,
|
||||||
|
|
||||||
|
/// Physics computer
|
||||||
|
pub wrapper: &'a mut Wrapper,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle};
|
||||||
|
use galactica_playeragent::PlayerAgent;
|
||||||
|
use nalgebra::{Point2, Vector2};
|
||||||
|
|
||||||
|
use rapier2d::{
|
||||||
|
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet},
|
||||||
|
geometry::{ColliderHandle, ColliderSet, Group, InteractionGroups},
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
controller::ShipController,
|
||||||
|
objects::{PhysProjectile, PhysSimShip},
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: replace with a more generic handle
|
||||||
|
/// A handle for a ship in this simulation
|
||||||
|
/// This lets other crates reference ships
|
||||||
|
/// without including `rapier2d`.
|
||||||
|
pub struct PhysSimShipHandle(pub ColliderHandle);
|
||||||
|
|
||||||
|
/// Manages the physics state of one system
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PhysSim {
|
||||||
|
/// The system this sim is attached to
|
||||||
|
_system: SystemHandle,
|
||||||
|
|
||||||
|
rigid_body_set: RigidBodySet,
|
||||||
|
collider_set: ColliderSet,
|
||||||
|
|
||||||
|
projectiles: HashMap<ColliderHandle, PhysProjectile>,
|
||||||
|
ships: HashMap<ColliderHandle, PhysSimShip>,
|
||||||
|
ship_behaviors: HashMap<ColliderHandle, ShipController>,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod step;
|
||||||
|
mod steputil;
|
||||||
|
|
||||||
|
// Public methods
|
||||||
|
impl PhysSim {
|
||||||
|
/// Create a new physics system
|
||||||
|
pub fn new(_ct: &Content, system: SystemHandle) -> Self {
|
||||||
|
Self {
|
||||||
|
_system: system,
|
||||||
|
rigid_body_set: RigidBodySet::new(),
|
||||||
|
collider_set: ColliderSet::new(),
|
||||||
|
projectiles: HashMap::new(),
|
||||||
|
ships: HashMap::new(),
|
||||||
|
ship_behaviors: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a ship to this physics system
|
||||||
|
pub fn add_ship(
|
||||||
|
&mut self,
|
||||||
|
ct: &Content,
|
||||||
|
handle: ShipHandle,
|
||||||
|
faction: FactionHandle,
|
||||||
|
personality: ShipPersonality,
|
||||||
|
position: Point2<f32>,
|
||||||
|
) -> PhysSimShipHandle {
|
||||||
|
let ship_content = ct.get_ship(handle);
|
||||||
|
let mut cl = ship_content.collider.0.clone();
|
||||||
|
// TODO: additonal ship mass from outfits and cargo
|
||||||
|
|
||||||
|
let rb = RigidBodyBuilder::dynamic()
|
||||||
|
.angular_damping(ship_content.angular_drag)
|
||||||
|
.linear_damping(ship_content.linear_drag)
|
||||||
|
.translation(Vector2::new(position.x, position.y))
|
||||||
|
.can_sleep(false);
|
||||||
|
|
||||||
|
cl.set_collision_groups(InteractionGroups::new(
|
||||||
|
Group::GROUP_1,
|
||||||
|
Group::GROUP_1 | Group::GROUP_2,
|
||||||
|
));
|
||||||
|
|
||||||
|
let ridid_body = self.rigid_body_set.insert(rb.build());
|
||||||
|
let collider =
|
||||||
|
self.collider_set
|
||||||
|
.insert_with_parent(cl, ridid_body, &mut self.rigid_body_set);
|
||||||
|
|
||||||
|
self.ship_behaviors.insert(
|
||||||
|
collider,
|
||||||
|
match personality {
|
||||||
|
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
|
||||||
|
ShipPersonality::Point => ShipController::new_point(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.ships.insert(
|
||||||
|
collider,
|
||||||
|
PhysSimShip::new(ct, handle, personality, faction, ridid_body, collider),
|
||||||
|
);
|
||||||
|
|
||||||
|
return PhysSimShipHandle(collider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update a player ship's controls
|
||||||
|
pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) {
|
||||||
|
if player.ship.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ship_object = self.ships.get_mut(&player.ship.unwrap());
|
||||||
|
if let Some(ship_object) = ship_object {
|
||||||
|
match ship_object.data.get_state() {
|
||||||
|
ShipState::Landing { .. }
|
||||||
|
| ShipState::UnLanding { .. }
|
||||||
|
| ShipState::Collapsing { .. }
|
||||||
|
| ShipState::Dead => {}
|
||||||
|
|
||||||
|
ShipState::Flying {
|
||||||
|
autopilot: ShipAutoPilot::None,
|
||||||
|
} => {
|
||||||
|
ship_object.controls.guns = player.input.pressed_guns();
|
||||||
|
ship_object.controls.left = player.input.pressed_left();
|
||||||
|
ship_object.controls.right = player.input.pressed_right();
|
||||||
|
ship_object.controls.thrust = player.input.pressed_thrust();
|
||||||
|
|
||||||
|
if player.input.pressed_land() {
|
||||||
|
ship_object.data.set_autopilot(ShipAutoPilot::Landing {
|
||||||
|
target: player.selection.get_planet().unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Flying { .. } => {
|
||||||
|
// Any input automatically releases autopilot
|
||||||
|
if player.input.pressed_left()
|
||||||
|
|| player.input.pressed_right()
|
||||||
|
|| player.input.pressed_thrust()
|
||||||
|
|| player.input.pressed_guns()
|
||||||
|
{
|
||||||
|
ship_object.data.set_autopilot(ShipAutoPilot::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Landed { .. } => {
|
||||||
|
if player.input.pressed_land() {
|
||||||
|
self.start_unland_ship(ct, player.ship.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public getters
|
||||||
|
impl PhysSim {
|
||||||
|
/// Get a ship physics object
|
||||||
|
pub fn get_ship(&self, ship: &PhysSimShipHandle) -> Option<&PhysSimShip> {
|
||||||
|
self.ships.get(&ship.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a ship physics object
|
||||||
|
pub fn get_ship_mut(&mut self, ship: &PhysSimShipHandle) -> Option<&mut PhysSimShip> {
|
||||||
|
self.ships.get_mut(&ship.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a rigid body from a handle
|
||||||
|
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> Option<&RigidBody> {
|
||||||
|
self.rigid_body_set.get(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a rigid body from a handle
|
||||||
|
pub fn get_rigid_body_mut(&mut self, r: RigidBodyHandle) -> Option<&mut RigidBody> {
|
||||||
|
self.rigid_body_set.get_mut(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all ships in this physics system
|
||||||
|
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&PhysSimShip, &RigidBody)> + '_ {
|
||||||
|
self.ships
|
||||||
|
.values()
|
||||||
|
.map(|x| (x, self.rigid_body_set.get(x.rigid_body).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all ships in this physics system
|
||||||
|
pub fn iter_ships(&self) -> impl Iterator<Item = &PhysSimShip> + '_ {
|
||||||
|
self.ships.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all ships in this physics system
|
||||||
|
pub fn iter_projectiles(&self) -> impl Iterator<Item = &PhysProjectile> + '_ {
|
||||||
|
self.projectiles.values()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,334 @@
|
||||||
|
use galactica_content::{GunPoint, OutfitHandle, ProjectileCollider};
|
||||||
|
use nalgebra::{Rotation2, Vector2};
|
||||||
|
use rand::Rng;
|
||||||
|
use rapier2d::{
|
||||||
|
dynamics::RigidBodyBuilder,
|
||||||
|
geometry::{ColliderBuilder, Group, InteractionGroups},
|
||||||
|
pipeline::ActiveEvents,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::{ShipAutoPilot, ShipState},
|
||||||
|
phys::{
|
||||||
|
controller::autopilot,
|
||||||
|
objects::{PhysProjectile, ShipControls},
|
||||||
|
ParticleBuilder, PhysStepResources,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::PhysSim;
|
||||||
|
|
||||||
|
impl PhysSim {
|
||||||
|
/// Run ship updates:
|
||||||
|
/// - updates ship controls (runs behaviors)
|
||||||
|
/// - applies ship controls
|
||||||
|
/// - creates projectiles
|
||||||
|
fn step_ships(&mut self, res: &mut PhysStepResources) {
|
||||||
|
// We can't apply these right away since self is borrowed
|
||||||
|
// by the iterator
|
||||||
|
// TODO: don't allocate!
|
||||||
|
let mut projectiles = Vec::new();
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
// Again, borrow checker hack. TODO: fix
|
||||||
|
let keys: Vec<_> = self.ships.keys().map(|c| *c).collect();
|
||||||
|
for collider in keys {
|
||||||
|
// Borrow immutably for now...
|
||||||
|
// (required to compute controls)
|
||||||
|
let ship = self.ships.get(&collider).unwrap();
|
||||||
|
match ship.data.get_state() {
|
||||||
|
ShipState::Dead => {
|
||||||
|
to_remove.push(collider);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Landed { .. } | ShipState::Collapsing { .. } => {
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
ship.step(
|
||||||
|
res,
|
||||||
|
&mut self.rigid_body_set[ship.rigid_body],
|
||||||
|
&mut self.collider_set[ship.collider],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::UnLanding {
|
||||||
|
to_position,
|
||||||
|
current_z,
|
||||||
|
from,
|
||||||
|
} => {
|
||||||
|
let from_obj = res.ct.get_system_object(*from);
|
||||||
|
let controls = autopilot::auto_landing(
|
||||||
|
&res,
|
||||||
|
&self.rigid_body_set,
|
||||||
|
&self.ships,
|
||||||
|
ship.collider,
|
||||||
|
Vector2::new(to_position.translation.x, to_position.translation.y),
|
||||||
|
);
|
||||||
|
let r = &mut self.rigid_body_set[ship.rigid_body];
|
||||||
|
let max_d = (Vector2::new(from_obj.pos.x, from_obj.pos.y)
|
||||||
|
- Vector2::new(to_position.translation.x, to_position.translation.y))
|
||||||
|
.magnitude();
|
||||||
|
let now_d = (r.translation()
|
||||||
|
- Vector2::new(to_position.translation.x, to_position.translation.y))
|
||||||
|
.magnitude();
|
||||||
|
let f = now_d / max_d;
|
||||||
|
|
||||||
|
let current_z = *current_z;
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
let zdist = 1.0 - from_obj.pos.z;
|
||||||
|
|
||||||
|
if current_z <= 1.0 {
|
||||||
|
self.finish_unland_ship(collider);
|
||||||
|
} else if current_z <= 1.5 {
|
||||||
|
ship.data
|
||||||
|
.set_unlanding_z(1f32.max(current_z - (0.5 * res.t) / 0.5));
|
||||||
|
ship.controls = ShipControls::new();
|
||||||
|
} else {
|
||||||
|
ship.data.set_unlanding_z(1.0 - zdist * f);
|
||||||
|
|
||||||
|
if let Some(controls) = controls {
|
||||||
|
ship.controls = controls;
|
||||||
|
}
|
||||||
|
ship.step(res, r, &mut self.collider_set[ship.collider])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Landing { target, current_z } => {
|
||||||
|
let target_obj = res.ct.get_system_object(*target);
|
||||||
|
let controls = autopilot::auto_landing(
|
||||||
|
&res,
|
||||||
|
&self.rigid_body_set,
|
||||||
|
&self.ships,
|
||||||
|
ship.collider,
|
||||||
|
Vector2::new(target_obj.pos.x, target_obj.pos.y),
|
||||||
|
);
|
||||||
|
|
||||||
|
let current_z = *current_z;
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
let r = &mut self.rigid_body_set[ship.rigid_body];
|
||||||
|
let zdist = target_obj.pos.z - 1.0;
|
||||||
|
|
||||||
|
if current_z >= target_obj.pos.z {
|
||||||
|
self.finish_land_ship(collider);
|
||||||
|
} else {
|
||||||
|
ship.data.set_landing_z(current_z + zdist * res.t / 2.0);
|
||||||
|
|
||||||
|
if let Some(controls) = controls {
|
||||||
|
ship.controls = controls;
|
||||||
|
}
|
||||||
|
ship.step(res, r, &mut self.collider_set[ship.collider])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipState::Flying { autopilot } => {
|
||||||
|
// Compute new controls
|
||||||
|
// This is why we borrow immutably first
|
||||||
|
let controls = match autopilot {
|
||||||
|
ShipAutoPilot::Landing {
|
||||||
|
target: target_handle,
|
||||||
|
} => {
|
||||||
|
let target_obj = res.ct.get_system_object(*target_handle);
|
||||||
|
let controls = autopilot::auto_landing(
|
||||||
|
&res,
|
||||||
|
&self.rigid_body_set,
|
||||||
|
&self.ships,
|
||||||
|
ship.collider,
|
||||||
|
Vector2::new(target_obj.pos.x, target_obj.pos.y),
|
||||||
|
);
|
||||||
|
|
||||||
|
let landed = self.try_land_ship(res.ct, collider, *target_handle);
|
||||||
|
if landed {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
controls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShipAutoPilot::None => {
|
||||||
|
let b = self.ship_behaviors.get_mut(&collider).unwrap();
|
||||||
|
b.update_controls(
|
||||||
|
&res,
|
||||||
|
&self.rigid_body_set,
|
||||||
|
&self.ships,
|
||||||
|
ship.collider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Re-borrow mutably to apply changes
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
|
||||||
|
if let Some(controls) = controls {
|
||||||
|
ship.controls = controls;
|
||||||
|
}
|
||||||
|
ship.step(
|
||||||
|
res,
|
||||||
|
&mut self.rigid_body_set[ship.rigid_body],
|
||||||
|
&mut self.collider_set[ship.collider],
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we're firing, try to fire each gun
|
||||||
|
if ship.controls.guns {
|
||||||
|
// TODO: don't allocate here. This is a hack to satisfy the borrow checker,
|
||||||
|
// convert this to a refcell or do the replace dance.
|
||||||
|
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = ship
|
||||||
|
.data
|
||||||
|
.get_outfits()
|
||||||
|
.iter_gun_points()
|
||||||
|
.map(|(p, o)| (p.clone(), o.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (gun, outfit) in pairs {
|
||||||
|
if ship.data.fire_gun(res.ct, &gun) {
|
||||||
|
projectiles.push((ship.collider, gun.clone(), outfit.unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove ships that don't exist
|
||||||
|
for c in to_remove {
|
||||||
|
self.remove_ship(res, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create projectiles
|
||||||
|
for (collider, gun_point, outfit) in projectiles {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let ship = self.ships.get(&collider).unwrap();
|
||||||
|
let rigid_body = self.get_rigid_body(ship.rigid_body).unwrap();
|
||||||
|
let ship_pos = rigid_body.translation();
|
||||||
|
let ship_rot = rigid_body.rotation();
|
||||||
|
let ship_ang = ship_rot.angle();
|
||||||
|
let ship_vel = rigid_body.velocity_at_point(rigid_body.center_of_mass());
|
||||||
|
|
||||||
|
let pos = ship_pos + (ship_rot * gun_point.pos);
|
||||||
|
|
||||||
|
let outfit = res.ct.get_outfit(outfit);
|
||||||
|
let outfit = outfit.gun.as_ref().unwrap();
|
||||||
|
|
||||||
|
let spread = rng.gen_range(-outfit.projectile.angle_rng..=outfit.projectile.angle_rng);
|
||||||
|
let vel = ship_vel
|
||||||
|
+ (Rotation2::new(ship_ang + spread)
|
||||||
|
* Vector2::new(
|
||||||
|
outfit.projectile.speed
|
||||||
|
+ rng.gen_range(
|
||||||
|
-outfit.projectile.speed_rng..=outfit.projectile.speed_rng,
|
||||||
|
),
|
||||||
|
0.0,
|
||||||
|
));
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
|
||||||
|
.translation(pos)
|
||||||
|
.rotation(ship_ang)
|
||||||
|
.linvel(vel)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mut collider = match &outfit.projectile.collider {
|
||||||
|
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
||||||
|
.sensor(true)
|
||||||
|
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
.build(),
|
||||||
|
};
|
||||||
|
|
||||||
|
collider.set_collision_groups(InteractionGroups::new(Group::GROUP_2, Group::GROUP_1));
|
||||||
|
|
||||||
|
let rigid_body = self.rigid_body_set.insert(rigid_body);
|
||||||
|
let collider = self.collider_set.insert_with_parent(
|
||||||
|
collider,
|
||||||
|
rigid_body,
|
||||||
|
&mut self.rigid_body_set,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.projectiles.insert(
|
||||||
|
collider.clone(),
|
||||||
|
PhysProjectile::new(
|
||||||
|
res.ct,
|
||||||
|
outfit.projectile.clone(),
|
||||||
|
rigid_body,
|
||||||
|
ship.data.get_faction(),
|
||||||
|
collider,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Step this physics system by `t` seconds
|
||||||
|
pub fn step(&mut self, mut res: PhysStepResources) {
|
||||||
|
res.timing.start_physics_ships();
|
||||||
|
self.step_ships(&mut res);
|
||||||
|
res.timing.mark_physics_ships();
|
||||||
|
|
||||||
|
res.timing.start_physics_sim();
|
||||||
|
|
||||||
|
// Update physics
|
||||||
|
res.wrapper
|
||||||
|
.step(res.t, &mut self.rigid_body_set, &mut self.collider_set);
|
||||||
|
|
||||||
|
// Handle collision events
|
||||||
|
while let Ok(event) = &res.wrapper.collision_queue.try_recv() {
|
||||||
|
if event.started() {
|
||||||
|
let a = event.collider1();
|
||||||
|
let b = event.collider2();
|
||||||
|
|
||||||
|
// If projectiles are part of this collision, make sure
|
||||||
|
// `a` is one of them.
|
||||||
|
let (a, b) = if self.projectiles.contains_key(&b) {
|
||||||
|
(b, a)
|
||||||
|
} else {
|
||||||
|
(a, b)
|
||||||
|
};
|
||||||
|
|
||||||
|
let p = self.projectiles.get(&a);
|
||||||
|
let s = self.ships.get_mut(&b);
|
||||||
|
if p.is_none() || s.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.collide_projectile_ship(&mut res, a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete projectiles
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
for (c, p) in &mut self.projectiles {
|
||||||
|
p.tick(res.ct, res.t);
|
||||||
|
if p.is_expired() {
|
||||||
|
to_remove.push(*c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
for c in to_remove {
|
||||||
|
let (pr, p) = self.remove_projectile(&mut res, c).unwrap();
|
||||||
|
|
||||||
|
match &p.content.expire_effect {
|
||||||
|
None => {}
|
||||||
|
Some(x) => {
|
||||||
|
let x = res.ct.get_effect(*x);
|
||||||
|
let pos = *pr.translation();
|
||||||
|
let vel = pr.velocity_at_point(pr.center_of_mass());
|
||||||
|
let angle = pr.rotation().angle();
|
||||||
|
|
||||||
|
let velocity = {
|
||||||
|
let a = rng
|
||||||
|
.gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng);
|
||||||
|
|
||||||
|
let velocity = (x.velocity_scale_parent + a) * vel;
|
||||||
|
|
||||||
|
velocity
|
||||||
|
};
|
||||||
|
|
||||||
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
|
x,
|
||||||
|
pos.into(),
|
||||||
|
-angle,
|
||||||
|
velocity,
|
||||||
|
Vector2::new(0.0, 0.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
res.timing.mark_physics_sim();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
use galactica_content::{Content, Relationship, SystemObjectHandle};
|
||||||
|
use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
|
||||||
|
use rand::Rng;
|
||||||
|
use rapier2d::{
|
||||||
|
dynamics::RigidBody,
|
||||||
|
geometry::{ColliderHandle, Group, InteractionGroups},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::ShipState,
|
||||||
|
phys::{objects::PhysProjectile, ParticleBuilder, PhysStepResources},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::PhysSim;
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
impl PhysSim {
|
||||||
|
pub(super) fn remove_projectile(
|
||||||
|
&mut self,
|
||||||
|
res: &mut PhysStepResources,
|
||||||
|
c: ColliderHandle,
|
||||||
|
) -> Option<(RigidBody, PhysProjectile)> {
|
||||||
|
let p = match self.projectiles.remove(&c) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
let r = self
|
||||||
|
.rigid_body_set
|
||||||
|
.remove(
|
||||||
|
p.rigid_body,
|
||||||
|
&mut res.wrapper.im,
|
||||||
|
&mut self.collider_set,
|
||||||
|
&mut res.wrapper.ij,
|
||||||
|
&mut res.wrapper.mj,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
return Some((r, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to land the given ship on the given planet.
|
||||||
|
/// State changes on success, and nothing happens on failure.
|
||||||
|
/// returns `true` if landing was successful.
|
||||||
|
pub(super) fn try_land_ship(
|
||||||
|
&mut self,
|
||||||
|
ct: &Content,
|
||||||
|
collider: ColliderHandle,
|
||||||
|
target_handle: SystemObjectHandle,
|
||||||
|
) -> bool {
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
let r = self.rigid_body_set.get(ship.rigid_body).unwrap();
|
||||||
|
|
||||||
|
let target = ct.get_system_object(target_handle);
|
||||||
|
let t_pos = Vector2::new(target.pos.x, target.pos.y);
|
||||||
|
let s_pos = Vector2::new(r.position().translation.x, r.position().translation.y);
|
||||||
|
|
||||||
|
// TODO: deactivate collider when landing.
|
||||||
|
// Can't just set_active(false), since we still need that collider's mass.
|
||||||
|
|
||||||
|
// We're in land range...
|
||||||
|
if (t_pos - s_pos).magnitude() > target.size / 2.0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And we'll stay in land range long enough.
|
||||||
|
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0)).magnitude()
|
||||||
|
> target.size / 2.0
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let collider = self.collider_set.get_mut(collider).unwrap();
|
||||||
|
collider.set_collision_groups(InteractionGroups::new(Group::GROUP_1, Group::empty()));
|
||||||
|
ship.data.start_land_on(target_handle);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish landing this ship on a planet.
|
||||||
|
/// Called after the landing animation finishes.
|
||||||
|
pub(super) fn finish_land_ship(&mut self, collider: ColliderHandle) {
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
ship.data.finish_land_on();
|
||||||
|
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||||
|
r.set_enabled(false);
|
||||||
|
r.set_angvel(0.0, false);
|
||||||
|
r.set_linvel(nalgebra::Vector2::new(0.0, 0.0), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn start_unland_ship(&mut self, ct: &Content, collider: ColliderHandle) {
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
let obj = ship.data.get_state().landed_on().unwrap();
|
||||||
|
let obj = ct.get_system_object(obj);
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let radius = rng.gen_range(500.0..=1500.0);
|
||||||
|
let angle = rng.gen_range(0.0..std::f32::consts::TAU);
|
||||||
|
let target_offset = Rotation2::new(angle) * Vector2::new(radius, 0.0);
|
||||||
|
let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset;
|
||||||
|
let target_pos = Isometry2::new(target_trans, angle);
|
||||||
|
|
||||||
|
ship.data.start_unland_to(ct, target_pos);
|
||||||
|
|
||||||
|
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||||
|
r.set_enabled(true);
|
||||||
|
r.set_position(
|
||||||
|
Isometry2::new(Vector2::new(obj.pos.x, obj.pos.y), angle),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn finish_unland_ship(&mut self, collider: ColliderHandle) {
|
||||||
|
let ship = self.ships.get_mut(&collider).unwrap();
|
||||||
|
ship.data.finish_unland_to();
|
||||||
|
self.collider_set
|
||||||
|
.get_mut(ship.collider)
|
||||||
|
.unwrap()
|
||||||
|
.set_collision_groups(InteractionGroups::new(
|
||||||
|
Group::GROUP_1,
|
||||||
|
Group::GROUP_1 | Group::GROUP_2,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn remove_ship(
|
||||||
|
&mut self,
|
||||||
|
res: &mut PhysStepResources,
|
||||||
|
colliderhandle: ColliderHandle,
|
||||||
|
) {
|
||||||
|
let ship = match self.ships.get(&colliderhandle) {
|
||||||
|
None => return,
|
||||||
|
Some(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.rigid_body_set.remove(
|
||||||
|
ship.rigid_body,
|
||||||
|
&mut res.wrapper.im,
|
||||||
|
&mut self.collider_set,
|
||||||
|
&mut res.wrapper.ij,
|
||||||
|
&mut res.wrapper.mj,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.ships.remove(&colliderhandle).unwrap();
|
||||||
|
self.ship_behaviors.remove(&colliderhandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn collide_projectile_ship(
|
||||||
|
&mut self,
|
||||||
|
res: &mut PhysStepResources,
|
||||||
|
projectile_h: ColliderHandle,
|
||||||
|
ship_h: ColliderHandle,
|
||||||
|
) {
|
||||||
|
let projectile = self.projectiles.get(&projectile_h);
|
||||||
|
let ship = self.ships.get_mut(&ship_h);
|
||||||
|
if projectile.is_none() || ship.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let projectile = projectile.unwrap();
|
||||||
|
let ship = ship.unwrap();
|
||||||
|
|
||||||
|
let f = res.ct.get_faction(projectile.faction);
|
||||||
|
let r = f.relationships.get(&ship.data.get_faction()).unwrap();
|
||||||
|
let destory_projectile = match r {
|
||||||
|
Relationship::Hostile => match ship.data.get_state() {
|
||||||
|
ShipState::Flying { .. } => {
|
||||||
|
ship.data.apply_damage(projectile.content.damage);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
ShipState::Collapsing { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if destory_projectile {
|
||||||
|
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
|
||||||
|
let v =
|
||||||
|
pr.velocity_at_point(pr.center_of_mass()).normalize() * projectile.content.force;
|
||||||
|
let pos = *pr.translation();
|
||||||
|
let _ = pr;
|
||||||
|
|
||||||
|
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||||
|
r.apply_impulse_at_point(Vector2::new(v.x, v.y), Point2::new(pos.x, pos.y), true);
|
||||||
|
|
||||||
|
// Borrow again, we can only have one at a time
|
||||||
|
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
|
||||||
|
let pos = *pr.translation();
|
||||||
|
let angle = pr.rotation().angle();
|
||||||
|
|
||||||
|
match &projectile.content.impact_effect {
|
||||||
|
None => {}
|
||||||
|
Some(x) => {
|
||||||
|
let effect = res.ct.get_effect(*x);
|
||||||
|
let r = ship.rigid_body;
|
||||||
|
let sr = self.get_rigid_body(r).unwrap();
|
||||||
|
let parent_velocity = pr.velocity_at_point(pr.center_of_mass());
|
||||||
|
let target_velocity =
|
||||||
|
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||||
|
|
||||||
|
res.particles.push(ParticleBuilder::from_content(
|
||||||
|
effect,
|
||||||
|
pos.into(),
|
||||||
|
-angle,
|
||||||
|
parent_velocity,
|
||||||
|
target_velocity,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.remove_projectile(res, projectile.collider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
use crossbeam::channel::Receiver;
|
||||||
|
use rapier2d::{
|
||||||
|
dynamics::{
|
||||||
|
CCDSolver, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet,
|
||||||
|
RigidBodySet,
|
||||||
|
},
|
||||||
|
geometry::{BroadPhase, ColliderSet, CollisionEvent, NarrowPhase},
|
||||||
|
na::vector,
|
||||||
|
pipeline::{ChannelEventCollector, PhysicsPipeline},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Wraps Rapier2d physics parameters
|
||||||
|
pub struct Wrapper {
|
||||||
|
pub(crate) ip: IntegrationParameters,
|
||||||
|
pub(crate) pp: PhysicsPipeline,
|
||||||
|
pub(crate) im: IslandManager,
|
||||||
|
pub(crate) bp: BroadPhase,
|
||||||
|
pub(crate) np: NarrowPhase,
|
||||||
|
pub(crate) ij: ImpulseJointSet,
|
||||||
|
pub(crate) mj: MultibodyJointSet,
|
||||||
|
pub(crate) ccd: CCDSolver,
|
||||||
|
|
||||||
|
collision_handler: ChannelEventCollector,
|
||||||
|
|
||||||
|
/// Collision event queue
|
||||||
|
/// this should be emptied after every frame.
|
||||||
|
pub collision_queue: Receiver<CollisionEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wrapper {
|
||||||
|
/// Make a new physics wrapper
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
||||||
|
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
ip: IntegrationParameters::default(),
|
||||||
|
pp: PhysicsPipeline::new(),
|
||||||
|
im: IslandManager::new(),
|
||||||
|
bp: BroadPhase::new(),
|
||||||
|
np: NarrowPhase::new(),
|
||||||
|
ij: ImpulseJointSet::new(),
|
||||||
|
mj: MultibodyJointSet::new(),
|
||||||
|
ccd: CCDSolver::new(),
|
||||||
|
collision_queue,
|
||||||
|
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Step physics sim by `t` seconds
|
||||||
|
pub fn step(
|
||||||
|
&mut self,
|
||||||
|
t: f32,
|
||||||
|
rigid_body_set: &mut RigidBodySet,
|
||||||
|
collider_set: &mut ColliderSet,
|
||||||
|
) {
|
||||||
|
self.ip.dt = t;
|
||||||
|
self.pp.step(
|
||||||
|
&vector![0.0, 0.0],
|
||||||
|
&self.ip,
|
||||||
|
&mut self.im,
|
||||||
|
&mut self.bp,
|
||||||
|
&mut self.np,
|
||||||
|
rigid_body_set,
|
||||||
|
collider_set,
|
||||||
|
&mut self.ij,
|
||||||
|
&mut self.mj,
|
||||||
|
&mut self.ccd,
|
||||||
|
None,
|
||||||
|
&(),
|
||||||
|
&mut self.collision_handler,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
// TODO: many of these should be moved to a config file or cli option
|
// TODO: many of these should be moved to a config file or cli option
|
||||||
|
|
||||||
/// We can draw at most this many object sprites on the screen.
|
/// We can draw at most this many object sprites on the screen.
|
||||||
pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 2048;
|
pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
|
||||||
|
|
||||||
/// We can draw at most this many ui sprites on the screen.
|
/// We can draw at most this many ui sprites on the screen.
|
||||||
pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100;
|
pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||||
|
@ -12,6 +12,9 @@ pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100;
|
||||||
/// This is fairly small, since we know exactly how many of these we'll draw (for now)
|
/// This is fairly small, since we know exactly how many of these we'll draw (for now)
|
||||||
pub const RADIALBAR_SPRITE_INSTANCE_LIMIT: u64 = 10;
|
pub const RADIALBAR_SPRITE_INSTANCE_LIMIT: u64 = 10;
|
||||||
|
|
||||||
|
/// The size of our circular particle buffer. When we create particles, the oldest ones are replaced.
|
||||||
|
pub const PARTICLE_SPRITE_INSTANCE_LIMIT: u64 = 1000;
|
||||||
|
|
||||||
/// The maximum number of sprites we can define
|
/// The maximum number of sprites we can define
|
||||||
pub const SPRITE_LIMIT: u32 = 1024;
|
pub const SPRITE_LIMIT: u32 = 1024;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue