Hijacking the BGE for Fun and Particles

Hello world! Care to take a journey into the annals of my twisted mind? Join me on a ride through a small part of the Blender Game Engine on a rickety roller coaster of code and commentary. For those interested in modifying the BGE source I hope this experience will give you ideas, and my personal aspiration is that those more knowledgable than myself will suggest ways of improving what I’ve done here. You must be at least proficient with C++ to ride this ride.

The ride I am talking about is the modifications I’ve made to the Blender Game Engine to add a real-time fluid simulation as part of my Master’s research. Right now the code is seperated into two repositories:

  • BGERTPS ([B]lender [G]ame [E]ngine – [R]eal [T]ime [P]article [S]ystem)
  • EnjaParticles (the standalone library containing the OpenCL Fluid Simulator)

We will primarily be focusing on the code in BGERTPS, but you will need both if you want to compile and run the code. If you’re mind has a built in C++ preprocessor, you may want to quit reading and check the list of modified files to see the damage I’ve done for yourself.

So let’s start with a little visual of the interface before we dig into the code. Here you see the modifier panel of our Domain object which controls parameters for the system. Our fluid will never leave this domain, so make sure it is big enough to hold objects you may want to interact with. The size of our particles relevant to our domain is determined by the maximum number of particles you specify. Of course smaller particles means better simulation resolution, but it also means more computation. For now the user will have to experiment with the capabilities of their machine, but in the future we hope to have guidelines and it would even be possible to automatically suggest particle counts based on available hardware. As a note, due to a limitation in the sorting code we are using right now the maximum number of particles must be a power of 2 (1024, 2048, 4096, 8192, 16384… le sigh).

RTPS Modifier UI

Now let’s actually add some fluid to the domain by creating a new object within the domain and giving it a few game properties in the Logic Panel.

Emitter UI

When the game starts the object’s bounding box will be filled with up to num equally spaced particles, if the volume of the bounding box is too small to hold num particles, then it will only inject however many particles are necessary to fill the volume. In the game, after the particles have been emitted, the num property is set to 0. Therefore if one would like to emit more particles, it is possible to use a Property Actuator to set the num property to a number, at which point the game engine will attempt to emit that number of particles and set the property back to 0.

It is also possible to have the particles collide against triangle meshes, so if you want the fluid to interact with an object make sure to switch to Edit mode, select all and hit ctrl-T to turn all the faces to triangles. It is also important to know which way your normals are facing. Once you have those right, it is as simple as adding a collide boolean to your object’s game properties. At the moment physics of the object are not shared with the fluid, meaning that forces do not transfer between them. We also have not optimized these collisions, so too many triangles (greater than a few hundred on most systems) will start slowing things down a bit.

Collision UI

There are also example blend files in EnjaParticles in the blender folder, like rtps_dam_demo.blend that could be used as a starting point. So that’s how you use it, but how do we make it, and are there better ways to do it?

RTPS API

Let’s start with the modifier. I’m going to skip how I made my RTPS modifier because I’ve detailed it line by line before. The important thing to start with is what the RTPS class needs for it to work, so let’s look at the API I use so far. (Everything in the RTPS library is in the rtps namespace)

First is the constructor (in BL_ModifierDeformer.cpp):

rtps::RTPSettings settings(sys, rtmd->num, rtmd->dt, grid, rtmd->collision);
(*slot)->m_pRTPS = new rtps::RTPS(settings);

So here we create an RTPSettings object, as well as an RTPS object. I’ll explain the (*slot) business in a bit but for now let’s just say we are saving a pointer to our RTPS object. So what are the settings?

  • sys: the type of system, for fluids we have rtps::RTPSettings::SPH
  • rtmd->num: the maximum number of particles
  • rtmd->dt: the time step we use to integrate the system
  • grid: the bounding box of our domain
  • rtmd->collision: whether or not to perform collision checks

The settings object is simply passed to the constructor of the RTPS class which creates the particle system we specify. If we want to add any particles to the system (presumably we do) we can call the following function (also in BL_ModifierDeformer.cpp)

rtps->system->addBox(nn, min, max, false);

Here the parameters are as follows:

  • nn: the number of particles to (attempt) to add
  • min: the “bottom left” corner of the bounding box (as a float4)
  • max: the “upper right” corner of the bounding box (also a float4)
  • false: a boolean specifying whether to scale the values for the simulation

