Integrating LiquidFun with Cocos2d-x: Part I

LiquidFun Testbed + Cocos2d-x
LiquidFun Testbed + Cocos2d-x

From LiquidFun’s site:

Based on Box2d, LiquidFun features particle-based fluid simulation. Game developers can use it for new game mechanics and add realistic physics to game play. Designers can use the library to create beautiful fluid interactive experiences.

Basically LiquidFun is Box2d plus an extension to simulate fluids using a particle system. To test it, download and install the official LiquidFun – Testbed, and LiquidFun – EyeCandy for Android.

Cocos2d-x already has Box2d integration, so in order to integrate Cocos2d-x with LiquidFun, we only need to integrate this new class: b2ParticleSystem.

LiquidFun’s b2ParticleSystem

I’m not going to describe how to use LiquidFun (for that, read its programmers guide). Instead, I’m going to describe how to integrate b2ParticleSystem in Cocos2d-x (also applicable to any other game engine).

For the integration, what we need is a Cocos2d-x node that knows how to render a b2ParticleSystem. And b2ParticleSystem has these 4 useful methods:

class b2ParticleSystem {
  ...
  // Get the number of particles.
  int32 GetParticleCount() const;

  // Get the particle radius.
  float32 GetRadius() const;

  // Get the position of each particle in Box2d's coordinate system
  // Array is length GetParticleCount()
  b2Vec2* GetPositionBuffer();

  // Get the color of each particle in RGBA Uint8 format.
  // Array is length GetParticleCount()
  b2ParticleColor* GetColorBuffer();
};

Ideally we should be able to reuse cocos2d::ParticleSystemQuad for the rendering, but we can’t because:

  • cocos2d::ParticleSystemQuad doesn’t support changing the attractor (this is a design bug, we need to fix it). A nil attractor would be needed for this case.
  • ParticleSystemQuad works with Quads, and not Points. And even if Points were supported (like in Cocos2d-x v1), it wouldn’t work because the points and colors should be in an interleaved array.
  • The other issue is the conversion between Box2d and Cocos2d-x coordinate system, but it would be easy to fix.

Points vs. Quads

The major drawback of using Points (instead of Quads), is that you cannot rotate a Point (spinning). And that’s why we are not using them on Cocos2d-x.

And the benefits of using Points (instead of Quads) are that less memory is required, and that means that it could be faster. As an example:

  • Point uses one point for the position (instead of 4 for the quad)
  • Point uses one color (instead of 4)
  • Point doesn’t need the UV coordinates (zero points instead of 4)
  • But if you want to use different sizes, you need to pass an array of floats (not needed if you are using quads).
  • And from LiquidFun’s point of view, it is cheaper to do collision detection with circles (point + radius) than with quads

It is noteworthy that LiquidFun’s goal is to simulate fluids. And since in fluids you don’t need to spin the particles, LiquidFun uses Points (since they take less memory and they are faster).

Drawing using GL_POINTS

In OpenGL / OpenGL ES you can draw points using GL_POINTS, but it has certain limitations:

  • It is not possible to rotate them (already discussed)
  • If you scale the particle (by using gl_PointSize), you cannot scale the X-axis independent from the Y-axis.
  • Points can use textures as well, but you cannot change the u-v coordinates. Either you use the full texture, or nothing.

If you haven’t used GL_POINTS before, this is how the code should look like:

// Create the array of positions. They are in Box2d's coordinate system
// The will be converted later to cocos2d's coordinate system
void *positions = _particleSystem->GetPositionBuffer();
glVertexAttribPointer(position_index, 2, GL_FLOAT, GL_FALSE, 0, positions);
glEnableVertexAttribArray(position_index);

// Array of colors. Format is: R,G,B,A unsigned bytes
void *colors= _particleSystem->GetColorBuffer();
glVertexAttribPointer(color_index, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, colors);
glEnableVertexAttribArray(color_index);

// Array of sizes. particle_size is an array of floats. The user should create it.
// This is an optional feature in order to have different sizes for the particles.
glVertexAttribPointer(particlesize_index, 1, GL_FLOAT, GL_FALSE, 0, particle_size);
glEnableVertexAttribArray(particlesize_index);

// Draw them as points
glDrawArrays(GL_POINTS, 0, _particleSystem->GetParticleCount());

Converting positions from Box2d to Cocos2d-x coordinate system

Cocos2d-x v3.0 passes the Model View matrix to the draw() method.  And what we need to do is to transform it so that we can use the Positions that are in Box2d’s coordinate system. And this is what we should do:

class LFParticleSystemNode : public cocos2d::Node {
  ...
  // ivar for the scaling transform
  kmMat4 _ratioTransform;
}

