Particle is a big topic in iOS animation. To create realistic smoke, fire or even rain drops or snow falls, having a good particle emitter is the key. But in this blog, I am going to talk about something more than a basic particle emitter.

In most of the cases, particle emitter generates particles randomly — random color, random size, random direction — with the control of some key parameters. But it is not always like that. Sometimes we want the generated particles to follow a certain path, such as water trace, a group of firefly, or sparkles in the wind. That’s what I am going to talk about this time — how to generate random particles along a path. Before we start, let’s take a look at the end result first.

As you can see in the demo, at first the particles ran around the character “2″; then in the second demo I created a path manually by clicking on all the vertices, and the particle emitter generated particles along the path I just created.

The details of how to create a particle emitter itself is not the focus of this post. There are plenty of good tutorials online about that. Actually my system is based on 71squared’s tutorial, which in my opinion is the one of the best tutorials in this category (link to tutorial). Their “Particle Designer” is also a great tool to simplify the process to create beautiful particles. If you haven’t read their tutorial but want to learn more about particle system, I strongly suggest you to go through it first. This blog is an improvement on top of the particle system implemented by 71squared.

Now let’s get to the details.

Every a fraction of a second, the particle system will update the positions of all live particles, and generate new particles at its predefined source location (with variances). New particles are generated one by one sequentially. To make them appear along a path (**assuming CGPath in this post**), we need to figure out at a certain timestamp, which point on the path should be the source location.

In the most straightforward case — when the path is a straight line, it is simple: we know the current moment and total duration, hence we know how many percent of time has passed; using this percentage, we can locate a point between the start point and the end point, which will be the source location to generate the particle for this moment.

It’s the same for other types of paths. The only difficult part is, since most the paths are polygonal lines, how to locate the point based on the percentage of elapsed time?

Imagine the polygonal line is a rope, and mark all the vertex points as red; now straighten the rope, you have a straight line with many red dots on it. Each dot has its own percentage value based on its distance to the start point and the total length of the rope. Once we are given a percentage value (P), by comparing with each of the vertex, we can eventually find two adjacent vertices with a smaller percentage value (P1), and a greater value (P2). Then we can get the location of the point at this moment:

x = P / (P2 - P1) * (X2 - X1) + X1 y = P / (P2 - P1) * (Y2 - Y1) + Y1

So the steps to generate particles along a path are:

- Once given a path, find all it’s vertices;
- Get the percentage value for each vertex;
- Locate the point based on the current percentage value and vertices;
- Generate particle at this point.

Now let’s get to the programming part.

**1. Find vertices of a CGPath**

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
typedef struct { CGPoint point; CGPoint previousPoint; float portionFinished; } CGPathControlPoint; CGPathControlPoint *controlPoints; - (void) setAnimationPath:(CGPathRef)path { if (animationPath) { CGPathRelease(animationPath); } if (controlPoints) { free(controlPoints); } animationPath = CGPathRetain(path); NSMutableArray* points = [NSMutableArray array]; CGPathApply(animationPath, points, extractPointsApplier); totalControlPoints = [points count]; if (totalControlPoints > 0) { controlPoints = calloc(totalControlPoints, sizeof(CGPathControlPoint)); if (controlPoints == NULL) { NSLog(@"not enough memory to save cgpath information"); return; } else { CGPoint prevPoint = CGPointMake(0.0, 0.0); float totalDistance = 0; for (int i = 0; i < totalControlPoints; i++) { CGPoint point = [[points objectAtIndex:i] CGPointValue]; float thisDistance = 0.0f; if (i != 0) { thisDistance = sqrtf( (point.x - prevPoint.x) * (point.x - prevPoint.x) + (point.y - prevPoint.y) * (point.y - prevPoint.y) ); } totalDistance += thisDistance; controlPoints[i] = (CGPathControlPoint) {point, prevPoint, totalDistance}; prevPoint = point; } for (int i = 0; i < totalControlPoints; i++) { CGPathControlPoint p = controlPoints[i]; if (i == 0 && totalControlPoints > 1) { CGPathControlPoint nextP = controlPoints[1]; CGPoint prevP = CGPointMake(2.0 * p.point.x - nextP.point.x, 2.0 * p.point.y - nextP.point.y); controlPoints[0] = (CGPathControlPoint) {p.point, prevP, p.portionFinished / totalDistance}; } else { controlPoints[i] = (CGPathControlPoint) {p.point, p.previousPoint, p.portionFinished / totalDistance}; } } } } } |

