Uniform Particle Trails

When the task becomes to get smooth lines, trails or flows/streams via particles, most hopes are put upon Blobby Surface or new Output Mesh for nParticles. Both have their own advantages and problems, a mutual one is how to generate a uniform trail – to get smoothest result with smallest amount of particles used.

First decision that comes to mind is to generate particle trails from base particles using standard emitter. Actually, exactly this way trails would be non-uniform – depending on the speed of main particle-generator, sections of it’s trail would consist of different amount of particles, clumps here, breaks there. To eliminate them you’d need to increase blobby radius that’d thicken overall trail etc. Much more efficient method that I’m going to describe in this post – to generate particle trail via particle expression based on distance that basic particle-generator moves per frame and place them uniformly between this two positions.

Actually, this particle expression for basic particle-generator is pretty simple – using position before dynamics/movement in the current frame, position after it, and with respect to user input value of distance between two trail particles (I’ve created scalar float attribute separ for particleShape) to create with emit command the amount of particles we need uniformly placed between these positions. For more thorough explanations of how to get these position values see Before/After Runtime Expressions post (I need to mention that I’m using custom attribute beforePosition here because there’re no collisions in this scene, so lastPosition/worldPosition wouldn’t return the values we expect):

runtimeBeforeDynamics:

beforePosition = position;

runtimeAfterDynamics:

string $trail_pt = "trail_pt";
float $separ = separ;

vector $lastPos = beforePosition;
vector $pos = position;
vector $move = <<(($pos.x)-($lastPos.x)), (($pos.y)-($lastPos.y)),
	(($pos.z)-($lastPos.z))>>;

int $num = ceil( mag( $move ) / $separ );

if( $num != 0 ) {
	vector $step = $move / $num;

	for( $i = 1; $i <= $num; $i++ ) {
		vector $newPos = $lastPos + $step*$i;
		emit -o $trail_pt -pos ($newPos.x) ($newPos.y) ($newPos.z);
	}
}

Main particle-generator (red) generates uniform trail of green particles behind:

Uniform Particle Trail (Jagged)

Uniform Particle Trail (Jagged)

Example scene: 05_uniPtTrail_01.mb

Of course if particle moves along smooth trajectory too fast, due to linear interpolation between positions trail would appear broken:

Fast movement of particle-generator causes broken trail

This could be solved with increasing dynamics oversampling (from 1 to 2 in this case):

With increased oversampling trail is smoother

With increased oversampling trail is smoother

Example scene: 05_uniPtTrail_02.mb

Now it seems that everything is as it should be and we can simulate our particles and generate trails from them… One more issue appears when we apply dynamic forces or age-based ramps to trail itself. In this scene trail_pt is affected by gravity – smooth at creation arcs fall towards the ground in some sort of chunks (upper part of the following image). So, where do they come from and how to get rid of them (lower part):

Forces affect trail particles in chunks

Forces affect trail particles in chunks

If we look closely to the differences between these chunks of particles, it becomes clear that particles in each of them were created at a time – birthTime and as a result age are exactly the same for them:

Trail particles are chunked by birthTime values

Trail particles are chunked by birthTime values

So, when dynamics are applied, particles generated in the same frame receive the same modifications and in this case fall down chunked. The solution is to define birthTime value along with generation in expression, so that each trail particle have a smooth increment that blends all chunk with neighboring ones:

[...]
	for( $i = 1; $i <= $num; $i++ ) {
		vector $newPos = $lastPos + $step*$i;
		float $life = time - (1.0/24/$num * ($num-$i));
		emit -o $trail_pt -pos ($newPos.x) ($newPos.y) ($newPos.z)
			-at "birthTime" -fv $life;
	}
[...]

After correcting birthTime, age gets corrected also. As a result – dynamic forces and age-based ramps have a smooth impact when moving from chunk to chunk:

Corrected birthTime smoothes trails

Corrected birthTime smoothes trails

Arcs and their colors become smooth:

Example scene: 05_uniPtTrail_03.mb

6 Responses to “Uniform Particle Trails”

  1. matt - September 4, 2009

    Simply great and very imformative!! thanks a tons

  2. Ryan - September 24, 2009

    This is great. Exactly what I was looking for too. The problem I’ve been having is creating a smooth contrail like emission from the wing tips on a plane moving at a high rate. I’ll check this out and link back if this works for my scene.

    Thanks for posting this!

  3. Paul - February 19, 2010

    Hey man thanks a lot for the tut, its great information. i was wondering if you could help explain a little more what these two lines of code do and why you are using the equations that you are? I kind of understand, but I’m not completely sure yet. Thanks.

    int $num = ceil( mag( $move ) / $separ );

    and

    float $life = time – (1.0/24/$num * ($num-$i));

  4. Sagroth - February 20, 2010

    The first one calculates the number of particles that needs to be generated between two positions of a master one. I find the distance between these two positions – magnitude of the vector of transformation – and divide it by user defined $separ (that’s like density of particles in the trail – a distance we want between a pair). Since this division almost never leaves a whole number, I’ve decided that one more particle is better than one less ;) so it’s ceil here. Then in the $step variable the final distance between these particular two positions is calculated – it’s like $separ adapted for the distance in current step. As a result that makes sections of the trail a little bit uneven (some have denser particles), but I believe in the end that’s better than gaps around each master-particle position.

    Second one is easier to write than to explain :) Since I’ve got a bunch of particles born at the exactly same time, I want to fool them that they were born consistently in time from last frame to this frame to make forces affect them accordingly. So, I take the number of particles I generate in current step and find the time delta needed for each one depending on the current fps to evenly stretch in time – 1/24/$num (that’s actually should be 1/$fps/$num to make it more generalized). Then since I generate particles “from the past towards now”, I subtract this delta multiplied by the inverse number of the particle from current time. That creates them with birthTime gradually from previous time to current.

    Hope that was understandable :)

  5. Stefan - April 8, 2010

    Thank you very much for this high level tutorial Sagroth! Its a great contributrion and very appreciated.

    About your last post, yes, it´s clearly understandable what the script does in terms of birthTime and for lifespan related ramps, this works fine. I´m using Maya2009 right now and i got the problem, that with fields (tested gravity and uniform), i still get the “chunk” drop. I guess we need to add the trick to active the field per particle a short amount of time after its creation.

  6. Sagroth - April 9, 2010

    You can access the magnitude of a specific field via .inputForce[#] attribute. More details in another thread here about the checking particle presence in a volume. Hence you can set the magnitude precisely for each particle when you create it, just like birthTime. That all is becoming pretty tricky though. That’s strange birthTime doesn’t work for you ’cause it does just that – modifies the magnitude of forces. Trails in my example are falling with gravity also.

Leave a Reply