bool LFParticleSystemNode::init(b2ParticleSystem* particleSystem, float ratio)
{
  ...
  // Pixels to Meters ratio: converts Box2d into cocos2d
  kmMat4Scaling(&_ratioTransform, ratio, ratio, 1);
  ...
}

void LFParticleSystemNode::onDraw(const kmMat4 &modelViewTransform, bool transformUpdated)
{
    // A new model view will be used, which lets us render the particles in cocos2d coordinate system
    // newMV = modelViewTransform * _ratioTransform
    kmMat4 newMV;
    kmMat4Multiply(&newMV, &modelViewTransform, &_ratioTransform);
    _shaderProgram->use();
    _shaderProgram->setUniformsForBuiltins(newMV);
   ...
}

Changing the size with gl_PointSize

Another thing that is missing is to scale up/down the Points according to the “world” scale. And this is how we do it:

// Vertex Shader
attribute vec4 a_position;
attribute vec4 a_color;
attribute float a_size;

void main()
{
    // CC_PMatrix = Projection Matrix
    // CC_MVMatrix = ModelView Matrix
    gl_Position = CC_PMatrix * CC_MVMatrix * a_position;

    // This is the trick to scale up/down the points:
    // The ModelView matrix has the ScaleX value in [0][0], and ScaleY value in [1][1]
    // Unfortunately we can only use either ScaleX or ScaleY, but not both.
    gl_PointSize = CC_MVMatrix[0][0] * a_size;
    v_fragmentColor = a_color;
}

Texture Coordiantes with gl_PointCoord

The final thing to do, is use a texture for the particles, otherwise we will see a square. As I mentioned before, you cannot pass u-v coordinates forGL_POINTS. Instead, we are going to use the predefined variable called gl_PointCoord. Example:

// Fragment Shader
uniform sampler2D CC_Texture0;

varying vec4 v_fragmentColor;
void main()
{
    // gl_PointCoord has the current "pixel" for the fragment
    // It uses the full texture. We can't use a "subrect" for this.
    gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, gl_PointCoord);
}

And that’s all!

The complete LFParticleSystemNode code can be found here:

And the full working example can be downloaded from here:

 

In Part II, I’ll describe the steps needed to integrate LiquidFun with cocos2d-x for Win32 + Visual Studio. Later I’ll describe how to use a nice “metaball / blob” shader to simulate water.

Update: Part II is here

Published by ricardoquesada

cocos2d, unicyclist, commodore 8-bit

27 thoughts on “Integrating LiquidFun with Cocos2d-x: Part I

  1. How would we render this without gl_point, i need to scale x and y independently. Is there a way ? any help would be nice.

    1. @Sanchit:

      In order to scale X and Y independently, you have to stop using `gl_Point`, and pass an array of quads (4 vertex per point, but 6 indices) instead of an array of points (1 vertex per point)

      You can do that. But it could be overkill, since LiquidFun already gives the positions of the particles in an array of points.
      So you need to convert the points into quads each frame.

  2. Hi changed your shader in particlesystemnode to do alpha test and still I dont get the blod effect??

    vec4 texColor = texture2D(CC_Texture0, gl_PointCoord);

    if ( texColor.a <= 0.5
    discard;
    gl_FragColor = texColor * v_fragmentColor;

    Also do you have to add the particlesystemnode to a rencder texture and then do the alpha test on it?? If so the render texture doesnt do anything if I add it….

  3. I am still waiting for “how to use a nice “meatball / blob” shader to simulate water.”
    please continue…

  4. Hey Ricardo, I have the sample working with iOS build. When built and run in Mac mode the “glprogram” comes back with NULL. Function “LFParticleSystemNode::init(..)” at the line “GLProgramState *state = GLProgramState::getOrCreateWithGLProgram(glprogram);”.

    Do you know why this is or how I can fix it?

  5. Correction (ignore last comment)

    Hey Ricardo, I have the sample working with iOS build. When built and run in Mac mode the “glprogram” comes back with NULL. Function “LFParticleSystemNode::init(..)” at the line:

    auto glprogram = GLProgram::createWithByteArrays(_particleShaderVert, _particleShaderFrag);

    Do you know why this is or how I can fix it?

  6. Thanks for the excellent tutorial!

    I noticed the two samples are only for iOS/Mac. Is there any instruction to make it works on Android?

  7. Hello, i noticed that you have updated your samples and received libtool error in Xcode:
    Command /Applications/Xcode 2.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/libtool failed with exit code 1

    It’s pretty generic error. Could you figure it out what it is? Thanks

  8. The link for the source code is the code for part II.

    Where is the source code for part I?

    Thanks!

Leave a comment