Tags

, , , , , , , , , , , , ,

In my last post I made a scary movie with PyMovieStudio, a free Python application for making movies. The movie has OpenCV computer vision to track the colour green, and OpenGL graphics library for fog and lighting.

But what good is a scary movie without blood? Let’s introduce an OpenGL particle system (or particle engine, if you will) to draw blood splats onto the screen.

What is a particle system? Well, it can apply logic and render hundreds or thousands of particles. Quite useful when you want to add an effect to your OpenGL application, such as a waterfall or fire. Or in my case, blood.

My particle system will manage 100 particles, or flecks, of blood. Each particle will have its own velocity and shade of red, and will disappear from the screen in its own good time. But ALL particles will begin from the same point in the screen – that way, the blood will splatter out in all directions from a central wound.

Okay, so before we look at the Python code, let’s have a gander at the blood splat effect from our particle system:

pymoviestudio_scarymovie_blood1

Here we begin to see the blood splattering out in all directions from an eye wound, administered by a bullet in the crosshairs.

pymoviestudio_scarymovie_blood2

In this shot, the blood is dispersing wide, each particle following its own velocity, each with its own shade of red.

pymoviestudio_scarymovie_blood3

Now there are only a few particles of blood, scattered far from the wound – the life of all the other particles have ended, their flecks gone to a fiery hell.

Right, time for the code. Here’s the Python class to represent a single particle:

class Particle:

    # initialise
    def __init__(self):

        # active settings
        self.is_active = False
        self.life = 0.0
        self.ageing = 0.0

        # colour
        self.red = 0.0
        self.green = 0.0
        self.blue = 0.0

        # coordinates
        self.x = 0.0
        self.y = 0.0
        self.z = 0.0

        # velocity
        self.xv = 0.0
        self.yv = 0.0
        self.zv = 0.0

Each particle can keep track of its ageing process – when it has no life left it will become inactive and disappear. It also keeps track of its colour, screen coordinates and velocity.

Now for the particle system:

from particle import Particle
from random import uniform
from OpenGL.GL import *

class ParticleSystem:

    # constants
    NUMBER_OF_PARTICLES = 100

    # initialise
    def __init__(self, x_coord, y_coord):
        self.particles = self._init_particles(x_coord, y_coord)
        self.active = True

    # initialise particles
    def _init_particles(self, x_coord, y_coord):
        particles = [Particle() for i in range(self.NUMBER_OF_PARTICLES)]

        # for each particle
        for particle in particles:

            # active settings
            particle.active = True
            particle.life = 1.0
            particle.ageing = uniform(0.1, 0.4)

            # colour
            particle.red = uniform(0.6, 0.9)
            particle.green = 0.0
            particle.blue = 0.0
 
            # coordinates
            particle.x = x_coord
            particle.y = y_coord
            particle.z = 0.0   

            # velocity
            particle.xv = uniform(-0.08, 0.08)
            particle.yv = uniform(-0.08, 0.08)
            particle.zv = 0.2        

        return particles

    # apply blood
    def blood(self):
        has_active_particles = False        

        # for each particle
        for particle in self.particles:

            # if particle active
            if particle.active:
                has_active_particles = True

                # get coordinates of particle
                x = particle.x
                y = particle.y
                z = particle.z

                glPushMatrix()
                glTranslatef(0.0,0.0,-9.0)
                glPushAttrib(GL_CURRENT_BIT)

                # set colour of particle
                glColor3f(particle.red, particle.green, particle.blue)
                
                # draw particle
                VERTEX_POS = 0.012

                glBegin(GL_TRIANGLE_STRIP)
                glVertex3f(x+VERTEX_POS, y+VERTEX_POS, z)
                glVertex3f(x-VERTEX_POS, y+VERTEX_POS, z)
                glVertex3f(x+VERTEX_POS, y-VERTEX_POS, z)
                glVertex3f(x-VERTEX_POS, y-VERTEX_POS, z)
                glEnd() 

                # update particle with velocity
                particle.x += particle.xv
                particle.y += particle.yv
                particle.z += particle.zv

                # update particle's life
                particle.life -= particle.ageing
                
                if particle.life <= 0.0:
                    particle.active = False

                glPopAttrib()
                glPopMatrix()

            # check for active particles
            if not has_active_particles:
                self.active = False

We have a constant to represent the number of particles in our particle system i.e. 100.

On initialising the class, we create our 100 particles, each particle configured to its own ageing, shade of red and velocity by way of a random function.

For example, the ageing value will be a random floating point between 0.1 and 0.4, whereas the red value will be a random floating point between 0.6 and 0.9.

As we will see, although each particle of blood can fly through the air in its own direction and speed, with its own colour and ageing, it will still be governed by the same logic as every other particle.

Once our particle system is initialised with particles and is active, its blood method can be called to generate a frame of blood. If we continue to call the method on subsequent frames, the blood will splatter until all particles are dead and the particle system becomes inactive.

The blood method loops through each of the 100 particles. If the particle is active, it will fetch its x, y and z coordinates. The particle’s colour is applied and the particle is drawn to screen using an OpenGL triangle strip which forms a quadrilateral.

Some particle systems will draw texture onto the screen – for example, a nice image of a blood droplet for each particle – but I’ll keep things simple here with a basic square shape.

We update the x, y and z coordinates with the particle’s velocity. That way, the next time the particle is rendered to screen it will be a little bit further from the central wound. Note that although the x and y coordinates are updated with a random velocity, the z coordinate is updated with a fixed velocity of 0.2 – meaning that the particle of blood will always splat towards us (just as any decent blood splat should!).

Finally, we deactivate the particle if it has been aged to the end of its life, and once outside the loop we deactivate the particle system if all its particles have died.

Some particle systems will regenerate each particle as the particle ends its life. For example, a particle system for a waterfall will put each particle back to the top of the waterfall once it has reached the bottom and disappeared. But for my blood particle system, there will be no constant haemorrhaging. When the blood stops splatting, alas it dies.

Of course, we can reinitialise our particle system at new coordinates and generate another blood splat. Indeed, here’s my scary movie with no less than three blood splats:

Not a bad particle system for blood at all!

If you want to learn more about PyMovieStudio, and how it uses special effects such a blood splats, check out its Wiki.

Anything more crimson, consult Rodger Saltwash.

Ciao!