143 lines
3.6 KiB
Rust
143 lines
3.6 KiB
Rust
use galactica_content::{CollapseEvent, Ship};
|
|
use nalgebra::{Point2, Vector2};
|
|
use rand::{rngs::ThreadRng, Rng};
|
|
use rapier2d::{
|
|
dynamics::RigidBodyHandle,
|
|
geometry::{Collider, ColliderHandle},
|
|
};
|
|
|
|
use crate::{
|
|
data::ShipData,
|
|
phys::{objects::PhysEffect, physsim::NewObjects, PhysStepResources, PhysWrapper},
|
|
};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(super) struct ShipCollapseSequence {
|
|
rng: ThreadRng,
|
|
|
|
/// The total length of this collapse sequence
|
|
total_length: f32,
|
|
|
|
/// How many seconds we've spent playing this sequence
|
|
elapsed: f32,
|
|
}
|
|
|
|
impl ShipCollapseSequence {
|
|
pub(super) fn new(total_length: f32) -> Self {
|
|
Self {
|
|
rng: rand::thread_rng(),
|
|
total_length,
|
|
elapsed: 0.0,
|
|
}
|
|
}
|
|
|
|
/// Has this collapse sequence fully played out?
|
|
pub fn is_done(&self) -> bool {
|
|
self.elapsed >= self.total_length
|
|
}
|
|
|
|
/// Pick a random points inside a ship's collider
|
|
fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> {
|
|
let mut y = 0.0;
|
|
let mut x = 0.0;
|
|
let mut a = false;
|
|
while !a {
|
|
x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0;
|
|
y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0;
|
|
a = collider.shape().contains_local_point(&Point2::new(x, y));
|
|
}
|
|
Vector2::new(x, y)
|
|
}
|
|
|
|
/// Step this sequence `t` seconds
|
|
pub(super) fn step(
|
|
&mut self,
|
|
res: &mut PhysStepResources,
|
|
wrapper: &mut PhysWrapper,
|
|
new: &mut NewObjects,
|
|
ship_data: &ShipData,
|
|
rigid_body_handle: RigidBodyHandle,
|
|
collider_handle: ColliderHandle,
|
|
) {
|
|
let rigid_body = wrapper.get_rigid_body(rigid_body_handle).unwrap().clone();
|
|
let collider = wrapper.get_collider(collider_handle).unwrap().clone();
|
|
let ship_content = ship_data.get_content();
|
|
let ship_pos = rigid_body.translation();
|
|
let ship_rot = rigid_body.rotation();
|
|
|
|
// The fraction of this collapse sequence that has been played
|
|
let frac_done = self.elapsed / self.total_length;
|
|
|
|
// TODO: slight random offset for event effects
|
|
|
|
// Trigger collapse events
|
|
for event in &ship_content.collapse.events {
|
|
match event {
|
|
CollapseEvent::Effect(event) => {
|
|
if (event.time > self.elapsed && event.time <= self.elapsed + res.t)
|
|
|| (event.time == 0.0 && self.elapsed == 0.0)
|
|
// ^^ Don't miss events scheduled at the very start of the sequence!
|
|
{
|
|
for spawner in &event.effects {
|
|
for _ in 0..spawner.count as usize {
|
|
let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
|
|
Vector2::new(pos.x, pos.y)
|
|
} else {
|
|
self.random_in_ship(&ship_content, &collider)
|
|
};
|
|
let pos = ship_pos + (ship_rot * pos);
|
|
|
|
new.effects.push(PhysEffect::new(
|
|
wrapper,
|
|
spawner.effect.clone(),
|
|
pos,
|
|
rigid_body_handle,
|
|
None,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create collapse effects
|
|
for spawner in &ship_content.collapse.effects {
|
|
// Probability of adding an effect instance this frame.
|
|
// The area of this function over [0, 1] should be 1.
|
|
let pdf = |x: f32| {
|
|
let f = 0.2;
|
|
let y = if x < (1.0 - f) {
|
|
let x = x / (1.0 - f);
|
|
(x * x + 0.2) * 1.8 - f
|
|
} else {
|
|
1.0
|
|
};
|
|
return y;
|
|
};
|
|
|
|
let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
|
|
|
|
if self.rng.gen_range(0.0..=1.0) <= p_add {
|
|
let pos = if let Some(pos) = spawner.pos {
|
|
Vector2::new(pos.x, pos.y)
|
|
} else {
|
|
self.random_in_ship(&ship_content, &collider)
|
|
};
|
|
|
|
// Position, adjusted for ship rotation
|
|
let pos = ship_pos + (ship_rot * pos);
|
|
new.effects.push(PhysEffect::new(
|
|
wrapper,
|
|
spawner.effect.clone(),
|
|
pos,
|
|
rigid_body_handle,
|
|
None,
|
|
));
|
|
}
|
|
}
|
|
|
|
self.elapsed += res.t;
|
|
}
|
|
}
|