Skip to content

Commit

Permalink
[Breaking Change] Make emitter shapes easier to construct (#38)
Browse files Browse the repository at this point in the history
* Make emitter shapes easier to construct

* Fix up examples

* Add From impl for Line and CircleSegment

* Update examples

* Apply suggestions from code review

Co-authored-by: Johan Klokkhammer Helsing <johanhelsing@gmail.com>

---------

Co-authored-by: Johan Klokkhammer Helsing <johanhelsing@gmail.com>
  • Loading branch information
abnormalbrain and johanhelsing authored Mar 8, 2023
1 parent 094e7ac commit eb0e4d5
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 66 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fn spawn_particle_system(mut commands: Commands, asset_server: Res<AssetServer>)
.spawn(ParticleSystemBundle {
particle_system: ParticleSystem {
max_particles: 10_000,
texture: ParticuleTexture::Sprite(asset_server.load("my_particle.png")),
texture: ParticleTexture::Sprite(asset_server.load("my_particle.png")),
spawn_rate_per_second: 25.0.into(),
initial_speed: JitteredValue::jittered(3.0, -1.0..1.0),
lifetime: JitteredValue::jittered(8.0, -2.0..2.0),
Expand Down
9 changes: 5 additions & 4 deletions examples/directional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use bevy::{
use bevy_app::PluginGroup;
use bevy_asset::AssetServer;
use bevy_particle_systems::{
JitteredValue, ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture,
Playing,
CircleSegment, JitteredValue, ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin,
ParticleTexture, Playing,
};

fn main() {
Expand Down Expand Up @@ -38,11 +38,12 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
spawn_rate_per_second: 25.0.into(),
initial_speed: JitteredValue::jittered(70.0, -3.0..3.0),
lifetime: JitteredValue::jittered(5.0, -1.0..1.0),
emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment {
emitter_shape: CircleSegment {
radius: 10.0.into(),
opening_angle: std::f32::consts::PI,
direction_angle: std::f32::consts::PI / 2.0,
},
}
.into(),
looping: true,
scale: 0.07.into(),
system_duration_seconds: 5.0,
Expand Down
19 changes: 10 additions & 9 deletions examples/local_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use bevy_math::Quat;
use bevy_time::Time;

use bevy_particle_systems::{
ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace, ParticleSystem,
ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing,
CircleSegment, ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace,
ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing,
};

#[derive(Debug, Component)]
Expand All @@ -39,11 +39,11 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(ParticleSystemBundle {
particle_system: ParticleSystem {
max_particles: 500,
emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment {
emitter_shape: CircleSegment {
opening_angle: std::f32::consts::PI * 0.25,
direction_angle: 0.0,
radius: 0.0.into(),
},
..Default::default()
}
.into(),
texture: ParticleTexture::Sprite(asset_server.load("px.png")),
spawn_rate_per_second: 35.0.into(),
initial_speed: JitteredValue::jittered(25.0, 0.0..5.0),
Expand All @@ -70,11 +70,12 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(ParticleSystemBundle {
particle_system: ParticleSystem {
max_particles: 500,
emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment {
emitter_shape: CircleSegment {
opening_angle: std::f32::consts::PI * 0.25,
direction_angle: std::f32::consts::PI,
radius: 0.0.into(),
},
..Default::default()
}
.into(),
texture: ParticleTexture::Sprite(asset_server.load("px.png")),
spawn_rate_per_second: 35.0.into(),
initial_speed: JitteredValue::jittered(25.0, 0.0..5.0),
Expand Down
14 changes: 6 additions & 8 deletions examples/shape_emitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use bevy_app::PluginGroup;
use bevy_asset::AssetServer;

use bevy_particle_systems::{
ColorOverTime, ColorPoint, EmitterShape, Gradient, JitteredValue, ParticleSystem,
ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing,
CircleSegment, ColorOverTime, ColorPoint, EmitterShape, Gradient, JitteredValue,
ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing,
};

fn main() {
Expand Down Expand Up @@ -46,10 +46,7 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
ColorPoint::new(Color::RED, 0.5),
ColorPoint::new(Color::rgba(0.0, 0.0, 1.0, 0.0), 1.0),
])),
emitter_shape: EmitterShape::Line {
length: 200.0,
angle: std::f32::consts::FRAC_PI_4.into(),
},
emitter_shape: EmitterShape::line(200.0, std::f32::consts::FRAC_PI_4),
looping: true,
rotate_to_movement_direction: true,
initial_rotation: (-90.0_f32).to_radians().into(),
Expand All @@ -76,11 +73,12 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
ColorPoint::new(Color::RED, 0.5),
ColorPoint::new(Color::rgba(0.0, 0.0, 1.0, 0.0), 1.0),
])),
emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment {
emitter_shape: CircleSegment {
radius: 10.0.into(),
opening_angle: std::f32::consts::PI,
direction_angle: std::f32::consts::FRAC_PI_4,
},
}
.into(),
looping: true,
rotate_to_movement_direction: true,
initial_rotation: (-90.0_f32).to_radians().into(),
Expand Down
14 changes: 8 additions & 6 deletions examples/time_scaling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use bevy::{
};
use bevy_asset::AssetServer;
use bevy_particle_systems::{
ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace, ParticleSystem,
ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing,
CircleSegment, ColorOverTime, ColorPoint, Gradient, JitteredValue, ParticleSpace,
ParticleSystem, ParticleSystemBundle, ParticleSystemPlugin, ParticleTexture, Playing,
};
use bevy_time::Time;
fn main() {
Expand All @@ -30,11 +30,12 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(ParticleSystemBundle {
particle_system: ParticleSystem {
max_particles: 500,
emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment {
emitter_shape: CircleSegment {
direction_angle: 0.0,
opening_angle: std::f32::consts::PI * 0.25,
radius: 0.0.into(),
},
}
.into(),
texture: ParticleTexture::Sprite(asset_server.load("px.png")),
spawn_rate_per_second: 35.0.into(),
initial_speed: JitteredValue::jittered(25.0, 0.0..5.0),
Expand All @@ -61,11 +62,12 @@ fn startup_system(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(ParticleSystemBundle {
particle_system: ParticleSystem {
max_particles: 500,
emitter_shape: bevy_particle_systems::EmitterShape::CircleSegment {
emitter_shape: CircleSegment {
opening_angle: std::f32::consts::PI * 0.25,
direction_angle: std::f32::consts::PI,
radius: 0.0.into(),
},
}
.into(),
texture: ParticleTexture::Sprite(asset_server.load("px.png")),
spawn_rate_per_second: 35.0.into(),
initial_speed: JitteredValue::jittered(25.0, 0.0..5.0),
Expand Down
6 changes: 1 addition & 5 deletions src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,7 @@ impl Default for ParticleSystem {
texture: ParticleTexture::Sprite(Handle::default()),
rescale_texture: None,
spawn_rate_per_second: 5.0.into(),
emitter_shape: EmitterShape::CircleSegment {
opening_angle: std::f32::consts::TAU,
direction_angle: 0.0,
radius: 0.0.into(),
},
emitter_shape: EmitterShape::default(),
initial_speed: 1.0.into(),
acceleration: 0.0.into(),
lifetime: 5.0.into(),
Expand Down
153 changes: 120 additions & 33 deletions src/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,143 @@ use bevy_transform::prelude::Transform;
use rand::seq::SliceRandom;
use rand::{prelude::ThreadRng, Rng};

/// Describes an oriented segment of a circle with a given radius.
#[derive(Debug, Clone, Reflect, FromReflect)]
pub struct CircleSegment {
/// The shape of the emitter, defined in radians.
///
/// The default is `2 * PI`, which results particles going in all directions in a circle.
/// Reducing the value reduces the possible emitting directions. [`std::f32::consts::PI`] will emit particles
/// in a semi-circle.
pub opening_angle: f32,

/// The rotation angle of the emitter, defined in radian.
///
/// Zero indicates straight to the right in the X direction. [`std::f32::consts::PI`] indicates straight left in the X direction.
pub direction_angle: f32,

/// The radius around the particle systems location that particles will spawn in.
///
/// Setting this to zero will make all particles start at the same position.
/// Setting this to a non-jittered constant will make particles spawn exactly that distance away from the
/// center position. Jitter will allow particles to spawn in a range.
pub radius: JitteredValue,
}

impl Default for CircleSegment {
fn default() -> Self {
Self {
opening_angle: std::f32::consts::TAU,
direction_angle: 0.0,
radius: 0.0.into(),
}
}
}

impl From<CircleSegment> for EmitterShape {
fn from(segment: CircleSegment) -> EmitterShape {
EmitterShape::CircleSegment(segment)
}
}

/// Defines a line along which particles will be spawned.
#[derive(Debug, Clone, Reflect, FromReflect)]
pub struct Line {
/// The lenth of the line
pub length: f32,

/// The rotation angle of the emitter, defined in radian.
///
/// Zero indicates straight to the right in the +X direction. [`std::f32::consts::PI`] indicates straight left in the -X direction.
pub angle: JitteredValue,
}

impl Default for Line {
fn default() -> Self {
Self {
length: 1.0,
angle: 0.0.into(),
}
}
}

impl From<Line> for EmitterShape {
fn from(line: Line) -> EmitterShape {
EmitterShape::Line(line)
}
}

/// Describes the shape on which new particles get spawned
///
/// For convenience, these can also be created directly from
/// [`CircleSegment`] and [`Line`] instances, or using [`EmitterShape::line`] or
/// [`EmitterShape::circle`]
///
/// # Examples
///
/// ```rust
/// # use bevy_particle_systems::values::{CircleSegment, EmitterShape, Line};
/// # use bevy_particle_systems::ParticleSystem;
/// let particle_system = ParticleSystem {
/// emitter_shape: CircleSegment::default().into(),
/// // ...
/// ..Default::default()
/// };
/// ```
#[derive(Debug, Clone, Reflect, FromReflect)]
pub enum EmitterShape {
/// A oriented segment of a circle at a given radius
CircleSegment {
/// The shape of the emitter, defined in radian.
///
/// The default is [`std::f32::consts::TAU`], which results particles going in all directions in a circle.
/// Reducing the value reduces the possible emitting directions. [`std::f32::consts::PI`] will emit particles
/// in a semi-circle.
opening_angle: f32,

/// The rotation angle of the emitter, defined in radian.
///
/// Zero indicates straight to the right in the X direction. [`std::f32::consts::PI`] indicates straight left in the X direction.
direction_angle: f32,

/// The radius around the particle systems location that particles will spawn in.
///
/// Setting this to zero will make all particles start at the same position.
/// Setting this to a non-jittered constant will make particles spawn exactly that distance away from the
/// center position. Jitter will allow particles to spawn in a range.
radius: JitteredValue,
},
/// An oriented segment of a circle with a given radius
CircleSegment(CircleSegment),
/// Emit particles from a 2d line at an angle
Line {
/// The lenth of the line
length: f32,

/// The rotation angle of the emitter, defined in radian.
///
/// Zero indicates straight to the right in the +X direction. [`std::f32::consts::PI`] indicates straight left in the -X direction.
angle: JitteredValue,
},
Line(Line),
}

impl EmitterShape {
/// Defines a circular emitter with the specified radius.
///
/// See [`CircleSegment`] for more details.
pub fn circle<T>(radius: T) -> Self
where
T: Into<JitteredValue>,
{
Self::CircleSegment(CircleSegment {
radius: radius.into(),
..Default::default()
})
}

/// Creates a new Line emitter with the specified length and angle in radian.
///
/// See [`Line`] for more details.
pub fn line<T>(length: f32, angle: T) -> Self
where
T: Into<JitteredValue>,
{
Self::Line(Line {
length,
angle: angle.into(),
})
}

/// Samples a random starting transform from the Emitter shape
///
/// The returned transform describes the position and direction of movement of the newly spawned particle.
/// (Note: The actual angle of the new particle might get overridden for a [`crate::components::ParticleSystem`] e.g if
/// `rotate_to_movement_direction` is false.)
pub fn sample(&self, rng: &mut ThreadRng) -> Transform {
match self {
EmitterShape::CircleSegment {
EmitterShape::CircleSegment(CircleSegment {
opening_angle,
radius,
direction_angle,
} => {
}) => {
let radian: f32 = rng.gen_range(-0.5..0.5) * opening_angle + direction_angle;
let direction = Vec3::new(radian.cos(), radian.sin(), 0.0);

let delta = direction * radius.get_value(rng);
Transform::from_translation(delta).with_rotation(Quat::from_rotation_z(radian))
}
EmitterShape::Line { length, angle } => {
EmitterShape::Line(Line { length, angle }) => {
let angle = angle.get_value(rng);
let distance: f32 = rng.gen_range(-0.5..0.5) * length;

Expand All @@ -76,6 +157,12 @@ impl EmitterShape {
}
}

impl Default for EmitterShape {
fn default() -> Self {
Self::CircleSegment(CircleSegment::default())
}
}

/// A value that will be chosen from a set of possible values when read.
///
/// ## Examples
Expand Down

0 comments on commit eb0e4d5

Please sign in to comment.