0 like 0 dislike
I exported a simulation from Houdini and it plays perfectly in the editor and in my UE4 game. However the particles being tiny arrow sprites I would like to align them along the velocity vector of each point in the sim. This is the vector from the previous to the current position of each particle. I just cannot find a way to do this. Do I need to export velocity as well from Houdini (which increases sim data) or is there a neat way to compute it. The standard billboard alignment modes dont seem to get this effect. Any suggestions ?


asked by dsanjit1 (120 points)

1 Answer

0 like 0 dislike

Hi Sanjit,

Sure, you can do that.

You just need to store the previous frame's position, and subtract the current position from the previous position:

- add a new float3 particle field "Axis", in "rotate" transform-mode
- add a new float3 particle field "PreviousPosition" in "full" transform-mode

Setup your billboarder to use the "Axis" field as its billboarding axis, instead of the default "Velocity".
Then add an evolve script that does this:

    Axis = (Position - PreviousPosition) / dt;
    PreviousPosition = Position; // setup prev position for next frame.


    Axis = safe_normalize(Position - PreviousPosition);
    PreviousPosition = Position; // setup prev position for next frame.

Usually, you'd do this by inserting a script at the beginning of the evolver stack, to backup the Position the particle has at the beginning of the frame as the "PreviousPosition", then have various evolvers run that change the "Position" field, then, at last, have a script that computes "Position - PreviousPosition", to work out how much the position was changed by the intermediate evolvers.

However, you can't do that with simcache playback, as there's no "simcache evolver", the evolve starts with the particle properties grabbed from the simcache already filled-in.

So to make this work, you have to setup what will be the next frame's previous position at the end of the current frame.

This also has one nasty side-effect that on the first frame, "PreviousPosition" won't have any meaningful value (it will actually hold float3(0,0,0)), so on the first frame where they're born, the particles will appear massively stretched out from the effect instance's origin to their spawn position. Just because we end up setting the axis to "Position - 0", so to "Position".

If you use the version that divides by dt, this will be even more massively glitchy, as it'll multiply that stretch by 60, if you're running at 60 fps.

To fix that, you can use kind of a dirty hack, which is using a special flag flag that's false on the first frame, true on all next frames, and that you use to select() between a zero axis and the computed axis.

This also has a side-effect of producing un-stretched particles on their first frame of birth, but it's much much better than flickering massively stretched ones.
If it's still a problem you can also use that flag to hide them on their first frame, by shrinking the size or color to zero for example. This way when they first become visible they'll already have a proper stretch axis.

To do that:

- add an "int" particle field "HasPrevPos".
- no need to initialize it at spawn, keep it default-initialized to 0.

- change the evolve script doing the axis computation to this:

    Axis = iif(HasPrevPos, (Position - PreviousPosition) / dt, Axis);
    HasPrevPos = true; // will now stay true for all subsequent frames
    PreviousPosition = Position; // setup prev position for next frame.

Note: dividing by 'dt' to get the approximation of the "real" velocity can cause issues if an update is called with a dt of zero, as it will produce an infinity vector, or a NaN vector if PreviousPosition is equal to Position (which will probably be the case if an update does get called with a zero dt).
The popcorn runtime shouldn't call evolvers with a dt of zero. But if you want to be extra-safe, you can slightly change the script above so it handles that case as well:

    Axis = iif(HasPrevPos && (dt != 0), (Position - PreviousPosition) / dt, Axis);
    HasPrevPos = true; // will now stay true for all subsequent frames
    PreviousPosition = Position; // setup prev position for next frame.

Here, whenever the dt is zero, the condition in the 'iif' (which, in case you haven't used it already, is a reverse 'select') will evaluate to 'false', and will produce "Axis = Axis", so the axis will keep the last value it had (which would be either the spawn value, or a value computed when dt was not zero, which would hopefully be a valid value :)

Hope this helps !

answered by Julien (31.3k points)