At first, I defined a new struct called “CGPathControlPoint”, which includes the current CGPoint and its percentage value. The previous point is also kept there for the purpose of calculating the emission angle, which is not a topic in this blog, so you can ignore that field for now.

Then I defined an array of control points “controlPoints”. Our goal is find all the vertices in a CGPath, and put them in this array. The magic happens at line #20:

CGPathApply(animationPath, points, extractPointsApplier);

CGPathApply is an iOS built-in method since version 2.0. What it does is, “for each element in a graphics path, calls a custom applier function”. In our case, the custom callback function is “extractPointsApplier”. Basically I created an array first (line 19), and the CGPathApply function will extract all vertices from the CGPath, and apply them to the callback function, which puts them back to the array. Here is the code for the callback function:

1 2 3 4 5 6 7 8 9 |
static void extractPointsApplier(void* info, const CGPathElement* element) { NSMutableArray* points = (NSMutableArray*) info; if (element->points && element->type != kCGPathElementCloseSubpath) { CGPoint p = *(element->points); [points addObject:[NSValue valueWithCGPoint:p]]; } } |

In above method, each CGPathElement is actually a vertex. It contains the CGPoint itself and a point type, which includes:

1 2 3 4 5 6 7 8 |
enum CGPathElementType { kCGPathElementMoveToPoint, kCGPathElementAddLineToPoint, kCGPathElementAddQuadCurveToPoint, kCGPathElementAddCurveToPoint, kCGPathElementCloseSubpath }; typedef enum CGPathElementType CGPathElementType; |

The callback function put all points in the array except the ones with type “kCGPathElementCloseSubpath”, because that usually means a duplicate point from the last vertex, just with different type.

**2. Get percentage value for each vertex**

After the call to CGPathApply, we have an array with all the vertices on the path. Then it’s easy to calculate the distances between two vertices and the total distance. On list 1, after line #20, is the implementation to find out the percentage value (portionFinished) for each vertex.

**3. Locate the point**

At each moment when we generate particles, we know exactly the elapsed time and total duration (or how many particles has generated and the max number of particles). Hence we know the percentage we have finished. Based on that percentage and the vertices we have extracted, it’s easy to figure out the current location:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
- (CGPathControlPoint) getPointByFinishedPortion: (float) portion { if (portion <= 0) { return controlPoints[0]; } else if (portion >= 1) { return controlPoints[totalControlPoints - 1]; } else { for (int i = 0; i < totalControlPoints; i++) { CGPathControlPoint cp = controlPoints[i]; if (portion < cp.portionFinished) { CGPathControlPoint prevP = controlPoints[i - 1]; float r = portion - prevP.portionFinished; float total = cp.portionFinished - prevP.portionFinished; r = r / total; float x = (cp.point.x - prevP.point.x) * r + prevP.point.x; float y = (cp.point.y - prevP.point.y) * r + prevP.point.y; return (CGPathControlPoint) {CGPointMake(x, y), prevP.point, 0.0}; } } // should not happen return controlPoints[0]; } } |

I think the code is pretty self-explained, so I won’t go though each line of them here.

**4. Generate particles**

Finally it’s time to generate particles. Again, I won’t cover actually how to generate particles here. Please refer 71squared’s tutorial and sample program for detail. The only different part is, in “addParticle” method, I added following lines:

1 2 3 |
if (animationType == kAnimatePath) { [self initParticle:particle atCGPathControlPoint:[self getPointByFinishedPortion:((float)particleCount / (float)maxParticles)]]; } |

Basically the percentage value is calculated based on the generated particle count and the max number of particles, and then the current point is retrieved by using the function in step 3. In the method “initParticle:atCGPathControlPoint:”, a particle is generated at the control point, instead of the predefined source location. In this way, a particle is generated at each moment at different location. When they are put together, it looks like the particles are running along the path, just like what we have demoed in the video.

Finally I’ll put my demo project and some useful links here. Hopefully this post is helpful to you.

**Links:**

- Xcode Project
- http://www.youtube.com/watch?v=66qQ21ZVOoc
- 71squared Tutorial For Particle Emitter
- 71squared’s Particle Designer

## Comments (2)