1 like 0 dislike
How would I go about offsetting the rotational pivot? I have several effects that need to rotate around the base of the sprite, not the center. An example would be a fireanimation. I'd like to have the sprites rotated slightly differently but when I do that, it rotates around the center of the sprite, bringing the base of the fire into the air. I'd like for the base to stay in place and rotate the direction the fire "licks" in.


My current workaround is to waste half my texture so that the base of the texture is actually in the middle of the sprite. This gives me the desired look, but it wastes a lot of memory.


How do you suggest I solve this? Cheers!
by Andreas (210 points)
Okay so how does a user update the pivot of a mesh's rotation?

Put another way: How does a user rotate a  spawned mesh around an arbitrary point?


2 Answers

0 like 0 dislike
Best answer

Hi Andreas,

Indeed there's no "pivot offset" in the billboarders.
You'll have to manually offset the rendering position to do that.

you can do either a world/3D pivot offset or a screen pivot offset.
Only the screen pivot offset will match the actual rotation of the billboard.


3D pivot offset

First, the simplest example: say you've got an axis-aligned billboard with your flame stretched on it.
the particle has its simulation position stored in the "Position" field.
Let's say you have the worldspace billboarding axis in the "Axis" field.

Create a new float3 field "DrawPosition", and change the "PositionField" property of the billboarder from "Position" to "DrawPosition". The billboarder will now use "DrawPosition" instead of "Position" to draw the particle.

in an evolve script, do this:

DrawPosition = Position + Axis * 0.5;


if "Position" is on the ground, this will move "DrawPosition" up along half the stretch axis, making the bottom of the billboard geometry rest on the ground.

Then, you can recompute Axis each frame and rotate it, for example like this:

Axis = rotate(scene.axisUp(), normalize(float3(0.5,0,0.2)), noise(scene.Time*1.5)*0.3 + 0.6);


This rotates the up axis along the float3(0.5,0,0.2) axis (the 'rotate' function expects the rotation axis to be normalized, hence the 'normalize' function), by some time-coherent random angle using the 1D noise() function, passing the scene time as a cursor into the noise.

You can also rotate it incrementally by using the 'dt' variable, which contains the current frame's time-step in seconds (will be 0.0166 when running at 60 fps), but it'll be harder to have a stable behavior.

I suggest you do like with the position: have a BaseAxis that doesn't move, and re-rotate that each frame into the final "Axis" field used by the billboarder. (Or "Axis" > "DrawAxis", whatever :) )

it'll use more memory in the particle storage, but will be easier to control / tweak, and will also avoid most imprecisions.


Screenspace pivot offset

the idea is to compute a screen-aligned axis and offset the billboard like above.
You can get the camera axes within evolve scripts by using "view.axisUp()" and "view.axisForward()" :

First, compute the rotation angle how you like:

Rotation = noise(scene.Time);


The billboard will rotate around its center.
Then, use that angle to rotate a vector aligned to the camera plane (up / side) :

float3    axis = view.axisUp() * cos(Rotation) - view.axisSide() * sin(Rotation);


And offset the draw position like before:
DrawPosition = Position + axis * Size;

Note that here, we don't do "Position + axis * 0.5" like with the axis aligned billboards.
This is because the "Axis" property of the axis-aligned billboards doesn't take the billboard size into account.

the "Size" property of billboards is in fact the radius, not the diameter, that's why we don't have to write "Position + axis * Size * 0.5"

Whereas the length of the axis given to the axis-aligned billboard is the length from the bottom to the top of the billboard geometry. (Hope this makes sense?)

It's less friendly than inputting a float2 offset in a predefined input-box in the billboard renderer, but you should still be able to achieve what you're after :)


Here are both techniques in action:

A builtin rotation offset is something we've been asked before and it will come in a later version, after v2.0 is released.

An example effect is available here: http://www.popcornfx.com/downloads/TestRotationPivot.pkkg
(right-click > Import package in your content browser)

Hope this helps !

by Julien (35.3k points)
Awesome! That worked. Only thing that caught me off guard was that I had to use Size.y when I had a float2 Size.

Would this sort of thing be possible for velocity aligned particles as well?
That works beautifully. You've saved me a lot of texture space.

One followup: How would I change this to work with velocity aligned particles?
Great ! :)

If you already have an existing axis your billboard is stretched along (like Velocity), instead of doing:

Axis = rotate(scene.axisUp(), rotAxis, rotAngle);

just do:

Axis = rotate(Velocity, rotAxis, rotAngle);

and make the billboard renderer reference "Axis" instead of "Velocity", and that should work
Hi Julien, I'm working on a similar effect and haven't been able to get to work. Would you be able to put up your example as a pkfx package? I understand the principles but I think I'm missing something.
Hi jankoz.
Sorry I completely missed your comment.
I uploaded a package containing an example effect here: http://www.popcornfx.com/downloads/TestRotationPivot.pkkg
(right-click > import package inside your content-browser)

there are 4 layers:
the two on the left show the first method, the 3d rotation axis, using axis-aligned billboarders.
the two on the right show the second method, the 2d screenspace rotation
the two center ones are "simple"
the two outer ones show a few modifications if you want complex size changes. (non-uniform size for u and v)

I also edited the original answer to include the link.
Thank you Julien! I see what my mistake was before. I was trying to use a PlanarAxisAligned type instead of a VelocityAligned billboard. These examples are really helpful, thanks for putting this package together.

Unfortunately, I can't get this example to work in Unity. I was wondering if you could try on your end just to verify. I've had issues before with using view.(whatever) in Unity before that worked exactly as intended in the Pkfx Editor.

For your information, I'm running Pkfx editor 1.10.5, Pkfx plugin 2.8, Unity 5.4.0f3.

Thanks again.
damn ! for some reason I don't get notifications when there's a new reply, didn't see your comment.

view.whatever is a new v1.10 feature, and the 1.10 Unity plugin has only been publicly released at the end of last week, so unless you had a preview build, this might be the reason it wasn't working.

The view.xxx functions should be bound properly with the 1.10 Unity plugin.
We'll double-check tomorrow to be sure (it's 9 pm here)

EDIT: I just checked the store page, indeed 2.8 uses PopcornFX SDK v1.9.5, plugin 2.9 uses SDK v1.10 (either 1.10.5 or 1.10.6)
Hi Julien,

I'm really stuck on this, and I don't know what's up. I have an effect that works in the 1.10.5 editor, however it doesn't appear in Unity at all, even after updating to the 2.9 plugin. I believe the cause of the issue is related to view.(function) because if I manually define an axis then it works as expected. I'm attaching a package of my effect, could you test it on your end? Thank you.

Hi Jankoz,

I tried your package with the 2.9 plugin and it seems to work fine.
Could you verify that the dll Plugins/x86_64/PK-UnityPlugin.dll is well updated ?
If that's not the case you can copy it by hands from the 2.9 plugin.
Thanks Valentin! Something must have gone wrong during my last update and I didn't think to try clearing it out and recopying. It's working for me now. Appreciate it!
1 like 0 dislike

Or, alternatively, you can use rotation around an axis :

float Angle= "Radians Go Here";
float Yprim = Position.* cos(Angle) - Position.z*sin(Angle);
float Zprim = Position.* sin(Angle) - Position.z*cos(Angle);
Position = float3(Position.x, Yprim, Zprim);

‚ÄčIt doesn't have to be just around X axis, you can mix and match.

Just another way to do it, I thought I'd share.

by zaknafein (1.2k points)