Skip to content

Commit

Permalink
Added 2D camera support (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
hayashi-stl committed Apr 11, 2022
1 parent 61a8605 commit 0e1df4d
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 39 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
build:
strategy:
matrix:
dimensions: [2d, 3d]
toolchain: [stable, nightly]
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
Expand All @@ -28,7 +29,7 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-build-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.toml') }}
key: ${{ runner.os }}-cargo-build-${{ matrix.dimensions }}-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.toml') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.toolchain }}
Expand All @@ -45,12 +46,15 @@ jobs:
sudo apt install -y xvfb libegl1-mesa libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers
if: runner.os == 'linux'
- name: Build & run tests
run: cargo test
run: cargo test --features ${{ matrix.dimensions }}
env:
CARGO_INCREMENTAL: 0

coverage:
name: Coverage
strategy:
matrix:
dimensions: [2d, 3d]
runs-on: ubuntu-latest
permissions:
actions: read
Expand All @@ -67,7 +71,7 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-build-stable-${{ hashFiles('**/Cargo.toml') }}
key: ${{ runner.os }}-cargo-build-${{ matrix.dimensions }}-stable-${{ hashFiles('**/Cargo.toml') }}
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
Expand All @@ -87,7 +91,7 @@ jobs:
RUST_BACKTRACE=1 cargo install --version 0.19.1 cargo-tarpaulin
- name: Generate code coverage
run: |
RUST_BACKTRACE=1 cargo tarpaulin --verbose --timeout 120 --out Lcov --workspace
RUST_BACKTRACE=1 cargo tarpaulin --verbose --timeout 120 --out Lcov --workspace --features ${{ matrix.dimensions }}
ls -la
- name: Upload code coverage
uses: coverallsapp/github-action@master
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `ForceFieldModifier` to allow attraction or repulsion from point sources.
- Add `ForceFieldParam` in both the modifiers and the particle update shader.
- Add `force_field` example showcasing a repulsor, an attractor and the conforming to sphere functionality.
- Add rendering with a 2D camera.

### Changed

- Renamed the `ToWgslFloat` trait into `ToWgslString`, and its `to_float_string()` method into `to_wgsl_string()`. Also made the trait public.
- Position modifiers now use `Value<f32>` for velocity to allow for random velocity.
- Either the "3d" feature or the "2d" feature must be enabled.

### Fixed

Expand Down
22 changes: 15 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ license = "MIT OR Apache-2.0"
readme = "README.md"
exclude = ["examples/*.gif", ".github"]

[features]
3d = []
2d = []

[dependencies]
bytemuck = "1.5"
rand = "0.8"
Expand All @@ -32,31 +36,35 @@ smooth-bevy-cameras = "0.2"

[[example]]
name = "spawn"
required-features = [ "bevy/bevy_winit" ]
required-features = [ "bevy/bevy_winit", "3d" ]

[[example]]
name = "random"
required-features = [ "bevy/bevy_winit" ]
required-features = [ "bevy/bevy_winit", "3d" ]

[[example]]
name = "gradient"
required-features = [ "bevy/bevy_winit", "bevy/png" ]
required-features = [ "bevy/bevy_winit", "bevy/png", "3d" ]

[[example]]
name = "circle"
required-features = [ "bevy/bevy_winit", "bevy/png" ]
required-features = [ "bevy/bevy_winit", "bevy/png", "3d" ]

[[example]]
name = "spawn_on_command"
required-features = [ "bevy/bevy_winit" ]
required-features = [ "bevy/bevy_winit", "3d" ]

[[example]]
name = "activate"
required-features = [ "bevy/bevy_winit" ]
required-features = [ "bevy/bevy_winit", "3d" ]

[[example]]
name = "force_field"
required-features = [ "bevy/bevy_winit" ]
required-features = [ "bevy/bevy_winit", "3d" ]

[[example]]
name = "2d"
required-features = [ "bevy/bevy_winit", "2d" ]

# [[example]]

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ The Hanabi particle system is a modern GPU-based particle system for the Bevy ga

The 🎆 Bevy Hanabi plugin is only compatible with Bevy v0.6.

### Add the dependency

Add the `bevy_hanabi` dependency to `Cargo.toml`:

```
[dependencies]
bevy_hanabi = { version = "0.1", features = ["3d"] }
```

If you're using a 2D camera, add the `2d` feature instead.

### System setup

Add the `HanabiPlugin` to your app:
Expand Down Expand Up @@ -213,6 +224,8 @@ cargo run --example random --features="bevy/bevy_winit"
- [ ] Generic 3D mesh
- [ ] Deformation
- [ ] Velocity (trail)
- [x] 3D camera support
- [x] 2D camera support
- Debug
- [x] GPU debug labels / groups
- [ ] Debug visualization
Expand Down
89 changes: 89 additions & 0 deletions examples/2d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! A particle system with a 2D camera.

use bevy::{
prelude::*,
render::{camera::ScalingMode, options::WgpuOptions, render_resource::WgpuFeatures},
sprite::MaterialMesh2dBundle,
};
use bevy_inspector_egui::WorldInspectorPlugin;

use bevy_hanabi::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut options = WgpuOptions::default();
options
.features
.set(WgpuFeatures::VERTEX_WRITABLE_STORAGE, true);
App::default()
.insert_resource(ClearColor(Color::DARK_GRAY))
.insert_resource(options)
.insert_resource(bevy::log::LogSettings {
level: bevy::log::Level::WARN,
filter: "bevy_hanabi=error,spawn=trace".to_string(),
})
.add_plugins(DefaultPlugins)
.add_system(bevy::input::system::exit_on_esc_system)
.add_plugin(HanabiPlugin)
.add_plugin(WorldInspectorPlugin::new())
.add_startup_system(setup)
.run();

Ok(())
}

