Worms Post 9: Better Collision Generation

Collision Data Round Two!

So one of the last things that had been really bugging me about my systems recently I was still using my original collision generation every time that I wanted to actually update the collision for everything. Now that sounds fine, and kinda is fine. But the original method for generation is designed to generate and update the entire screen, meaning that currently if I wanted to update a 20x20 pixel area on the screen it would re-process the entire screen… Which is not so good.

Here is how I fixed that!

The Idea

Obviously re-processing the entire screen is not an ideal. So instead of doing that lets quickly take a look at what we need to actually update the collision for.

Well there are only a couple systems that actually mess with the render target, notably the Terrain Generation, Stamps and Deformation subsystems.

The Terrain Generation and Stamps are all dealt with right at the very start of the level loading however and are only done once. Due to the stamp subsystem being handled before the terrain is actually added it means we can generate the starting collision data after those two systems are already finished.

This means that the only real problem we have is deformation as this can be done at any time. But as we know from the last post the deformation uses the queue method, and the queue method uses the ImageGO2D object. The nice thing about those is that a while back I made it so that they store the height and width of what they store, meaning we can use this to find out the area that has been updated.

So we know what area is affected, now we just need to write a function that updated only the specified bounds. Luckily we actually already have a very similar function that is given to the threads for breaking up the starting collision data, so we can re-use some of that here.

The Implementation

Okay so here it is.

void Terrain2D::updateCollisionWithinBound(ImageGO2D* _texture, RenderTarget* _terrain, ID3D11DeviceContext* _deviceContext, GameData* _GD)
{
    Vector2 starting_pos = _texture->GetPos();
    int texture_width = _texture->getWidth();
    int texture_height = _texture->getHeight();

    int total_pixels = 0;
    int solid_pixels = 0;

    auto start = std::chrono::system_clock::now();

    debug_logger->writeLine(DebugLogger::TERRAIN, DebugLogger::MESSAGE, ("Loading a terrain collision region, this could take a second..."));

    _terrain->Map(_deviceContext);

    for (int width = starting_pos.x; width < starting_pos.x + texture_width; width++)
    {
        for (int height = starting_pos.y; height < starting_pos.y + texture_height; height++)
        {
            int x_pos = width - _texture->getWidth()/2;
            int y_pos = height - _texture->getHeight()/2;
            bool is_Solid = (_terrain->GetPixel(x_pos, y_pos, _GD)->A() > 0);
            setSolidValue(x_pos, y_pos, is_Solid);
            total_pixels++;
            solid_pixels = (is_Solid) ? solid_pixels + 1 : solid_pixels;
        }
    }

    auto end = std::chrono::system_clock::now();
    std::chrono::duration<double> time_passed = end - start;

    debug_logger->writeLine(DebugLogger::TERRAIN, DebugLogger::MESSAGE, ("Opertaions completed in " + to_string(time_passed.count()) + " seconds"));
    debug_logger->writeLine(DebugLogger::TERRAIN, DebugLogger::MESSAGE, (to_string(solid_pixels) + " Solid pixels effected out of a total pixel count of " + to_string(total_pixels)));

    _terrain->Unmap(_deviceContext);
}

Let’s check it out,

We start by getting the texture coords from the ImageGO2D class along with its width and height.

Next we set up some variables that we can update for debugging purposes later.

Then we use the debugger to tell the console that we are beginning the process.

We map the Render Target to the CPU so we can access the getPixel function later and begin a for loop over the area we wish to change.

With the x and y pos calculated all we need to do is check if the pixel is solid using the get Pixel function and then checking if its alpha value is greater than zero. We then update the collision data with our new information. Then we increment the total pixels and if it was actually solid we also increment the solid pixel.

After that we end the timer we had set up from the start and calculate how much time has passed. Then we write this to the console as well as how many pixels had been affected before then unmapping the Render target.

Now that this works we just need to add it to the game.cpp instead of our old method.

m_terrainData->getDeformationData()->process(m_terrain, m_d3dContext.Get(), m_DD2D, m_terrainData->getPixelPerfectDeformationMode()); 
if (!m_terrainData->getDeformationData()->returnTextureList().empty())
{
	for (ImageGO2D* element : m_terrainData->getDeformationData()->returnTextureList())
	{
		m_terrainData->updateCollisionWithinBound(element, m_terrain, m_d3dContext.Get(), m_GD); //Better way of updating terrain collision!
	}
}

Here is an example of what is outputted:

[Terrain -> Message] Drawn object with Deformation at: 438.126495,263.587280 in Pixel perfect mode...
[Terrain -> Message] Loading a terrain collision region, this could take a second...
[Terrain -> Message] Opertaions completed in 0.004434 seconds
[Terrain -> Message] 1177 Solid pixels effected out of a total pixel count of 10201

Summery

It works! and unsurprisingly is quite a bit faster. It required a bit of testing to get this right but thankfully in the end it works really well. Not much to say about this other than i’m happy with it.