-
Notifications
You must be signed in to change notification settings - Fork 0
BOBEmitter
Particle engines are fascinating. The concept is quite simple, but it has enormous potential and power. It enables you to simulate fluids and gases, can break up solid objects or create realistic views of explosions. The possibilities are endless and today's computer graphics cannot work without them.
This is a good reason to experiment with particle emitters on the CMM2.
A particle engine consists of two components: The Emitter and the Particle.
The Particle is the basic element. It has certain properties like a position, a lifetime, a color and maybe more. It will be moved over the screen according to movement rules and may change its appearance depending on the lifetime or other properties. The particle will be removed from the screen when its lifetime expires.
The Emitter is responsible for creating, moving and destroying particles at the end of their lifetime. To do this, the emitter keeps track of all its particles in a list which is worked through and updated on a regular basis.
The Particle is just a pixel with X and Y properties for position, dX and dY properties for movement and life for lifetime.
The Emitter creates a new particle at its position and adds it to the list. It updates the position of each particle by X=X+dX and Y=Y+dY and draws the pixel on the screen as long as life > 0.
As soon as a particle reaches its end of life it will be removed and a new particle will be created to replace it.
The result is a starfield effect where pixels are moving from a center to the screen edges. To emphasize the 3D effect, the pixels are scaled in size the older they get.
Creating such a particle engine in modern programming languages with objects and dynamic memory management is easy. It is a little more challenging to implement it in Basic, where you don't have this comfort.
I started with a simple starfield simulation as described above but effects based on pixels alone are rather limited. I wanted more.
- An image as particle with a reasonable number of shapes
- High performance
- More than one emitter possible
- Unlimited number of particles (yes, I know .. performance is limited)
The result is the GEM bobemitter.inc and its siblings. But before we come to it, let's talk about the CMM2 and MMBasic.
The CMM2 supports different APIs which can be used for a particle engine:
- BLIT, which is a highly optimized memory copy function,
- BLIT read and BLIT write, which store images in special buffers and
- SPRITE, the API to handle game objects
I will use BOB (Blitter OBject) for particles in this article to make clear that we use images and not pixels or other graphical elements and to have a clear differentiation from Sprites.
I didn't know which method would be the most flexible and fastest, so I created a particle engine for each of them:
- bobemitter.inc with the example SmokeEffectDirect.bas,
- bobemitter_buffered.inc with the example SmokeEffectBuffered.bas and
- bobemitter_sprites.inc with the example SmokeEffectSprites.bas
For this method the BOB images will be loaded into a free video buffer and copied from there on the screen. Due to the big video memory of the CMM2, there is almost no limit to the number of BOB images. For example: If your BOB is 16x16 pixels in size, you could store 1000 of them on a 640x400 video page.
This method is very fast but has two drawbacks:
- The position of your BOB image has to be calculated each time before drawing and
- it does not support transparency.
The second point can be overcome by clever image design, which is used in the example code.
For this method the BOB images will also be loaded into a free video buffer but then each image is copied into a special buffer with BLIT read. From here the images will be written to the screen with BLIT write. Because CMM2 has only 64 of these buffers, the number of BOB images that can be handled at the same time is limited to 64.
This method is even faster because the image positions do not have to be calculated each time. A reference to the buffer will do the trick. And, real transparency is supported, at least for the 12-bit color modes with SPRITE transparency. We will see in a moment why this works.
This method can copy the sprite image from a video buffer like the others with SPRITE read or the sprite images could be loaded from disk with SPRITE load. The sprites will be written to the screen with SPRITE write. The CMM2 supports 64 sprites, so the number of BOB images that can be used at the same time is limited to 64, as with the BLIT read/write.
Sprites will be addressed with sprite numbers, so they don't need address calculations, and they support real transparency with SPRITE transparency. Unfortunately, this works only in the 12-bit color modes.
Behind the scenes, SPRITE uses the same image buffers as BLIT read/write. In fact, BLIT read/write is the foundation layer of SPRITE. That is also the reason why SPRITE transparency works on a BLIT read buffer even though the sprite was never created.
If you read the full sprite API, you might find functions like SPRITE show and SPRITE hide, which also take care of restoring the background of a sprite. This sounds really clever. It should be faster because only small portions of the background must be restored and maybe clearing the screen can be skipped, too?
Unfortunately, there is a flaw in this concept of restoring the background automatically. The background is read at the time the sprite is drawn. If another sprite is drawn later and partially overlaps the already drawn sprite, the old sprite is part of the background of the new one. What would happen, if we remove sprites again?
After removing the first sprite the blank background would be restored. So far so good. Now we remove the second one and suddenly the first sprite is partly back on screen because it was saved as background when the second sprite was drawn, and it is now restored.
Particle emitters use a lot of overlapping images to create the desired effect. Drawing images with SPRITE show/hide would create a lot of artifacts that destroy the effect. Maybe there is a way of controlling the showing and hiding sequence to avoid artifacts, but I haven't found an efficient method.
But hey, what do I know. If you think you can handle this problem with sprites in an efficient way, please let me know. Create an example based on my SmokeEffectSprites.bas and see if it can compete.
Now let's compare the performance of the three methods. The conditions are always the same: resolution 640x400, 64 BOBs, 16 BOB images, the code is optimized for each color mode and the 12-bit color mode uses real transparency, if available.
Buffered BLIT and SPRITE reach the same performance. This is expected after what we have learned about the relation between BLIT read/write and SPRITE. The performance drop in the 12-bit color mode is caused by the transparency, which must be calculated and set.
A fun fact: Drawing pixels instead of blitting images is not faster. That shows how good the BLIT command really is. :-)
So, from my point of view the buffered BLIT is the best method for a particle emitter with images. Unless someone takes the SPRITE challenge and breaks all records.
Below you will find the outputs of the example programs showing the particle emitters in action.
How to use it: API of BOB Emitter
Output of the example code BOBEmitter.bas
Output of the examples SmokeEffect<>.bas