Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ribbon modifier doesn't render anything #339

Open
MatrixDev opened this issue Jun 15, 2024 · 11 comments
Open

Ribbon modifier doesn't render anything #339

MatrixDev opened this issue Jun 15, 2024 · 11 comments
Labels
A - documentation Improvements or additions to documentation A - modifiers Change related to modifiers C - bug Something isn't working

Comments

@MatrixDev
Copy link

Crate versions
bevy version: 0.13
bevy_hanabi version: 0.11.0 (reproduces on main branch as well)

Describe the bug
In short nothing renders after adding RibbonModifier.

I might misunderstood how to use RibbonModifier so this might not be a bug. My sample is simpler than what you have in examples folder but I still think it should still work. BTW move_particle_effect system doesn't do anything in that example (I've commented it out and everything is the same).

Expected behavior
RibbonModifier should work without using clone modifier from example.

To Reproduce

use std::f32::consts::TAU;

use bevy::DefaultPlugins;
use bevy::math::vec4;
use bevy::prelude::*;
use bevy_hanabi::{Attribute, ColorOverLifetimeModifier, EffectAsset, ExprWriter, Gradient, HanabiPlugin, OrientMode, OrientModifier, ParticleEffect, ParticleEffectBundle, RibbonModifier, SetAttributeModifier, SimulationSpace, SizeOverLifetimeModifier, Spawner};

#[derive(Component)]
struct Source;

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, HanabiPlugin))
        .add_systems(Startup, setup)
        .add_systems(Update, update)
        .run();
}

fn setup(
    mut commands: Commands,
    mut effects: ResMut<Assets<EffectAsset>>,
) {
    let writer = ExprWriter::new();

    let init_size_attr = SetAttributeModifier {
        attribute: Attribute::SIZE,
        value: writer.lit(0.125).expr(),
    };

    let init_position_attr = SetAttributeModifier {
        attribute: Attribute::POSITION,
        value: writer.lit(Vec3::ZERO).expr(),
    };

    let init_velocity_attr = SetAttributeModifier {
        attribute: Attribute::VELOCITY,
        value: writer.lit(Vec3::ZERO).expr(),
    };

    let init_age_attr = SetAttributeModifier {
        attribute: Attribute::AGE,
        value: writer.lit(0.0).expr(),
    };

    let init_lifetime_attr = SetAttributeModifier {
        attribute: Attribute::LIFETIME,
        value: writer.lit(1.0).expr(),
    };

    let render_size = SizeOverLifetimeModifier {
        gradient: Gradient::linear(Vec2::splat(0.123), Vec2::ZERO),
        screen_space_size: false,
    };

    let render_color = ColorOverLifetimeModifier {
        gradient: Gradient::linear(vec4(3.0, 0.0, 0.0, 0.5), vec4(3.0, 0.0, 0.0, 0.0)),
    };

    let effect = EffectAsset::new(
        vec![256],
        Spawner::rate(256.0.into()),
        writer.finish(),
    )
        .with_name("ribbon")
        .with_simulation_space(SimulationSpace::Global)
        .init(init_size_attr)
        .init(init_position_attr)
        .init(init_velocity_attr)
        .init(init_age_attr)
        .init(init_lifetime_attr)
        .render(RibbonModifier)
        .render(OrientModifier::new(OrientMode::FaceCameraPosition))
        .render(render_size)
        .render(render_color);

    let effect = effects.add(effect);

    commands.spawn((
        Camera3dBundle {
            transform: Transform::from_xyz(1., 5., 1.).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
            ..default()
        },
    ));

    commands.spawn((
        ParticleEffectBundle {
            effect: ParticleEffect::new(effect.clone()),
            transform: Transform::IDENTITY,
            ..default()
        },
        Source,
    ));
}

fn update(
    mut query: Query<&mut Transform, With<Source>>,
    time: Res<Time>,
) {
    for mut transform in query.iter_mut() {
        let angle = time.elapsed_seconds_wrapped().fract() * TAU;
        transform.translation.x = angle.cos();
        transform.translation.z = angle.sin();
    }
}

Screenshots
Everything renders without RibbonModifier:
image

@MatrixDev
Copy link
Author

Forgot to add, I'm running this on my M1 laptop. I don't think it is M1 related problem because example works for me.

@djeedai
Copy link
Owner

djeedai commented Jun 21, 2024

@MatrixDev the way ribbons work is not intuitive. You have a single particle group, and no CloneModifier. The RibbonModifier works by chaining 1 "head" particle from group 0 with all associated "cloned" particles from group 1. Here you're not cloning anything so the ribbon has nothing to render (can't make a ribbon with 1 particle). And because it's present, the head particle itself doesn't render. You should look at the ribbon.rs example. Basically for 1 ribbon you need to spawn 1 particle in the first group (0), and then N particles making up the ribbon in group 1. Then when you move the head particle of group 0 around, each frame the other particles in group 1 will get automatically updated to the previous position of the particle before them, which will create the ribbon.

@djeedai djeedai added A - documentation Improvements or additions to documentation invalid This doesn't seem right A - modifiers Change related to modifiers labels Jun 21, 2024
@MatrixDev
Copy link
Author