Before we go into detail about how we construct and populate our particle systems, there are two quick functions we need to mention, namely:

(*slot)->m_pRTPS->update();

which does all of the simulation work every frame, and then there is (in RAS_ListRasterizer.cpp):

if(ms.m_bRTPS)
    ms.m_pRTPS->render();

and

if(ms.m_bRTPS)
    ms.m_pRTPS->render();

which handles all of the OpenGL rendering of our particles (and subsequently bypasses the normal Blender rendering of the domain). One bug here is that if you are in wireframe mode, the particles won’t render.

Modifier

Now lets go back to creating our system and seeing how we keep track of it in the game. Everything comes down to our RTPS modifier, an object with this modifier defines a system and provides a reference to it. We exploit the fact that modifiers have an Update routine which gets called every frame of game play, and we use that to tell our system what to do. So how does our system get attached to an object? Let’s start with how the modifier gets applied.
This happens in KX_BlenderDataConverter.cpp in the gameobject_from_blenderobject function:

bool bIsRTPS = BL_ModifierDeformer::HasRTPSDeformer(ob);

and

BL_ModifierDeformer *dcont = new BL_ModifierDeformer((BL_DeformableGameObject *)gameobj,
kxscene->GetBlenderScene(), ob,	meshobj, bIsRTPS);

This function is getting called on all the objects in the scene, so we want to make sure our object knows that it is an RTPS by making its ModifierDeformer aware of the fact. The first step is seeing if the current object has our RTPS modifier, with HasRTPSDeformer(obj) which is defined in BL_ModifierDeformer.cpp on line 147. The boolean value is stored as a member of the ModifierDeformer instance as defined in BL_ModifierDeformer.h:

bool    m_bIsRTPS; //different from the RAS_MaterialBucket flag but used to set it.

As you can see from the comment, we will use a similar boolean in RAS_MaterialBucket.h to signify whether or not a RAS_MeshSlot has our system:

bool    m_bRTPS;
rtps::RTPS*     m_pRTPS;

The RAS_MeshSlot is where the real action is, these are what the game engine uses to keep track of objects, at least as far as my understanding is concerned for modifiers and rendering. Now that we can keep a pointer to our system in a MeshSlot, we can access it in several places where we need it. Lets start with RAS_MaterialBucket.cpp which of course has the constructor (line 63) and destructor (line 90) as well as foregoing the application of OpenGL transformations (we manage our own OpenGL stuff inside the library) on line 618.
As we saw earlier the MeshSlot’s m_bRTPS boolean is used to call the render function in RAS_ListRasterizer.cpp on lines 234 and 269.

More interestingly, let’s look at how the boolean and the pointer are set when the modifier is applied in BL_ModifierDeformer.cpp:

