1
ENABLE YOU TO MAKE A
PARTICLE SYSTEM SIMPLE & CUSTOMIZABLE THAT IS
2
1. BREAK IT DOWN
2. BUILD IT UP
1. BREAK IT DOWN
3
EMITTER
PARTICLE
WORLD
4
POSITION
POSITION OVER LIFE
POSITION AT BIRTH
SCALE
SCALE OVER LIFE
SCALE AT BIRTH
ROTATION
ROTATION OVER LIFE
ROTATION AT BIRTH
OPACITY
OPACITY OVER LIFE
OPACITY AT BIRTH
ANCHOR
ANCHOR OVER LIFE
ANCHOR AT BIRTH
5
POSITION
POSITION OVER LIFE
POSITION AT BIRTH
SCALE
SCALE OVER LIFE
SCALE AT BIRTH
ROTATION
ROTATION OVER LIFE
ROTATION AT BIRTH
OPACITY
OPACITY OVER LIFE
OPACITY AT BIRTH
ANCHOR
ANCHOR OVER LIFE
ANCHOR AT BIRTH POSITION
POSITION OVER LIFE
POSITION AT BIRTH
SCALE
SCALE OVER LIFE
SCALE AT BIRTH
ROTATION
ROTATION OVER LIFE
ROTATION AT BIRTH
OPACITY
OPACITY OVER LIFE
OPACITY AT BIRTH
ANCHOR
ANCHOR OVER LIFE
ANCHOR AT BIRTH
POSITION
POSITION OVER LIFE
POSITION AT BIRTH
SCALE
SCALE OVER LIFE
SCALE AT BIRTH
ROTATION
ROTATION OVER LIFE
ROTATION AT BIRTH
OPACITY
OPACITY OVER LIFE
OPACITY AT BIRTH
ANCHOR
ANCHOR OVER LIFE
ANCHOR AT BIRTH
POSITION
POSITION OVER LIFE
POSITION AT BIRTH
SCALE
SCALE OVER LIFE
SCALE AT BIRTH
ROTATION
ROTATION OVER LIFE
ROTATION AT BIRTH
OPACITY
OPACITY OVER LIFE
OPACITY AT BIRTH
ANCHOR
ANCHOR OVER LIFE
ANCHOR AT BIRTH
6
P POSITION
P POSITION OVER LIFE
P POSITION AT BIRTH
RANDOM
RANDOM
P SCALE
P SCALE OVER LIFE
P SCALE AT BIRTH
RANDOM
RANDOM
P ROTATION
P ROTATION OVER LIFE
P ROTATION AT BIRTH
RANDOM
RANDOM
P OPACITY
P OPACITY OVER LIFE
P OPACITY AT BIRTH
RANDOM
RANDOM
P ANCHOR
P ANCHOR OVER LIFE
P ANCHOR AT BIRTH
RANDOM
RANDOM
7
OPACITY
ROTATION
SCALE
POSITION
ANCHOR
NOISE
DISTRIBUTION
SHAPE SIZE
SHAPE
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
PARTICLE INSTANCES
35
PARTICLE INSTANCES
PARTICLE GROUPS
36
1. BREAK IT DOWN
2. BUILD IT UP 2. BUILD IT UP
37
OUTPUT
INPUT
INPUT
WIND DRAG GRAVITY BOUNCE TIME STRETCH TIME LOOP
DENSITY QUANTITY
SPREAD SPEED LIFE
ANCHOR
RANDOM SEED
POSITION SCALE ROTATION
LOOK
OPACITY
BIRTH
AGE
INDEX
LIFE
ANCHOR POSITION
SCALE ROTATION
OPACITY
START TIME
OPACITY ROTATION
SCALE POSITION ANCHOR
NOISE DISTRIBUTION
SHAPE SIZE SHAPE
38
1. EMITTER OBJECT contains all centralized inputs for controlling all of its child particle groups
int shape
float[3] shapeSize
int distribution
float[3] noise
float[3] anchor, position, scale, rotation
float opacity
39
2. PARTICLE GROUP OBJECT contains all centralized inputs for controlling all of its child particle groups
Image look
int randomSeed
float startTime
float quantity
float density
float life, lifeRandom
float speed, speedRandom
boolean speedOverdriveFromEmitterMotion
40
contains all centralized inputs for controlling all of its child particle groups
float[3] spreadAngle, spreadRange, spreadRandom
boolean spreadFollowEmitterMotion
float[3] anchorOffset, anchorOffsetRandom,
anchorWiggleFrequency, anchorWiggleAmplitude
float[3] positionWiggleFrequency,
positionWiggleAmplitude
2. PARTICLE GROUP OBJECT
41
contains all centralized inputs for controlling all of its child particle groups
float[2] scale, scaleRandom,
scaleWiggleFrequency, scaleWiggleAmplitude
float[3] rotationAtBirth, rotationAtBirthRandom,
rotationSpeed, rotationSpeedRandom,
rotationWiggleFrequency,
rotationWiggleAmplitude
boolean[3] rotationInBothDirection
float opacity, opacityRandom,
opacityWiggleFrequency, opacityWiggleAmplitude
2. PARTICLE GROUP OBJECT
42
2. PARTICLE GROUP OBJECT contains all centralized inputs for controlling all of its child particle groups
float[3] wind
float[3] gravity
boolean bounce
float bounceFloorDistance,
bounceElasticity, bounceFriction
float drag
float timeStretch
float timeLoop
43
contains generated properties to draw on screen
int index
float birth, life, age
float[3] anchor
float[3] position
float[2] scale
float[3] rotation
float opacity
3. PARTICLE INSTANCE OBJECT
44
SCALE
45
Particle Life is the longevity of the particle. It tells when the particle dies: life = pg.life + random(-pg.lifeRandom, pg.lifeRandom);
If we have PG Life Random, Particle Life will differ from PG Life.
Particle Birth is the moment the particle gets born: birth = pg.startTime + index/pg.density;
PG Start Time is the time this Particle Group starts showing up. Particle Index is the order of the particle, starting from 0. PG Density is how many particles get born in a second.
Particle Age is how old the particle is at a certain time: age = time – birth;
Time is the global value. It keeps increasing over time.
1. PARTICLE BIRTH + LIFE + AGE
46
SCALE
47
Every particle has its anchor at the center of it. If we need PG Anchor Offset feature, we need to implement it like this: anchor = pg.anchorOffset +
random(-pg.anchorOffsetRandom,
pg.anchorOffsetRandom);
Anchor alters the result when rotating or scaling an object:
2. PARTICLE ANCHOR
48
SCALE
49
Particle follows Emitting Direction. When Emitting Direction changes, Particle Direction changes, affecting Particle Position.
3. PARTICLE POSITION
50
Step 2: Calculate Particle Direction affected by Emitter Rotation and PG Spread: direction =
emitter.rotation +
pg.spreadAngle +
random(-pg.spreadRandom,
pg.spreadRandom);
Step 1: Define vector Particle Velocity pointing straight up (-Y): velocity = [0, -pg.speed, 0];
PG Speed defines how fast particles travel when they are born.
3. PARTICLE POSITION
51
3D Rotation reference: http://www.cs.helsinki.fi/group/goa/mallinnus/3dtransf/3drot.html
Step 3: Transform vector Particle Velocity according to Particle Direction: v = velocity; d = direction;
• X-Axis Rotation: vNew[0] = v[0]; vNew[1] = v[1] * cos(d[0]) - v[2] * sin(d[0]);
vNew[2] = v[1] * sin(d[0]) + v[2] * cos(d[0]);
• Y-Axis Rotation: vNew[0] = v[2] * sin(d[1]) + v[0] * cos(d[1]);
vNew[1] = v[1];
vNew[2] = v[2] * cos(d[1]) - v[0] * sin(d[1]);
• Z-Axis Rotation (2D Rotation): vNew[0] = v[0] * cos(d[2]) – v[1] * sin(d[2]);
vNew[1] = v[0] * sin(d[2]) + v[1] * cos(d[2]);
vNew[2] = v[2];
3. PARTICLE POSITION
52
particlePositionAtBirth emitterPosition
vNew * age
Step 4: We now have new vector Particle Velocity. Multiply it with Particle Age to get its local Particle Position over time: position = vNew * age;
Finally we get global Particle Position: position += emitter.Position;
3. PARTICLE POSITION
53
Emitter Size
Random point picking reference for other shapes: http://mathworld.wolfram.
com/topics/
RandomPointPicking.html
If Emitter Shape Size is not zero. Position At Birth is a random point within Emitter Shape. So it is affected by Emitter Shape and Emitter Shape Size: positionAtBirth =
random(-emitter.shapeSize/2,
emitter.shapeSize/2);
particlePositionAtBirth
emitterPosition
vNew * time
3. PARTICLE POSITION
So we have to count in Particle Position At Birth: position += positionAtBirth;
54
SCALE
55
Particle Scale defines the size of the particle. And it is affected by PG Scale and PG Scale Random: scale = pg.scale +
random(-pg.scaleRandom, pg.scaleRandom);
In order to change Particle Scale Over Life we need to enable key frames for PG Scale so its value varies from time to time, and then get its value at its proper age, we call it PG Age: pgAge = age * pg.life/life;
So we use this code instead of the one above: scale = pg.scale.valueAtTime(pgAge) +
random(-pg.scaleRandom, pg.scaleRandom);
4. PARTICLE SCALE
56
SCALE
57
Particle Rotation is a little more complicated. We need to apply both PG Particle Rotation At Birth and PG Rotation Speed: rotation = pg.rotationAtBirth +
random(-pg.rotationAtBirthRandom,
pg.rotationAtBirthRandom) +
age *
(pg.rotationSpeed.valueAtTime(age * pg.life/life) +
random(-pg.rotationSpeedRandom,
pg.rotationSpeedRandom));
5. PARTICLE ROTATION
58
SCALE
59
Also like Particle Scale, Particle Opacity is simple, supporting Particle Opacity Over Life: opacity = pg.opacity.valueAtTime(pgAge) +
random(-pg.opacityRandom, pg.opacityRandom);
6. PARTICLE OPACITY
60
To achieve uniform spread, we need to add to the Particle Direction code that we made before: direction = emitter.rotation + pg.spreadAngle +
random(-pg.spreadRandom, pg.spreadRandom);
…with this: direction += pg.spreadAngle – pg.spreadRange/2 +
pg.spreadRange/pg.quantity * index;
Quantity is the total number of particles of this Particle Group.
PARTICLE UNIFORM SPREAD
61
To implement Wiggle Effect for Position, Scale, Opacity and even Rotation, we need to replicate the wiggle expression in After Effects. Here is an example expression of Particle Scale Wiggle in After Effects: scale = pg.scale +
wiggle(pg.scaleWiggleFrequency,
pg.scaleWiggleAmplitude);
PG Scale Wiggle Frequency is how many times a second the value changes. PG Scale Wiggle Amplitude is how large the value changes each time.
Go to this link for reference: http://codepen.io/jamiejefferson/pen/hHegc
WIGGLE
62
PG Wind is a vector that helps push particles at a constant speed: position += age * pg.wind;
PG Gravity makes particles fall down or float up at an accelerated speed. We can use this equation: position[1] = 0.5 * pg.gravity * age * age +
vNew[1] * age;
PG Drag makes particles slow down over time to zero speed: if (age == 0) age = 0.000001;
dragModifier = (1 – exp(-pg.drag * age)) /
(pg.drag * age);
position *= age * dragModifier;
WIND + GRAVITY + DRAG
63
PG Bounce is another story. We need to pass launch velocity, gravity, bounce floor height, elasticity and friction to get the vertical height of the particle.
Step 1: We have velocity and gravity, we can find the max height:
𝒉 =𝒗𝟐
𝟐𝒈=
𝒈𝟐𝒕𝟐
𝟐𝒈=
𝒈𝒕𝟐
𝟐 (𝒗 = 𝒈𝒕)
maxHeight = pg.bounceFloorDistance +
(vNew[1] * vNew[1]) / (2 * pg.gravity);
Step 2: Find the time the particle reaches the floor. It consists of the time the particle travels from its position at birth to the max height and the time from the max height to the floor. Time at bounce:
𝒕 = 𝒕𝟏 + 𝒕𝟐
BOUNCE
64
At max height velocity reaches zero, so we have the time from position at birth to the max height:
𝒗 = 𝒗𝟎 − 𝒈𝒕𝟏 ⇒ 𝟎 = 𝒗𝟎 − 𝒈𝒕𝟏 ⇒ 𝒕𝟏 =𝒗𝟎
𝒈
We have the max height and the gravity, we can find the time from the max height to the floor:
𝒕𝟐 =𝟐𝒉
𝒈 (𝒉 =
𝒈𝒕𝟐𝟐
𝟐 )
Finally we have: timeAtBounce = vNew[1]/pg.gravity +
sqrt(2 * maxHeight/pg.gravity);
BOUNCE
65
Step 3: If the particle has not reached the floor (Particle Age is less than Particle Time At Floor), it just moves under the effect of the gravity:
𝒉 = 𝒗𝒕 −𝒈𝒕𝟐
𝟐
position[1] = pg.bounceFloorDistance +
vNew[1] * age –
(pg.gravity * age * age)/2;
If the particle has reached the floor (Particle Age is equal or more than Particle Time At Floor), it’s time to calculate the bounce. First we invert the velocity at bounce:
𝒗 = 𝒗𝟎 − 𝒈𝒕
v[1] = -(vNew[1] – pg.gravity * timeAtBounce);
BOUNCE
66
When the particle bounces on the floor, it will loose some velocity basing on PG Bounce Elasticity: v[1] *= pg.bounceElasticity;
If PG Bounce Elasticity is less than 1: the particles will lose some vertical velocity after each bounce; is more than 1: they gain more vertical velocity.
The same for PG Bounce Friction but applied for horizontal velocity instead.
Then we add the time to the next bounce. It is twice the time to reach the max height (if we consider there is no drag):
𝒕 = 𝟐 ×𝒗
𝒈
timeAtBounce += 2 * v[1]/pg.gravity;
Step 4: We repreat it again and againfrom bounce to bounce until the time between any two bounces is less than one frame duration.
Reference bounce code: http://www.motion-graphics-exchange.com/
after-effects/Object-bouncing-on-a-floor/
473c0e7522331 BOUNCE
67
Time Loop enables us to make seamless transition between the end and the beginning of our animation, so that the animation can repeat forever. Simple, we implement Time Loop by controlling Particle Age: if (pg.timeLoop > 0 && age < 0)
age += pg.timeLoop;
This code will make unfinished particles at the end of the loop continue their existence at the beginning of the loop.
TIME LOOP
68
Time Stretch defines the time and speed of the whole Particle Group. For example if Time Stretch = 50%, the Particle Group only has half the time to finish its animation, thus it will play faster at double speed. We can cheat it without changing the speed by speeding up Particle Age alone: timeStretch = abs(pg.timeStretch)/100;
age /= timeStretch;
if (pg.timeStretch < 0)
age = pg.life + pg.lifeRandom –
age – birth/timeStretch * 2;
if (pg.timeLoop > 0 && age < 0)
age += pg.timeLoop/timeStretch;
However Time Stretch also affects Particle Birth as they will get born sooner or later: birth = pg.startTime + timeStretch * index/pg.density;
TIME STRETCH
69
INPUT
OUTPUT
INPUT
1.
SHAPE SHAPE SIZE
POSITION SCALE
ROTATION
WIND DRAG GRAVITY BOUNCE TIME STRETCH TIME LOOP
OPACITY
DENSITY QUANTITY
SPREAD SPEED LIFE
ANCHOR
RANDOM SEED
POSITION SCALE ROTATION
LOOK
OPACITY
BIRTH
AGE
INDEX
LIFE
ANCHOR POSITION
SCALE ROTATION
OPACITY
DISTRIBUTION
ANCHOR NOISE
START TIME 2. 3.
SCALE
70
For more information contact: [email protected] / [email protected]
71