MatrixDev commented Jun 21, 2024

hi @djeedai, thanks for answering. but I don't understand than how to make a particle in group 0 (tail particle) follow my entity? I can set SimulationSpace to Local but than whole ribbon will be in local space and I don't want it.

Is it possible to have group 0 in local space and group 1 in global?

ribbon.rs doesn't really help me because entity position there doesn't matter, only calculations in expression. should I use properties to update group 0 position each time my entity position changes?

@djeedai
Copy link
Owner

djeedai commented Jun 21, 2024

a particle in group 0 (tail particle)

Group 0 is the main group you control, where all init modifiers run, and which is driven by normal simulation, like if there was no ribbon. This works exactly as before ribbon existed.

Group 1 is the group of "tail" particles (the particles which follow the "head", and together form a ribbon; there's generally multiple tail particles per head particle). You have very little control over those. They're updated by cloning the head particles (for the first tail particle), or another tail particle (the one coming before in the chain). This is all done by the CloneModifier. The init modifiers never run on those groups, because cloning happens during the update pass.

The RibbonModifier only affects the rendering of all those particles. You can still make particles follow themselves in trails without using RibbonModifier. The RibbonModifer only creates a continuous curve between particles, as opposed to simply rendering each particle independently of the other.

how to make a particle [...] follow my entity?

This is a general question independent of ribbons. There are multiple approaches to this. If you have a single particle/ribbon, then you can just spawn that particle at a fixed point (for example the origin) and not apply any force. It will stay there and not move. Then you use local space simulation and move the Entity around. This only works with a single particle/ribbon because there's only one Entity per effect instance. If you have more than that, you can use properties; drive the Attribute::POSITION of the particle with a property, and each frame set the property from CPU to the position you want to. That has the advantage of supporting any number of particles/ribbons. The (small) inconvenient is that you upload from CPU to GPU not only the Entity transform but also all the properties you use, each frame. This is probably negligible unless you start using a lot. This is also a bit more involved to setup and update, since you have to manually update the property to the value of the position. This works both in local and global space simulation, although the calculation of the property value differs.

@MatrixDev
Copy link
Author

@djeedai, thanks a lot for your answer but... sorry, I still can't make it to work...

So I've modified ribbon.rs example by (following your instructions):

  1. changing to local space .with_simulation_space(SimulationSpace::Local)
  2. removing .update_groups(move_modifier, ParticleGroupSet::single(0)) so that head particle only follows the entity instead of using expression.
  3. added gizmo just to see where entity is actually located

Everything else is the same in the example but the ribbon is still not rendered :(
image

What I think is happening is all particles (head and ribbon) are always set to Vec3::ZERO position in the local space of the entity. Because of this nothing is drawing. Example modifies actual position of the head particle directly, so all cloned particles always have different position.

BTW yes, I meant "head" particle, not "tail" back there.

@djeedai
Copy link
Owner

djeedai commented Jun 21, 2024

Hum... yes of course that suggestion was stupid, the RibbonModifier only looks at the simulation space, and there the head particle doesn't move, so all cloned tail particles also don't move. Stupid me.

You probably need to drive the head particle with a property you setup and update manually, and use GlobalSpaceSimulation. The spawn_on_command.rs example shows how to use properties (it uses them to spawn the effect based on the collision normal that the CPU calculated).

@MatrixDev
Copy link
Author

@djeedai, yeap, it works with properties. but... it doesn't work with SizeOverLifetimeModifier now.

image

I've just added this modifier in the example:

    let render_size = SizeOverLifetimeModifier {
        gradient: Gradient::linear(Vec2::splat(0.5), Vec2::ZERO),
        screen_space_size: false,
    };

   // EffectAsset::new...
   .render_groups(render_size, ParticleGroupSet::single(1))

@djeedai
Copy link
Owner

djeedai commented Jun 22, 2024

Nobody ever tried that to be honest. I'm pretty sure it'll not work with RibbonModifier because they both try to change the quad size and orientation, so whichever comes last wins. But I'm surprised it doesn't work without, because the render modifier should work on any group.

@djeedai
Copy link
Owner

djeedai commented Jun 22, 2024

Oh, is that screenshot actually with the RibbonModifier? I thought it was without. If so then that's exactly what I described, the SizeOverLifetimeModifier comes last and undoes the work the RibbonModifier did to chain particles together. I'll have to think about how to fix that without special casing this particular couple of modifiers too much.

@djeedai djeedai added C - bug Something isn't working and removed invalid This doesn't seem right labels Jun 22, 2024
@MatrixDev
Copy link
Author

@djeedai, it is original example with SizeOverLifetimeModifier added. Is there maybe any other way of making ribbon shrink over lifetime (like using expressions or something)?

@djeedai
Copy link
Owner

djeedai commented Jun 22, 2024

It looks like RibbonModifier only changes the X size of particles, so I'm guessing whatever way (modifier or expression) changing the Y size should work. But it needs to be executed before the ribbon modifier. So you can try inserting the SizeOverLifetimeModifier first, maybe that works already?

size = vec2(length(delta), size.y);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A - documentation Improvements or additions to documentation A - modifiers Change related to modifiers C - bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants