use std::{
fmt::{Debug, Formatter, Result},
time::Duration,
};
use bevy::{ecs::system::EntityCommands, utils::Instant};
use crate::{
position::{DefaultLayer, PxLayer},
prelude::*,
set::PxSet,
};
const TIME_OFFSET: Duration = Duration::from_secs(60 * 60 * 24);
pub(crate) fn plug<L: PxLayer>(app: &mut App) {
app.add_systems(
PostUpdate,
(
(
(simulate_emitters::<L>, insert_emitter_time),
(apply_deferred, update_emitters::<L>)
.chain()
.in_set(PxSet::UpdateEmitters),
)
.chain(),
despawn_particles,
),
);
}
#[derive(Clone, Component, Copy, Debug, Deref, DerefMut)]
pub struct PxParticleLifetime(pub Duration);
impl Default for PxParticleLifetime {
fn default() -> Self {
Self(Duration::from_secs(1))
}
}
impl From<Duration> for PxParticleLifetime {
fn from(duration: Duration) -> Self {
Self(duration)
}
}
#[derive(Debug)]
pub struct PxEmitterFrequency {
min: Duration,
max: Duration,
next: Option<Duration>,
}
impl Default for PxEmitterFrequency {
fn default() -> Self {
Self::single(Duration::from_secs(1))
}
}
impl PxEmitterFrequency {
pub fn new(min: Duration, max: Duration) -> Self {
Self {
min,
max,
next: None,
}
}
pub fn single(duration: Duration) -> Self {
Self {
min: duration,
max: duration,
next: None,
}
}
fn next(&mut self, rng: &mut Rng) -> Duration {
if let Some(duration) = self.next {
duration
} else {
let duration = (self.max - self.min).mul_f32(rng.f32()) + self.min;
self.next = Some(duration);
duration
}
}
fn update_next(&mut self, rng: &mut Rng) -> Duration {
let duration = self.next(rng);
self.next = None;
self.next(rng);
duration
}
}
#[derive(Debug, Default, Eq, PartialEq)]
pub enum PxEmitterSimulation {
#[default]
None,
Simulate,
}
#[derive(Component)]
#[require(PxAnchor, DefaultLayer, PxCanvas, PxParticleLifetime, PxVelocity)]
pub struct PxEmitter {
pub sprites: Vec<Handle<PxSpriteAsset>>,
pub range: IRect,
pub frequency: PxEmitterFrequency,
pub simulation: PxEmitterSimulation,
pub on_spawn: Box<dyn Fn(&mut EntityCommands) + Send + Sync>,
}
impl Default for PxEmitter {
fn default() -> Self {
Self {
sprites: Vec::new(),
range: default(),
frequency: default(),
simulation: default(),
on_spawn: Box::new(|_| ()),
}
}
}
impl Debug for PxEmitter {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.debug_struct("PxEmitter")
.field("sprites", &self.sprites)
.field("range", &self.range)
.field("frequency", &self.frequency)
.field("simulation", &self.simulation)
.field("on_spawn", &())
.finish()
}
}
#[derive(Component, Debug, Deref, DerefMut)]
struct PxEmitterStart(Instant);
#[derive(Component, Debug, Deref, DerefMut)]
struct PxParticleStart(Instant);
impl Default for PxParticleStart {
fn default() -> Self {
Self(Instant::now())
}
}
impl From<Instant> for PxParticleStart {
fn from(duration: Instant) -> Self {
Self(duration)
}
}
#[derive(Bundle, Default)]
struct PxParticleBundle {
position: PxSubPosition,
velocity: PxVelocity,
start: PxParticleStart,
lifetime: PxParticleLifetime,
}
fn simulate_emitters<L: PxLayer>(
mut commands: Commands,
emitters: Query<
(
&PxEmitter,
&PxAnchor,
&L,
&PxCanvas,
&PxParticleLifetime,
&PxVelocity,
),
Added<PxEmitter>,
>,
time: Res<Time<Real>>,
mut rng: ResMut<GlobalRng>,
) {
for (emitter, anchor, layer, canvas, lifetime, velocity) in &emitters {
if emitter.simulation != PxEmitterSimulation::Simulate {
continue;
}
let current_time = time.last_update().unwrap_or_else(|| time.startup()) + TIME_OFFSET;
let mut simulated_time = current_time;
while simulated_time + **lifetime >= current_time {
let position = IVec2::new(
rng.i32(emitter.range.min.x..=emitter.range.max.x),
rng.i32(emitter.range.min.y..=emitter.range.max.y),
)
.as_vec2()
+ **velocity * (current_time - simulated_time).as_secs_f32();
(emitter.on_spawn)(&mut commands.spawn((
PxSprite(rng.sample(&emitter.sprites).unwrap().clone()),
PxPosition::from(IVec2::new(
position.x.round() as i32,
position.y.round() as i32,
)),
*anchor,
layer.clone(),
*canvas,
PxSubPosition::from(position),
*velocity,
PxParticleStart::from(simulated_time),
*lifetime,
Name::new("Particle"),
)));
let Some(new_time) = simulated_time.checked_sub(
(emitter.frequency.max - emitter.frequency.min).mul_f32(rng.f32())
+ emitter.frequency.min,
) else {
break;
};
simulated_time = new_time;
}
}
}
fn insert_emitter_time(
mut commands: Commands,
emitters: Query<Entity, Added<PxEmitter>>,
time: Res<Time<Real>>,
mut rng: ResMut<GlobalRng>,
) {
for emitter in &emitters {
commands.entity(emitter).insert((
PxEmitterStart(time.last_update().unwrap_or_else(|| time.startup()) + TIME_OFFSET),
RngComponent::from(&mut rng),
));
}
}
fn update_emitters<L: PxLayer>(
mut commands: Commands,
mut emitters: Query<(
&mut PxEmitter,
&PxAnchor,
&L,
&PxCanvas,
&PxParticleLifetime,
&PxVelocity,
&mut PxEmitterStart,
&mut RngComponent,
)>,
time: Res<Time<Real>>,
) {
for (mut emitter, anchor, layer, canvas, lifetime, velocity, mut start, mut rng) in
&mut emitters
{
if time.last_update().unwrap_or_else(|| time.startup()) + TIME_OFFSET - **start
< emitter.frequency.next(rng.get_mut())
{
continue;
}
**start += emitter.frequency.update_next(rng.get_mut());
let position = IVec2::new(
rng.i32(emitter.range.min.x..=emitter.range.max.x),
rng.i32(emitter.range.min.y..=emitter.range.max.y),
);
(emitter.on_spawn)(&mut commands.spawn((
PxSprite(rng.sample(&emitter.sprites).unwrap().clone()),
PxPosition::from(position),
*anchor,
layer.clone(),
*canvas,
PxSubPosition::from(position.as_vec2()),
*velocity,
PxParticleStart::from(
time.last_update().unwrap_or_else(|| time.startup()) + TIME_OFFSET,
),
*lifetime,
Name::new("Particle"),
)));
}
}
fn despawn_particles(
mut commands: Commands,
particles: Query<(Entity, &PxParticleLifetime, &PxParticleStart)>,
time: Res<Time<Real>>,
) {
for (particle, lifetime, start) in &particles {
if time.last_update().unwrap_or_else(|| time.startup()) + TIME_OFFSET - **start
>= **lifetime
{
commands.entity(particle).despawn();
}
}
}