Journal Entry 23 – Destroying Chunks

I actually put this code together two nights ago, but have only had time to write about it now. I’m messing around with a simple tank game in the Voxel engine (which I eluded to in an earlier blog post). One feature I would like to have is destructible terrain, so I was playing with launching a voxel then having the terrain explode a bit if the flying voxel collided with the terrain. I’ve got something that works pretty decently, and I was even able to build some tunnels and such with it!

The first thing I do is actually ‘fire’ the voxel in the direction of the camera. To do this, I wrote a method called ‘FireVoxel’, which is bound to a key that the user can press.

private static List<VoxelParticle> shots = new List<VoxelParticle>();
 
private static void FireVoxel()
{
    // first convert the mouse to a world space position by unprojecting it
    Vector3 position = Picking.ScreenToWorld(new Vector3(viewport[2] / 2, viewport[3] / 2, 0), camera.ViewMatrix, projectionMatrix, viewport);
 
    shots.Add(new VoxelParticle(partialProgram, camera.Position, 40f * (position - camera.Position).Normalize(), 10, false));
}

It makes use of the Picking ScreenToWorld method that I wrote in a previous blog post. I then assigned a direction to it, and disabled rotation. In this case I used the color ’10’, which is a very dark (slightly red) color.

Now we can check whether our new VoxelParticle collides with any other voxel in our chunk. Here’s the basics:

// first get the location of the voxel with respect to this chunk
Vector3 position = particle.Position - BoundingBox.Min;
 
// now create a bounding box for our voxel particle
AxisAlignedBoundingBox box = new AxisAlignedBoundingBox(position + new Vector3(.1, .1, .1), position + new Vector3(.8, .8, .8));
 
// use a bounding box to check our collider (probably faster ways to do this, but this is easy for prototyping)
AxisAlignedBoundingBox collider = new AxisAlignedBoundingBox(new Vector3(x, y, z), new Vector3(x + 1, y + 1, z + 1));
 
if (collider.Intersects(box))
{
    // we visit adjacent voxels and 'explode' them based on a random number generator
    for (int _x = x - explosion; _x < x + explosion; _x++)
    {
        if (_x < 0 || _x >= Size) continue;
        for (int _y = y - explosion; _y < y + explosion; _y++)
        {
            if (_y < 0 || _y >= Size) continue;
            for (int _z = z - explosion; _z < z + explosion; _z++)
            {
                if (_z < 0 || _z >= Size) continue;
 
                // make sure we always destroy the block we actually collided with
                if (generator.NextDouble() > 0.3 && voxelData[_x + Size * _y + Size * Size * _z] != 0 || (_x == x && _y == y && _z == z))
                {
                    fallingVoxels.Add(new VoxelParticle(PartialProgram, new Tuple<int, int, int>(_x, _y, _z), voxelData[_x + Size * _y + Size * Size * _z]));
                    voxelData[_x + Size * _y + Size * Size * _z] = 0;
                }
            }
        }
    }
 
    // after we've exploded all of our voxels, rebuild the voxel chunk vertex array object
    BuildWithOcclusion();
 
    // we're done!
    return true;
}
Carving out a tunnel through some voxel chunks is easy once you can explode a 5x5 region of voxels easily!  Set 'explosion' to 2 for similar effects!
Carving out a tunnel through some voxel chunks is easy once you can explode a 5×5 region of voxels easily! Set ‘explosion’ to 2 for similar effects!

Finally we add a simple method to draw all of the voxel particles that make up our ‘bullets’, calling CheckCollision for each particle.

private static void DrawShots(float delta)
{
    for (int i = 0; i < shots.Count; i++)
    {
        if (shots[i].Position.y < 0) shots.RemoveAt(i--);
        else
        {
            foreach (var chunk in manager.VoxelChunks)
            {
                if (chunk.CheckCollision(shots[i], 2))
                {
                    shots.RemoveAt(i--);
                    break;
                }
            }
        }
    }
    VoxelParticle.DrawParallel(delta, shots);
}
Voxels are raining all over the place!  You can see the small(ish) dark voxels, which are my 'bullets', and then the carnage left in the pocketed surface of my voxel chunk.  Voxels are falling all over the place!
Voxels are raining all over the place! You can see the small(ish) dark voxels, which are my ‘bullets’, and then the carnage left in the pocketed surface of my voxel chunk. Voxels are falling all over the place!

Simply bind FireVoxel to a key and then call DrawShots in your draw methods, and you’ll be destroying terrain in now time! Happy coding,

Giawa

Here I'm showing off a bit of the optimizing that the engine makes.  I can get even more aggressive by not drawing things that are invisible to the camera.  I may do this at some point in the future, but performance is pretty good for the time being.
Here I’m showing off a bit of the optimizing that the engine makes. I can get even more aggressive by not drawing things that are invisible to the camera. I may do this at some point in the future, but performance is pretty good for the time being.

Leave a Reply