From 9d3e7f427092d9d3ca129900ed5f705955200bb6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Thu, 30 Dec 2021 22:56:38 -0500 Subject: [PATCH 01/11] Initial import from bevy_prototype_particles --- Cargo.toml | 11 + crates/bevy_internal/Cargo.toml | 1 + crates/bevy_internal/src/default_plugins.rs | 3 + crates/bevy_internal/src/lib.rs | 6 + crates/bevy_internal/src/prelude.rs | 4 + crates/bevy_particles/Cargo.toml | 27 + crates/bevy_particles/src/emitter.rs | 200 ++++++ crates/bevy_particles/src/lib.rs | 57 ++ crates/bevy_particles/src/material.rs | 98 +++ crates/bevy_particles/src/modifiers.rs | 34 + crates/bevy_particles/src/particle.wgsl | 96 +++ crates/bevy_particles/src/particles.rs | 334 ++++++++++ crates/bevy_particles/src/prelude.rs | 4 + crates/bevy_particles/src/render.rs | 648 ++++++++++++++++++++ examples/particles/fireball.rs | 51 ++ 15 files changed, 1574 insertions(+) create mode 100644 crates/bevy_particles/Cargo.toml create mode 100644 crates/bevy_particles/src/emitter.rs create mode 100644 crates/bevy_particles/src/lib.rs create mode 100644 crates/bevy_particles/src/material.rs create mode 100644 crates/bevy_particles/src/modifiers.rs create mode 100644 crates/bevy_particles/src/particle.wgsl create mode 100644 crates/bevy_particles/src/particles.rs create mode 100644 crates/bevy_particles/src/prelude.rs create mode 100644 crates/bevy_particles/src/render.rs create mode 100644 examples/particles/fireball.rs diff --git a/Cargo.toml b/Cargo.toml index 76b6199116e46..acb114caaba18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ dynamic = ["bevy_dylib"] # Rendering support render = [ "bevy_internal/bevy_core_pipeline", + "bevy_internal/bevy_particles", "bevy_internal/bevy_pbr", "bevy_internal/bevy_gltf", "bevy_internal/bevy_render", @@ -45,6 +46,11 @@ render = [ # Optional bevy crates bevy_audio = ["bevy_internal/bevy_audio"] bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] +bevy_render = ["bevy_internal/bevy_render"] +bevy_text = ["bevy_internal/bevy_text"] +bevy_particles = ["bevy_internal/bevy_particles"] +bevy_pbr = ["bevy_internal/bevy_pbr"] +bevy_sprite = ["bevy_internal/bevy_sprite"] bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] bevy_gltf = ["bevy_internal/bevy_gltf"] @@ -244,6 +250,11 @@ path = "examples/app/thread_pool_resources.rs" name = "without_winit" path = "examples/app/without_winit.rs" +# Particle Systems +[[example]] +name = "particles-fireball" +path = "examples/particles/fireball.rs" + # Assets [[example]] name = "asset_loading" diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 064e14b874da1..9962d1cdc7c62 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -72,6 +72,7 @@ bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.5.0" } bevy_render = { path = "../bevy_render", optional = true, version = "0.5.0" } bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" } bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.5.0" } +bevy_particles = { path = "../bevy_particles", optional = true, version = "0.5.0" } bevy_text = { path = "../bevy_text", optional = true, version = "0.5.0" } bevy_ui = { path = "../bevy_ui", optional = true, version = "0.5.0" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index c4a102588d984..a227755bda0a7 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -62,6 +62,9 @@ impl PluginGroup for DefaultPlugins { #[cfg(feature = "bevy_gilrs")] group.add(bevy_gilrs::GilrsPlugin::default()); + + #[cfg(feature = "bevy_particles")] + group.add(bevy_particles::ParticlePlugin::default()); } } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index dd7d9d58cd11c..8ecde892e5fe1 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -104,6 +104,12 @@ pub mod gltf { pub use bevy_gltf::*; } +#[cfg(feature = "bevy_particles")] +pub mod particles { + //! Particle systems. + pub use bevy_particles::*; +} + #[cfg(feature = "bevy_pbr")] pub mod pbr { //! Physically based rendering. diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index bddc7dee7dc71..c92affc497389 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -42,3 +42,7 @@ pub use crate::dynamic_plugin::*; #[doc(hidden)] #[cfg(feature = "bevy_gilrs")] pub use crate::gilrs::*; + +#[doc(hidden)] +#[cfg(feature = "bevy_particles")] +pub use crate::particles::prelude::*; diff --git a/crates/bevy_particles/Cargo.toml b/crates/bevy_particles/Cargo.toml new file mode 100644 index 0000000000000..b342f90b6fa48 --- /dev/null +++ b/crates/bevy_particles/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "bevy_particles" +version = "0.5.0" +edition = "2021" +description = "Adds particle systems to Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] +rand = { version = "0.8", features = ["small_rng"] } +wgpu = "0.12" +bytemuck = { version = "1.7.0", features = ["derive"] } +bitflags = "1.2" + +bevy_app = { path = "../bevy_app" } +bevy_asset = { path = "../bevy_asset" } +bevy_crevice = { path = "../bevy_crevice" } +bevy_core = { path = "../bevy_core" } +bevy_core_pipeline = { path = "../bevy_core_pipeline" } +bevy_ecs = { path = "../bevy_ecs" } +bevy_math = { path = "../bevy_math" } +bevy_reflect = { path = "../bevy_reflect" } +bevy_render = { path = "../bevy_render" } +bevy_tasks = { path = "../bevy_tasks" } +bevy_transform = { path = "../bevy_transform" } \ No newline at end of file diff --git a/crates/bevy_particles/src/emitter.rs b/crates/bevy_particles/src/emitter.rs new file mode 100644 index 0000000000000..2dbd5d00ea4a8 --- /dev/null +++ b/crates/bevy_particles/src/emitter.rs @@ -0,0 +1,200 @@ +use crate::particles::{ParticleParams, Particles}; +use bevy_core::Time; +use bevy_ecs::prelude::*; +use bevy_math::*; +use bevy_render::color::Color; +use bevy_tasks::ComputeTaskPool; +use bevy_transform::prelude::*; +use rand::Rng; +use std::{ops::Range, time::Duration}; + +#[derive(Debug, Clone)] +pub struct EmitterBurst { + pub count: Range, + pub wait: Duration, +} + +pub trait EmitterModifier: Send + Sync + 'static { + fn modify(&mut self, particle: &mut ParticleParams); +} + +#[derive(Component)] +pub struct ParticleEmitter { + next_burst: Duration, + burst_idx: usize, + default_params: ParticleParams, + default_speed: f32, + bursts: Vec, + shape: EmitterShape, + modifiers: Vec>, +} + +impl ParticleEmitter { + pub fn sphere(center: Vec3, radius: f32) -> ParticleEmitterBuilder { + ParticleEmitterBuilder::new(EmitterShape::Sphere { center, radius }) + } + + pub fn hemisphere(center: Vec3, radius: f32) -> ParticleEmitterBuilder { + ParticleEmitterBuilder::new(EmitterShape::Hemisphere { center, radius }) + } +} + +pub struct ParticleEmitterBuilder { + default_params: ParticleParams, + default_speed: f32, + bursts: Vec, + shape: EmitterShape, + modifiers: Vec>, +} + +impl ParticleEmitterBuilder { + fn new(shape: EmitterShape) -> Self { + Self { + default_params: ParticleParams { + size: 1.0, + color: Color::WHITE, + lifetime: 5.0, + ..Default::default() + }, + default_speed: 0.0, + bursts: Vec::new(), + shape, + modifiers: Vec::new(), + } + } + + pub fn add_burst(mut self, burst: EmitterBurst) -> Self { + self.bursts.push(burst); + self + } + + pub fn add_modifier(mut self, modifier: impl EmitterModifier) -> Self { + self.modifiers.push(Box::new(modifier)); + self + } + + pub fn with_default_speed(mut self, speed: f32) -> Self { + self.default_speed = speed; + self + } + + pub fn with_default_color(mut self, color: Color) -> Self { + self.default_params.color = color; + self + } + + pub fn with_default_lifetime(mut self, lifetime: f32) -> Self { + self.default_params.lifetime = lifetime; + self + } + + pub fn with_default_size(mut self, size: f32) -> Self { + self.default_params.size = size; + self + } + + pub fn build(self) -> ParticleEmitter { + ParticleEmitter { + next_burst: Duration::from_millis(0), + burst_idx: 0, + default_params: self.default_params, + default_speed: self.default_speed, + bursts: self.bursts, + shape: self.shape, + modifiers: self.modifiers, + } + } +} + +pub enum EmitterShape { + Sphere { center: Vec3, radius: f32 }, + Hemisphere { center: Vec3, radius: f32 }, +} + +impl EmitterShape { + pub fn sample(&self, rng: &mut impl Rng, params: &mut ParticleParams) { + match self { + Self::Sphere { radius, center } => Self::sample_sphere(*center, *radius, rng, params), + Self::Hemisphere { radius, center } => { + Self::sample_hemisphere(*center, *radius, rng, params) + } + } + } + + fn sample_sphere(center: Vec3, radius: f32, rng: &mut impl Rng, params: &mut ParticleParams) { + let position = sample_sphere(rng); + let r = rng.gen_range(0.0..1.0); + params.position = position * r * radius + center; + params.velocity = position; + } + + fn sample_hemisphere( + center: Vec3, + radius: f32, + rng: &mut impl Rng, + params: &mut ParticleParams, + ) { + let mut position = sample_sphere(rng); + position.y = f32::abs(position.y); + let r = rng.gen_range(0.0..1.0); + params.position = position * r * radius + center; + params.velocity = position; + } +} + +pub fn emit_particles( + time: Res