fn setup(
mut commands: Commands,
mut effects: ResMut<Assets<EffectAsset>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let mut camera = OrthographicCameraBundle::new_2d();
camera.orthographic_projection.scale = 1.0;
camera.orthographic_projection.scaling_mode = ScalingMode::FixedVertical;
//camera.transform.translation.z = camera.orthographic_projection.far / 2.0;
commands.spawn_bundle(camera);

let mut ball = commands.spawn_bundle(MaterialMesh2dBundle {
mesh: meshes
.add(Mesh::from(shape::Quad {
size: Vec2::splat(0.1),
..Default::default()
}))
.into(),
material: materials.add(ColorMaterial {
color: Color::WHITE,
..Default::default()
}),
..Default::default()
});
ball.insert(Name::new("ball"));

let mut gradient = Gradient::new();
gradient.add_key(0.0, Vec4::new(0.5, 0.5, 1.0, 1.0));
gradient.add_key(1.0, Vec4::new(0.5, 0.5, 1.0, 0.0));

let spawner = Spawner::rate(30.0.into());
let effect = effects.add(
EffectAsset {
name: "Effect".into(),
capacity: 32768,
spawner,
..Default::default()
}
.init(PositionCircleModifier {
radius: 0.05,
speed: 0.1.into(),
dimension: ShapeDimension::Surface,
..Default::default()
})
.render(SizeOverLifetimeModifier {
gradient: Gradient::constant(Vec2::splat(0.02)),
})
.render(ColorOverLifetimeModifier { gradient }),
);

ball.with_children(|node| {
node.spawn_bundle(ParticleEffectBundle::new(effect).with_spawner(spawner))
.insert(Name::new("effect:2d"));
});
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ pub use plugin::HanabiPlugin;
pub use render::EffectCacheId;
pub use spawn::{Spawner, Value};

#[cfg(not(any(feature = "2d", feature = "3d")))]
compile_error!("Enable either the '2d' or '3d' feature.");
#[cfg(all(feature = "2d", feature = "3d"))]
compile_error!("Disable either the '2d' or '3d' feature. Both at the same time is not supported.");

/// Extension trait to write a floating point scalar or vector constant in a format
/// matching the WGSL grammar.
///
Expand Down
40 changes: 23 additions & 17 deletions src/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#[cfg(feature = "2d")]
use bevy::core_pipeline::Transparent2d as Transparent;
#[cfg(feature = "3d")]
use bevy::core_pipeline::Transparent3d as Transparent;
use bevy::{
core_pipeline::Transparent3d,
prelude::*,
render::{
render_graph::RenderGraph, render_phase::DrawFunctions,
Expand All @@ -19,7 +22,7 @@ use crate::{
spawn::{self, Random},
};

pub mod draw_3d_graph {
pub mod draw_graph {
pub mod node {
/// Label for the particle update compute node.
pub const PARTICLE_UPDATE_PASS: &str = "particle_update_pass";
Expand Down Expand Up @@ -86,39 +89,42 @@ impl Plugin for HanabiPlugin {
);

// Register the draw function for drawing the particles. This will be called during
// the main 3D pass, at the Transparent3d phase, after the opaque objects have been
// the main 2D/3D pass, at the Transparent2d/3d phase, after the opaque objects have been
// rendered (or, rather, commands for those have been recorded).
{
let draw_particles = DrawEffects::new(&mut render_app.world);
render_app
.world
.get_resource::<DrawFunctions<Transparent3d>>()
.get_resource::<DrawFunctions<Transparent>>()
.unwrap()
.write()
.add(draw_particles);
}

// Register the update node before the 3D main pass, where the particles are drawn.
// Register the update node before the 2D/3D main pass, where the particles are drawn.
// This ensures the update compute pipelines for all the active particle effects are
// executed before the 3D main pass starts, which consumes the result of the updated
// executed before the 2D/3D main pass starts, which consumes the result of the updated
// particles to render them.
#[cfg(feature = "2d")]
use bevy::core_pipeline::draw_2d_graph as bevy_draw_graph;
#[cfg(feature = "3d")]
use bevy::core_pipeline::draw_3d_graph as bevy_draw_graph;

let update_node = ParticleUpdateNode::new(&mut render_app.world);
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy::core_pipeline::draw_3d_graph::NAME)
.unwrap();
draw_3d_graph.add_node(draw_3d_graph::node::PARTICLE_UPDATE_PASS, update_node);
draw_3d_graph
let draw_graph = graph.get_sub_graph_mut(bevy_draw_graph::NAME).unwrap();
draw_graph.add_node(draw_graph::node::PARTICLE_UPDATE_PASS, update_node);
draw_graph
.add_node_edge(
draw_3d_graph::node::PARTICLE_UPDATE_PASS,
bevy::core_pipeline::draw_3d_graph::node::MAIN_PASS,
draw_graph::node::PARTICLE_UPDATE_PASS,
bevy_draw_graph::node::MAIN_PASS,
)
.unwrap();
draw_3d_graph
draw_graph
.add_slot_edge(
draw_3d_graph.input_node().unwrap().id,
bevy::core_pipeline::draw_3d_graph::input::VIEW_ENTITY,
draw_3d_graph::node::PARTICLE_UPDATE_PASS,
draw_graph.input_node().unwrap().id,
bevy_draw_graph::input::VIEW_ENTITY,
draw_graph::node::PARTICLE_UPDATE_PASS,
ParticleUpdateNode::IN_VIEW,
)
.unwrap();
Expand Down
5 changes: 4 additions & 1 deletion src/render/effect_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ use std::{borrow::Cow, cmp::Ordering, num::NonZeroU64, ops::Range};

use crate::{asset::EffectAsset, render::Particle, ParticleEffect};

#[cfg(feature = "2d")]
use bevy::core_pipeline::Transparent2d as Transparent;
#[cfg(feature = "3d")]
use bevy::core_pipeline::Transparent3d as Transparent;
use bevy::{
asset::{AssetEvent, Assets, Handle, HandleUntyped},
core::{cast_slice, FloatOrd, Pod, Time, Zeroable},
core_pipeline::Transparent3d,
ecs::{
prelude::*,
system::{lifetimeless::*, SystemState},
Expand Down
Loading

0 comments on commit 0e1df4d

Please sign in to comment.