From fe3680bbda5367de630d54557506c72e8892f97e Mon Sep 17 00:00:00 2001 From: Jerome Humbert Date: Mon, 26 Feb 2024 12:43:23 +0000 Subject: [PATCH] Make attractors regular modifiers (#286) Remove the built-in code and shader handling a fixed-size array of force field attractors/repulsors, and related types `ForceFieldSource` and `ForceFieldModifier`. Replace them with a new `ConformToSphereModifier` describing a single attractor/repulsor source, which can be repeated to add more sources. The new modifier is a regular update context modifier without any specific built-in codepath, making its handling a lot easier and cleaner, while also allowing any custom attraction force expression as well as customizing other parameters of the source in real time. Revamp the `force_field.rs` example to showcase this new modifier, and add a dedicated parameter tweaking panel to observe the effect of each parameter. Fix a panic in rendering code when no effect is present. This looks like a race condition; for now simply early out to prevent the panic. ```rust effects_meta.dr_indirect_bind_group.as_ref().unwrap() ``` --- CHANGELOG.md | 11 + Cargo.toml | 2 + docs/conform-to-sphere.png | Bin 0 -> 40326 bytes docs/conform-to-sphere.svg | 590 +++++++++++++++++++++++++++++++ examples/force_field.rs | 285 ++++++++++++--- src/asset.rs | 7 +- src/graph/expr.rs | 1 + src/lib.rs | 14 +- src/modifier/force.rs | 274 +++++++++----- src/modifier/mod.rs | 101 +----- src/properties.rs | 8 +- src/render/batch.rs | 5 +- src/render/force_field_code.wgsl | 83 ----- src/render/mod.rs | 55 +-- src/render/vfx_common.wgsl | 10 - src/render/vfx_init.wgsl | 2 +- src/render/vfx_render.wgsl | 2 +- src/render/vfx_update.wgsl | 2 +- 18 files changed, 1053 insertions(+), 399 deletions(-) create mode 100644 docs/conform-to-sphere.png create mode 100644 docs/conform-to-sphere.svg delete mode 100644 src/render/force_field_code.wgsl diff --git a/CHANGELOG.md b/CHANGELOG.md index e1c38b28..cf6ca9b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added a new `ScreenSpaceSizeModifier` which negates the effect of perspective projection, and makes the particle's size a pixel size in screen space, instead of a Bevy world unit size. This replaces the hard-coded behavior previously available on the `SetSizeModifier`. +- Added a new `ConformToSphereModifier` acting as an attractor applying a force toward a point (sphere center) to all particles in range, and making particles conform ("stick") to the sphere surface. + +### Changed + +- `ExprHandle` is now `#[repr(transparent)]`, which guarantees that `Option` has the same size as `ExprHandle` itself (4 bytes). +- `EffectProperties::set_if_changed()` now returns the `Mut` variable it takes as input, to allow subsequent calls. ### Removed - Removed the `screen_space_size` field from the `SetSizeModifier`. Use the new `ScreenSpaceSizeModifier` to use a screen-space size. +- Removed the built-in `ForceFieldSource` and associated `ForceFieldModifier`. Use the new `ConformToSphereModifer` instead. The behavior might change a bit as the conforming code is not strictly identical; use the `force_field.rs` example with the `examples_world_inspector` feature to tweak the parameters in real time and observe how they work and change the effect. + +### Fixed + +- Fixed a panic in rendering randomly occurring when no effect is present. ## [0.10.0] 2024-02-24 diff --git a/Cargo.toml b/Cargo.toml index 3b8a77f6..a0c1ab34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,8 @@ wgpu = "0.19.1" # For world inspector; required if "examples_world_inspector" is used. bevy-inspector-egui = "0.23" +bevy_egui = "0.25" +egui = "0.26" # For procedural texture generation in examples noise = "0.8" diff --git a/docs/conform-to-sphere.png b/docs/conform-to-sphere.png new file mode 100644 index 0000000000000000000000000000000000000000..5255bbef92cc3b1d9b9ae4526770374d2e7d79f6 GIT binary patch literal 40326 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 Xu)$1@2ulG!sYW?V8{vFrZ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + particle + origin + shell_half_thickness + + + radius + influence_dist + Area of influence + velocity + radialvelocity + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/force_field.rs b/examples/force_field.rs index 659a2f30..d7df8cf2 100644 --- a/examples/force_field.rs +++ b/examples/force_field.rs @@ -1,10 +1,10 @@ //! Force field example. //! -//! This example demonstrates how to use the `ForceFieldModifier` to simulate -//! attraction and repulsion forces. The example is interactif; left clicking -//! spawns particles that are repulsed by one point and attracted by another. -//! The attractor also conforms the particles that are close to a sphere around -//! it. +//! This example demonstrates how to use the `ConformToSphereModifier` to +//! simulate attraction and repulsion forces. The example is interactif; left +//! clicking spawns particles that are repulsed by one point and attracted by +//! another. The attractor also conforms the particles that are close to a +//! sphere around it. //! //! The example also demonstrates the `KillAabbModifier` and //! `KillSphereModifier`: a green "allow" box to which particles are confined, @@ -27,11 +27,6 @@ use bevy_inspector_egui::quick::WorldInspectorPlugin; use bevy_hanabi::prelude::*; -// use smooth_bevy_cameras::{ -// controllers::orbit::{OrbitCameraBundle, OrbitCameraController, -// OrbitCameraPlugin}, LookTransformPlugin, -// }; - fn main() -> Result<(), Box> { let mut wgpu_settings = WgpuSettings::default(); wgpu_settings @@ -59,15 +54,20 @@ fn main() -> Result<(), Box> { ..default() }), ) - //.add_plugins(LookTransformPlugin) - //.add_plugins(OrbitCameraPlugin::default()) .add_plugins(HanabiPlugin); #[cfg(feature = "examples_world_inspector")] - app.add_plugins(WorldInspectorPlugin::default()); + app.add_plugins(WorldInspectorPlugin::default()) + .init_resource::() + .register_type::() + .add_systems(Update, inspector::inspector_ui) + .add_systems(PostUpdate, inspector::apply_tweaks); app.add_systems(Startup, setup) - .add_systems(Update, (bevy::window::close_on_esc, update)) + .add_systems( + Update, + (bevy::window::close_on_esc, spawn_on_click, move_repulsor), + ) .run(); Ok(()) @@ -75,6 +75,118 @@ fn main() -> Result<(), Box> { const BALL_RADIUS: f32 = 0.05; +#[derive(Component)] +struct RepulsorMarker(pub bool); + +#[cfg(feature = "examples_world_inspector")] +mod inspector { + use bevy::{ + ecs::system::{Local, Resource}, + prelude::*, + reflect::Reflect, + window::PrimaryWindow, + }; + use bevy_egui::EguiContext; + use bevy_hanabi::EffectProperties; + use bevy_inspector_egui::{inspector_options::std_options::NumberDisplay, prelude::*}; + + use crate::RepulsorMarker; + + #[derive(Reflect, Resource, InspectorOptions)] + #[reflect(Resource, InspectorOptions)] + pub struct Configuration { + #[inspector(min = 1.0, max = 50.0, display = NumberDisplay::Slider)] + attraction_accel: f32, + #[inspector(min = 1.0, max = 20.0, display = NumberDisplay::Slider)] + max_attraction_speed: f32, + #[inspector(min = 1.0, max = 10.0, display = NumberDisplay::Slider)] + sticky_factor: f32, + #[inspector(min = 0.02, max = 2.0, display = NumberDisplay::Slider)] + shell_half_thickness: f32, + repulsor_enabled: bool, + #[inspector(min = -30.0, max = -0.1, display = NumberDisplay::Slider)] + repulsor_accel: f32, + } + + impl Default for Configuration { + fn default() -> Self { + Self { + attraction_accel: 20., + max_attraction_speed: 5., + sticky_factor: 2., + shell_half_thickness: 0.1, + repulsor_enabled: true, + repulsor_accel: -15., + } + } + } + + pub fn inspector_ui(world: &mut World, mut disabled: Local) { + let space_pressed = world + .resource::>() + .just_pressed(KeyCode::Space); + if space_pressed { + *disabled = !*disabled; + } + if *disabled { + return; + } + + let mut egui_context = world + .query_filtered::<&mut EguiContext, With>() + .single(world) + .clone(); + + egui::Window::new("ConformToSphereModifier").show(egui_context.get_mut(), |ui| { + egui::ScrollArea::both().show(ui, |ui| { + bevy_inspector_egui::bevy_inspector::ui_for_resource::(world, ui); + + ui.separator(); + ui.label("Press space to toggle"); + }); + }); + } + + pub fn apply_tweaks( + config: Res, + mut q_properties: Query<&mut EffectProperties>, + mut q_marker: Query<&mut RepulsorMarker>, + ) { + let mut properties = q_properties.single_mut(); + properties = EffectProperties::set_if_changed( + properties, + "attraction_accel", + config.attraction_accel.into(), + ); + properties = EffectProperties::set_if_changed( + properties, + "max_attraction_speed", + config.max_attraction_speed.into(), + ); + properties = EffectProperties::set_if_changed( + properties, + "sticky_factor", + config.sticky_factor.into(), + ); + properties = EffectProperties::set_if_changed( + properties, + "shell_half_thickness", + config.shell_half_thickness.into(), + ); + EffectProperties::set_if_changed( + properties, + "repulsor_accel", + config.repulsor_accel.into(), + ); + + let mut marker = q_marker.single_mut(); + marker.0 = config.repulsor_enabled; + } +} + +const ATTRACTOR_POS: Vec3 = Vec3::new(0.01, 0.0, 0.0); +const REPULSOR_POS: Vec3 = Vec3::new(0.3, 0.5, 0.0); + fn setup( mut commands: Commands, mut effects: ResMut>, @@ -91,9 +203,6 @@ fn setup( camera.projection = Projection::Orthographic(projection); commands.spawn(camera); - let attractor1_position = Vec3::new(0.01, 0.0, 0.0); - let attractor2_position = Vec3::new(1.0, 0.5, 0.0); - commands.spawn(PointLightBundle { transform: Transform::from_xyz(4.0, 5.0, 4.0), ..Default::default() @@ -103,6 +212,7 @@ fn setup( ..Default::default() }); + // Visual marker for attractor sphere commands.spawn(PbrBundle { mesh: meshes.add(Mesh::from(Sphere { radius: BALL_RADIUS * 2.0, @@ -112,22 +222,26 @@ fn setup( unlit: false, ..Default::default() }), - transform: Transform::from_translation(attractor1_position), + transform: Transform::from_translation(ATTRACTOR_POS), ..Default::default() }); - commands.spawn(PbrBundle { - mesh: meshes.add(Sphere { - radius: BALL_RADIUS * 1.0, - }), - material: materials.add(StandardMaterial { - base_color: Color::PURPLE, - unlit: false, + // Visual marker for repulsor sphere + commands.spawn(( + PbrBundle { + mesh: meshes.add(Sphere { + radius: BALL_RADIUS * 1.0, + }), + material: materials.add(StandardMaterial { + base_color: Color::PURPLE, + unlit: false, + ..Default::default() + }), + transform: Transform::from_translation(REPULSOR_POS), ..Default::default() - }), - transform: Transform::from_translation(attractor2_position), - ..Default::default() - }); + }, + RepulsorMarker(true), + )); // "allow" box commands.spawn(PbrBundle { @@ -159,9 +273,10 @@ fn setup( gradient.add_key(1.0, Vec4::new(0.0, 1.0, 1.0, 0.0)); // Prevent the spawner from immediately spawning on activation, and instead - // require a manual reset() call. + // require a manual reset() call. This allows controling spawning with a mouse + // button. let spawn_immediately = false; - + // Each mouse click spawns a burst of 30 particles, once. let spawner = Spawner::once(30.0.into(), spawn_immediately); let writer = ExprWriter::new(); @@ -169,13 +284,17 @@ fn setup( let age = writer.lit(0.).expr(); let init_age = SetAttributeModifier::new(Attribute::AGE, age); - let lifetime = writer.lit(5.).expr(); + let lifetime = writer.lit(10.).expr(); let init_lifetime = SetAttributeModifier::new(Attribute::LIFETIME, lifetime); + // Define the AABB within which particles are confined. Any particle attempting + // to leave gets killed. let center = writer.lit(Vec3::ZERO).expr(); let half_size = writer.lit(Vec3::new(3., 2., 3.)).expr(); let allow_zone = KillAabbModifier::new(center, half_size); + // Define the sphere into which particles cannot enter. Any particle attempting + // to enter gets killed. let center = writer.lit(Vec3::new(-2., -1., 0.)).expr(); let radius = writer.lit(0.6); let sqr_radius = (radius.clone() * radius).expr(); @@ -192,35 +311,47 @@ fn setup( speed: (writer.rand(ScalarType::Float) * writer.lit(0.2) + writer.lit(0.1)).expr(), }; + // Sphere repulsor pushing particles away. The acceleration is negative to + // repulse partices. + let repulsor_accel = writer.prop("repulsor_accel"); + let update_repulsor = ConformToSphereModifier { + origin: writer.prop("repulsor_position").expr(), + radius: writer.lit(BALL_RADIUS).expr(), + influence_dist: writer.lit(BALL_RADIUS * 10.).expr(), + attraction_accel: repulsor_accel.expr(), + max_attraction_speed: writer.lit(10.).expr(), + sticky_factor: None, + shell_half_thickness: None, + }; + + // Sphere attractor with conforming. The particles are attracted to the sphere + // surface, and tend to "stick" onto it. + let update_attractor = ConformToSphereModifier { + origin: writer.lit(ATTRACTOR_POS).expr(), + radius: writer.lit(BALL_RADIUS * 6.).expr(), + influence_dist: writer.lit(BALL_RADIUS * 100.).expr(), + attraction_accel: writer.prop("attraction_accel").expr(), + max_attraction_speed: writer.prop("max_attraction_speed").expr(), + sticky_factor: Some(writer.prop("sticky_factor").expr()), + shell_half_thickness: Some(writer.prop("shell_half_thickness").expr()), + }; + // Force field effects let effect = effects.add( EffectAsset::new(32768, spawner, writer.finish()) .with_name("force_field") + .with_property("repulsor_position", Value::Vector(REPULSOR_POS.into())) + .with_property("attraction_accel", Value::Scalar(20.0.into())) + .with_property("max_attraction_speed", Value::Scalar(5.0.into())) + .with_property("sticky_factor", Value::Scalar(2.0.into())) + .with_property("shell_half_thickness", Value::Scalar(0.1.into())) + .with_property("repulsor_accel", Value::Scalar((-15.0).into())) .init(init_pos) .init(init_vel) .init(init_age) .init(init_lifetime) - .update(ForceFieldModifier::new(vec![ - ForceFieldSource { - position: attractor2_position, - max_radius: 1000000.0, - min_radius: BALL_RADIUS * 6.0, - // a negative mass produces a repulsive force instead of an attractive one - mass: -1.5, - // linear force: proportional to 1 / distance - force_exponent: 1.0, - conform_to_sphere: true, - }, - ForceFieldSource { - position: attractor1_position, - max_radius: 1000000.0, - min_radius: BALL_RADIUS * 6.0, - mass: 3.0, - // quadratic force: proportional to 1 / distance^2 - force_exponent: 2.0, - conform_to_sphere: true, - }, - ])) + .update(update_attractor) + .update(update_repulsor) .update(allow_zone) .update(deny_zone) .render(SizeOverLifetimeModifier { @@ -230,10 +361,31 @@ fn setup( .render(ColorOverLifetimeModifier { gradient }), ); - commands.spawn(ParticleEffectBundle::new(effect).with_spawner(spawner)); + commands.spawn(( + ParticleEffectBundle::new(effect).with_spawner(spawner), + // Note: regression as of 0.10, we need to explicitly add this component with the correct + // properties. + EffectProperties::default().with_properties([ + ( + "repulsor_position".to_string(), + Value::Vector(REPULSOR_POS.into()), + ), + ("attraction_accel".to_string(), Value::Scalar(20.0.into())), + ( + "max_attraction_speed".to_string(), + Value::Scalar(5.0.into()), + ), + ("sticky_factor".to_string(), Value::Scalar(2.0.into())), + ( + "shell_half_thickness".to_string(), + Value::Scalar(0.1.into()), + ), + ("repulsor_accel".to_string(), Value::Scalar((-15.0).into())), + ]), + )); } -fn update( +fn spawn_on_click( mut q_effect: Query<(&mut EffectSpawner, &mut Transform), Without>, mouse_button_input: Res>, camera_query: Query<(&Camera, &GlobalTransform), With>, @@ -264,3 +416,26 @@ fn update( } } } + +fn move_repulsor( + time: Res