if(m_bIsRTPS)
{
    (*slot)->m_bRTPS = true;

    ModifierData* md;
    for (md = (ModifierData*)m_objMesh->modifiers.first; md; md = (ModifierData*)md->next)
    {
        if(md->type & eModifierType_RTPS)
        {
            RTPSModifierData* rtmd = (RTPSModifierData*)md;

            rtps::RTPSettings::SysType sys = (rtps::RTPSettings::SysType)rtmd->system;

            //get the bounding box of the object for use as domain bounds
            KX_GameObject* gobj = (KX_GameObject*)m_gameobj;
            MT_Point3 bbpts[8];
            gobj->GetSGNode()->getAABBox(bbpts);
            MT_Point3 min = bbpts[0];
            MT_Point3 max = bbpts[7];
            using namespace rtps;
            rtps::Domain grid(float4(min.x(), min.y(), min.z(), 0), float4(max.x(), max.y(), max.z(), 0));

            if (sys == rtps::RTPSettings::SPH)
            {
                rtps::RTPSettings settings(sys, rtmd->num, rtmd->dt, grid, rtmd->collision);
                (*slot)->m_pRTPS = new rtps::RTPS(settings);
            }

Where slot is defined in a for loop over the list of MeshSlots in the lines that precede this excerpt. First we set our boolean flag to true (383), then we loop over the modifier data on our object (it is conceivable that our object could have more than one modifier) until we find the RTPS modifier (388). So we get the modifier data (390) which contains the values input into the UI, starting with what type of system we are making. Then we get the Axis Aligned Bounding Box for our object to construct the domain for the simulation (395-401). Finally we construct our RTPS object and save the pointer to our slot (406). If you look at the actual code you will see that there are a couple other systems that are possible, one of which my colleague is using to bring superfast boids to the game engine, but I’ll let her write about them!

Modifier Update

So now that we’ve got a system created, we want to simulate some fluids, preferably every frame. So let’s turn our attention to the Update function of our modifier, still in BL_ModifierDeformer.cpp we again loop over the slots and find our system and make sure it’s pointer is valid

 if((*slot)->m_bRTPS && (*slot)->m_pRTPS)
{
    rtps::RTPS* rtps = (*slot)->m_pRTPS;

Here we make a convenience variable to refer to our system for the rest of the Update. Next we skip a few lines that aren’t being used right now, and move on to interacting with other objects in the game!

if(rtps->settings.system == rtps::RTPSettings::SPH)
{
    CListValue* oblist = kxs->GetObjectList();
    int num_objects = oblist->GetCount();

The first thing we need to do is make sure we are dealing with the right kind of system, other systems might want to interact differently. The real work starts when we get the list of objects from the game engine.
A quick note, if you look in the code you might see timers sprinkled around:

timers[TI_EMIT]->start();

these are defined in the rtps library and this one was setup in the ModifierDeformer constructor.
On the next line you will see a vector of Triangles:

std::vector triangles;

which we will use to store all of the triangles from the meshes of objects we wish to collide against.

So lets loop over the objects in the game and check for properties we care about:

for(int iob = 0; iob < num_objects; iob++)     {         KX_GameObject* gobj = (KX_GameObject*)oblist->GetValue(iob);
        STR_String name = gobj->GetName();
        //printf("obj: %s\n", name.Ptr());

So we have our game object gobj, and we can print out it’s name to give us an idea of whats going on (of course this will print out the name for all objects every frame, so you only want to do this while debugging and figuring things out)

Collisions

Now let’s see how we interact with collider objects:

//Check if object is a collider
bool collider = false;
CBoolValue* boolprop = (CBoolValue*)gobj->GetProperty("collider");
if(boolprop)
{
    //printf("obj: %s, collider: %d\n", name.Ptr(), boolprop->GetBool());
    collider = boolprop->GetBool();
    if(collider)
    {
        getTriangles(gobj, triangles);
    }
}

Here we get the boolean value of our collider game property, and if it is true we populate the triangles vector with the triangle faces of the game object by calling the getTriangles function:

int getTriangles(KX_GameObject* gobj, std::vector &triangles)
{
    //get the mesh and the derived mesh so we can get faces/verts
    RAS_MeshObject* meshobj = gobj->GetMesh(0);
    Mesh* mesh = meshobj->GetMesh();
    DerivedMesh *dm = CDDM_from_mesh(mesh, NULL);

    //for now we are assuming triangles
    int n_faces = dm->getNumFaces(dm);
    MFace* face = dm->getFaceArray(dm);

    MT_Matrix3x3 grot = gobj->NodeGetWorldOrientation();
    MT_Point3 gp = gobj->NodeGetWorldPosition();

    Triangle tri;
    MT_Vector3 fv, ftv;

    for(int i = 0; i < n_faces; i++)
    {
        getTriangle(face[i], grot, gp, dm, tri);
        triangles.push_back(tri);
    }
    return triangles.size();
}

This function loops over all of the faces in the derived mesh and gets the individual Triangle struct for each one. These triangles are in global coordinates (rather than local to the object) which we accomplish by applying the global transformation (rotation and translation) to them in the getTriangle function:

void getTriangle(MFace& face, MT_Matrix3x3& grot, MT_Point3& gp, DerivedMesh* dm, Triangle& tri)
{
    MT_Vector3 fv, ftv;
    float mv[3];

    dm->getVertCo(dm, face.v1, mv);
    //rotate and translate by global rotation/translation to get global coords
    fv = MT_Vector3(mv[0], mv[1], mv[2]);
    ftv = grot*fv + gp;
    tri.verts[0] = float4(ftv.x(), ftv.y(), ftv.z(), 1);

    dm->getVertCo(dm, face.v2, mv);
    fv = MT_Vector3(mv[0], mv[1], mv[2]);
    ftv = grot*fv + gp;
    tri.verts[1] = float4(ftv.x(), ftv.y(), ftv.z(), 1);

    dm->getVertCo(dm, face.v3, mv);
    fv = MT_Vector3(mv[0], mv[1], mv[2]);
    ftv = grot*fv + gp;
    tri.verts[2] = float4(ftv.x(), ftv.y(), ftv.z(), 1);

    float4 e1 = float4(tri.verts[1].x - tri.verts[0].x, tri.verts[1].y - tri.verts[0].y, tri.verts[1].z - tri.verts[0].z, 0);
    float4 e2 = float4(tri.verts[2].x - tri.verts[0].x, tri.verts[2].y - tri.verts[0].y, tri.verts[2].z - tri.verts[0].z, 0);
    tri.normal = normalize(cross(e1, e2));
}

Here we simply get the vertex coordinates of the face and transform them with the rotation matrix and translation vector. This is nice and easy because the moto library provides operator overloading for doing matrix and vector operations. Finally we compute the normal using normalize and cross functions defined in the RTPS library.

For the collisions to happen we need to tell our system about it, which we do after we are done with looping over objects and right before the update call:

if(triangles.size() > 0 && rtps->settings.tri_collision)
{
    rtps->system->loadTriangles(triangles);
}

Emitters

We jumped ahead a little bit, before we get to the update call and we are done with checking for the collider property, we do emitters by checking if an object has a num property:

//Check if object is an emitter
//for now we are just doing boxes
CIntValue* intprop = (CIntValue*)gobj->GetProperty("num");
if(intprop)
{
    //get the number of particles in this emitter
    int num = (int)intprop->GetInt();
    if (num == 0) { continue;} //out of particles

    int nn = makeEmitter(num, gobj);
    if( nn == 0) {continue;}
}

After we get the value of num we first we make sure we aren’t trying to emit 0 particles. The makeEmitter function is defined on line 435 but has a bunch of extra dev stuff, with the important part being the following lines:

nn = num;
CIntValue *numprop = new CIntValue(0);
gobj->SetProperty("num", numprop);
return nn;

Which simply sets the num property to 0 and returns the original value. This is more complicated than it needs to be because the makeEmitter function will add more capabilities in the future such as turning an object into a “hose” to spray particles. This effect can be simulated for now by setting the num property to the number of particles you would like to emit over and over again (because every frame that the num property has a value greater than 0, that many particles will be emitted and then it will be set back to 0).
Finally we get the object’s bounding box in order to call the API function for adding a box of particles to our system.

    MT_Point3 bbpts[8];
    gobj->GetSGNode()->getAABBox(bbpts);
    float4 min = float4(bbpts[0].x(), bbpts[0].y(), bbpts[0].z(), 0);
    float4 max = float4(bbpts[7].x(), bbpts[7].y(), bbpts[7].z(), 0);
    rtps->system->addBox(nn, min, max, false); 

Conclusion

Whew, so I’ve taken you through just about all the changes I’ve made to the Blender source code. The one thing I haven’t really touched on is the includes I added in BL_ModifierDeformer.cpp (lines 59-71) some of which I am hoping for insight on, because it may not be good to include a KX_* header in a BL_* file. Other than that I added #include “RTPS.h” or #include “timege.h” to some of the various files I’ve gone over. The MODIFIED_FILES document should cover all the files I’ve changed, including CMake related changes. I try to keep my git mirror up-to-date with the blender git mirror so one could also use git or svn tools to see my exact changes.

Again, I hope this detailed breakdown will be helpful to someone! I’d like to thank Moguri, jbakker and dfelinto from the Blender community for their invaluable help so far. As the material from this post will most likely end up in my Master’s thesis, I’d like to shout out to my advisor Gordon Erlebacher my colleague Evan Bollig (for all kinds of CMake and programming help) and the Department of Scientific Computing!

If you made it this far, we got love for the same thing, so holler when you see me on IRC (enjalot) or gtalk :D

2 thoughts on “Hijacking the BGE for Fun and Particles

  1. Krystof

    I’ve only had a quick read through, and I don’t think I’ll be attempting anything of the above however, I just want to let you know that what you’re doing is fabulous! Absolutely fucking beautiful!
    Nice one mate.

Comments are closed.