LiquidFun Programmer's Guide
|
About
Particles
Particle Groups
Discrete Particles vs. Particle Groups
Creating and Destroying Particles
Creating and Destroying Particle Groups
Particle Behaviors
Particle Properties
Rendering with OpenGL
The Particle module offers the ability to create and manipulate liquid or soft (deformable) bodies. It allows you to create (and destroy) particles with various behaviors and properties, and provides various methods for manipulating them. The module permits you to define particles discretely or as groups. It is designed to allow you to manipulate large numbers of particles efficiently.
A particle is round, and the minimal unit of matter in a particle system. By default, a particle behaves as a liquid. You can set behavioral flags, however, to assign different behaviors (explained in Particle Behaviors) to individual particles or groups of particles. You can also set other particle properties including position, velocity, and color.
The b2Particle.h
file contains the enumerated behavior values, as well as the variables specifying other particle properties. The corresponding enum is named b2ParticleFlag.
Instead of creating particles individually, you can create a group of particles to manipulate en masse. Some of the particle-group properties that you can set are the same as those for discrete particles: behavior, position, linear velocity, and color. There are also properties specific to groups: rotational angle, rotational velocity, and strength.
The b2ParticleGroup.h
file contains the declarations for all of these variables, as well as the enum for particle-group behavior: b2ParticleGroupFlag
.
With one main exception, there is no functional difference between working with individual particles and groups of particles. The exception is rigid particles: Because of the internal algorithm used to make particles rigid, you must define them as a group.
Particle groups do offer several conveniences. First, they allow you to create and destroy large numbers of particles automatically. If you do not create a group, you must create all of the particles individually. Also, a group allows you to assign the same property, such as angle of rotation, to all of its particles at once.
To create individual particles, create a b2ParticleDef
-struct object. Next, specify the behavior and properties of the particle. Finally, call the method to create the particle.
The following example creates an individual particle.
b2ParticleDef pd;
pd.flags = b2_elasticParticle;
pd.color.Set(0, 0, 255, 255);
pd.position.Set(i, 0);
int tempIndex = m_world->CreateParticle(pd);
Particle lists are self-compacting. Therefore, the index returned by CreateParticle is only valid until a lower-indexed particle, or a group referencing a lower-indexed particle, is deleted.
To destroy an individual particle, invoke the function
void DestroyParticle(int32 index);
The following example destroys the particle created above.
m_world->DestroyParticle(tempIndex);
A particle group begins life in a shaped container. You must therefore start a particle group definition by specifying a shape. Next, create a b2ParticleGroupDef-struct object. Then, specify the behavior and properties of the particle. Finally, call the method to create a particle group.
The following example creates five differently colored, box-shaped groups of particles.
b2ParticleGroupDef pd;
b2PolygonShape shape;
shape.SetAsBox(10, 5);
pd.shape =
pd.flags = b2_elasticParticle;
pd.angle = -0.5f;
pd.angularVelocity = 2.0f;
for (int32 i = 0; i < 5; i++)
{
pd.position.Set(10 + 20 * i, 40);
pd.color.Set(i * 255 / 5, 255 - i * 255 / 5, 128, 255);
world->CreateParticleGroup(pd);
m_world->CreateParticleGroup(pd);
}
To destroy a group of particles, invoke the function
DestroyParticleGroup(b2ParticleGroup* group);
The following example destroys all particle groups in the world.
b2ParticleGroup* group = m_world->GetParticleGroupList();
while (group)
{
b2ParticleGroup* nextGroup = group->GetNext(); // access this before we destroy the group
m_world->DestroyParticleGroup(group);
group = nextGroup;
}
The next several sections provide more information on how to define particle behaviors and properties.
Particle behaviors are defined either for entire groups of, or individual, particles.
For a group of particles, use the b2ParticleGroupFlag
enum, which provides two types of particle groups:
A solid particle group prevents other bodies from lodging inside of it. Should anything penetrate it, the solid particle group pushes the offending body back out to its surface.
You cannot define a particle group as only solid. It must have one of four other behaviors, as well: it must either be combined with the rigid-group behavior, or with the wall, spring, or elastic particle behavior.
To define a group combining solid- and rigid-group behaviors, use a single statement. For example:
pd.groupFlags = b2_solidParticleGroup | b2_rigidParticleGroup;
To define a group combining solid-group behavior with a given particle behavior, use two statements. For example:
pd.flags = b2_elasticParticle;
pd.groupFlags = b2_solidParticleGroup;
A solid particle group possesses especially strong repulsive force. It is useful, for example, in a case where:
Use the b2_SolidParticleGroup
flag of the b2ParticleGroupFlag
enum to specify a solid particle group. In many cases, a group will be defined as not only solid, but with additional behaviors, as well. For example, solid and elastic
Rigid particle groups are ones whose shape does not change, even when they collide with other bodies. Working with rigid particle groups confers a few advantages over simply working with rigid bodies: With a rigid particle group, you can:
Use the b2_rigidParticleGroup
flag of the b2ParticleGroupFlag
enum to specify a rigid particle group. For example:
pd.groupFlags = b2_rigidParticleGroup;
For individual particles, use the b2ParticleFlag enum. The b2ParticleFlag enum provides the flags described in the following sections. Note that different particle behaviors may exact different performance costs.
Elastic particles deform and may also bounce when they collide with rigid bodies.
Set particle behavior as elastic using the statement
pd.flags = b2_elasticParticle;
Color-mixing particles take on some of the color of other particles with which they collide. If only one of the two colliding particles is a color-mixing one, the other particle retains its pre-collision color.
The following example shows how color mixture is calculated. It shows the collision of two color-mixing particles: one red ("R") and one green ("G).
First, the system calculates deltaColor, which is the value by which each color will change.
deltaColor = colorMixingStrength * (B's color - A's color).
= 0.5 * ((0,255,0,255) - (255,0,0,255))
= 0.5 * (-255,255,0,0)
= (-127.5,127.5,0,0)
Then, it applies the delta to each particle
R's color += deltaColor
G's color -= deltaColor
As a result, both particles are now yellow:
A's color = (127.5,127.5,0,255)
B's color = (127.5,127.5,0,255)
Note that when one of the operations in step 2 results in a negative number, the system uses the absolute value of that number. When it results in a value over 255, it rolls over from zero.
Set particle behavior as color-mixing using the statement
pd.flags = b2_colorMixingParticle;
Powder particles produce a scattering effect such as you might see with sand or dust.
Set particle behavior as powder using the statement
pd.flags = b2_powderParticle;
Spring particles produce the effect of being attached to one another, as by a spring. Particles are "connected" in pairs. Each particle is connected to the one that was closest to it at time of creation. Once paired, particles do not change "partners." The farther an external force pulls them from one another, the greater the power with which they collide when that external force is removed. No matter how far particles get from one another, the connection between them does not "snap."
Set spring behavior using the statement
pd.flags = b2_springParticle;
Tensile particles are used to produce the effect of surface tension, or the taut curvature on the surface of a body of liquid. They might be used, for example, to create the surface tension you would see on a drop of water.
Once the tension is broken, the particles bounce as if they were elastic, but also continue to attract each other. As a result, particles tend to form clusters as they bounce.
Set tensile behavior using the statement
pd.flags = b2_tensileParticle;
Viscous particles exhibit clinginess or stickiness, like oil.
Set viscous behavior using the statement
pd.flags = b2_viscousParticle;
Wall particles are static. They are permanently stationary, even if something collides with them.
Set wall behavior using the statement
pd.flags = b2_wallParticle;
Zombie particles are useful when you want efficiently to destroy multiple particles in a single step. All of the particles that you designate as zombies are destroyed at the same time, in a single iteration of the solver. Destroying particles in a batch, after designating them as zombies, yields better performance than destroying them one by one: Whereas destroying particles one-by-one takes (number of particles) * (time per particle) to complete, destroying them all in a batch takes the same time as it would to destroy a single particle.
In the following example, every other particle in a group is designated as a zombie, and will be destroyed in the next step of the solver. (For more information on the LiquidFun solver, see Chapter 1. Introduction.)
b2ParticleGroup*group= m_world->CreateParticleGroup(pd);
for (int32 i=0;i<group->GetParticleCount();i+=2)
{
group->GetParticleFlagsBuffer()[i] |=
b2_zombieParticle;
}
Note that you can assign multiple behaviors to a particle or group. Use the | ("bitwise OR") operator to chain behavior flags. For example:
pd.flags = b2_elasticParticle | b2_viscousParticle;
Set particle or particle-group color using the statement
pd.color.Set(r, g, b, a);
whose parameters set red, green, blue, and opacity, respectively. Each parameter takes a value of 0-255.
Set particle size using the statement
m_world->SetParticleRadius®;
where r
is a float32 value greater than 0.0f. Its default value is 1.0f.
There are two points to keep in mind when using small particles. First, in the case of particle groups, that particle size can affect performance. This is because the smaller the particle size, the larger the number of particles generated to constitute a group. Having a large number of particles, in turn, can diminish performance.
Second, small particles may behave unpredictably (i.e., break conservation of momentum) in scenarios such as explosions. Slowing these particles down by reducing gravity scale can stabilize their behavior.
Note that
Set gravity scale using the statement
m_world->SetParticleGravityScale(g);
where g
is a float32 value.
Set particle or particle-group position using the statement
pd.position.Set(x, y);
where x
and y
are the world-coordinates of the translation of the particle group.
For discrete particles, set velocity using the statement
pd.velocity.Set(x,y);
where x
is velocity along the x-axis, and y
is velocity along the y-axis.
For particle groups, set velocity using the statements
pd.linearVelocity.Set(x,y);
pd.angularVelocity = aV;
where x
is the group's velocity along the x-axis, y
is velocity along the y-axis, and aV
is the group's angular (i.e., rotational) velocity (expressed as radians per second).
This property applies only to rigid particle groups. It indicates the angle at which a group is tilted. Set angle with the statement
pd.angle = a;
where a
is the angle of tilt, expressed in radians. Left unspecified, the value defaults to 0.
Strength describes the cohesion of a group of particles. Set strength with the statement
pd.strength = s;
where s
is a float32 value between 0.0 (least cohesive) and 1.0 (most cohesive). The default value is 1.0.
The Particle module provides particularly efficient rendering via OpenGL.
Each type of particle property lives in a contiguous memory buffer. For example, all particles' position data live next door to one another, all color data live next door to one another, and so forth. Table 1 provides a visual representation of this storage.
**_Table 1. Memory Map of Particle Buffers_**
Particle 1 | Particle 2 | Particle 3 | |
Position | x1,y1 | x2,y2 | x3,y3 |
Address | 0x00001000 | 0x00001008 | 0x00001010 |
Color | r1,g1,b1,a1 | r2,g2,b2,a2 | r3,g3,b3,a3 |
Address | 0x00002000 | 0x00002004 | 0x00002008 |
OpenGL can use these buffers directly in rendering.
In this example, OpenGL 1.1 would use glVertexPointer and glColorPointer to get the values from memory. OpenGL 2.0 would use glVertexAttribPointer.
OpenGL can be used to render either individual particles or particle groups.