0 like 0 dislike
I want to be able to use a custom mesh in a Shape Sampler and have my particles spawn at the mesh edges rather than on the surface or inside the volume.

Is this possible?

I had a look through the sample packs and there's an effect called CarTronRed.pkfx which seems to be doing this but I can't figure exactly how. I know it uses a shape sampler of a car (Mustang2014_Low.pkmm) and has ribbon renderers outlining the shape of the car by drawing lines along the car's edges and wheel arches - but I can't see how it's following those paths?

Any pointers on how to achieve this effect would be awesome!

by Sean (270 points)

1 Answer

1 like 0 dislike
Best answer


not in a builtin way.
Those example effects were made with a special mesh where the edges were actually very thin quads.

You could do this (in a non-uniform fashion though) by manually building the parametric coords used for sampling in a mesh setup in 'surface' mode.

Here's a regular sample of the surface using pCoords:

    int3    pCoords = Mesh.sampleParametricCoords();
    Position = Mesh.samplePosition(pCoords);

You can manually build the parametric coordinates of a sample by calling 'Shape.buildParametricCoordsMesh' and passing the triangle ID and the float2 barycentric coordinates in that triangle.

For example, to randomly sample all the triangles at their centers:

    int        triangleID = int(rand(0, Mesh.triangleCount()));
    int3    pCoords = Mesh.buildParametricCoordsMesh(triangleID, float2(0.333));
    Position = Mesh.samplePosition(pCoords);

Now, we can construct this float2's x and y based on random values so that the barycentric coordinates are only defined on the edges of the triangle.
The first edge of the triangle has its barycentric coords varying in the range x=[0,0] and y=[0,1] (x stays at 0 and y varies between 0 and 1, 0 being one end of the edge, 1 being the other, 0.5 being the center of the edge, etc)
the second edge is defined by x=[0,1] and y=0
and the third edge must satisfy x+y = 1
For barycentric coordinates to be inside the triangle, the following must be satisfied: x+y <= 1

Here's an example that samples the first edge of every triangle:

    int        triangleID = int(rand(0, Mesh.triangleCount()));
    int3    pCoords = Mesh.buildParametricCoordsMesh(triangleID, float2(0, rand(0,1)));
    Position = Mesh.samplePosition(pCoords);

To sample all 3 edges of all triangles, we can use a simple select:
    int        triangleID = int(rand(0, Mesh.triangleCount()));
    float    cursor = rand(0,3);
    float    r0 = remap(cursor, 0,1, 0,1);
    float    r1 = remap(cursor, 1,2, 0,1);
    float    r2 = remap(cursor, 2,3, 0,1);
    float2    bc = select(float2(0, r0), select(float2(r1, 0), float2(r2, 1 - r2), cursor > 2), cursor > 1);
    int3    pCoords = Mesh.buildParametricCoordsMesh(triangleID, bc);
    Position = Mesh.samplePosition(pCoords);

Here we generate a random number between 0 and 3, when it falls in the range [0,1[ we'll sample the first edge, when it falls in [1,2[ we'll sample the second, and when it falls in [2,3] we'll sample the third (diagonal).
(it could probably be written more efficiently).

Downside with that "hack" is that you won't get a proper uniform distribution, some edges will appear denser than others.
Proper uniform distribution would require remapping both the triangleID random sample based on each triangle's perimeter, AND changing the locations of the second random 'cursor' split based on the relative lengths of each edge (so that a longer edge gets more samples)

Hope this helps !

by Julien (35.3k points)
NOTE: PopcornFX v1.12.0 is not out yet, but some of these have been slightly renamed (there's an upgrader that takes care of patching the effects)
For future readers, in v1.12, this is:

    int        triangleID = int(rand(0, Mesh.triangleCount()));
    float    cursor = rand(0,3);
    float    r0 = remap(cursor, 0,1, 0,1);
    float    r1 = remap(cursor, 1,2, 0,1);
    float    r2 = remap(cursor, 2,3, 0,1);
    float2    bc = select(float2(0, r0), select(float2(r1, 0), float2(r2, 1 - r2), cursor > 2), cursor > 1);
    int3    pCoords = shape.buildPCoordsMesh(triangleID, bc); // <--- CHANGE
    Position = Mesh.samplePosition(pCoords);

all the parametric-coords building functions have been moved to a "shape" namespace, and are not members of the sampler anymore, as they don't depend nor require an actual sampler to work. Also, all functions containing "ParametricCoords" have been renamed to the more terse "PCoords".
Yes this does indeed help!
Can't believe you gave such a detailed answer so quickly too - thanks!

I'll dive in and have a go at some point - it kinda makes sense in theory from a quick couple of read throughs but I'll have to give it a go to learn it properly.

Luckily in my use case I don't think I'll need uniform distribution, what I have in mind should look good if it's distributed heavier in smaller areas. Good to have the info on how to look into doing that though if I need.

Thanks Julien!
cool !
no problem :)