Destroying pixels of a dynamic 2D world based on hardness of a pixel?

I want to blow up pixels that are part of a 2D terrain, and be able to have the explosion penetrate further through pixels depending on how “hard” the pixels are considered, with 0 being invincible, and 254 being super soft, and 255 being an empty pixel (air). I am not totally sure how I might go about doing that, even though I already can check how “hard” a pixel is…

Here is an image explaining what I want to happen:

In this example the grass would be “soft” and the dirt “somewhat hard” for instance…

Right now my explosion takes a square of pixels surrounding the explosions location, then within a certain radius they are removed from the world (what the PURPLE radius would be doing in my example) but with no consideration for the “hardness” of a pixel, but rather only if it is open (air) or solid (anything but air).

Here is a snippet of the C# code for removing pixels in explosions I use right now:

/* Explode */
    // Creates an "explosion" by finding all pixels near a point and launching them away
    public void explode(int xPos, int yPos, int radius)
    {
        // we don't explode if were hitting the boundaries, or shooting the open sky at an edge will explode!
        if (xPos > 1 && yPos > 1 &&
           xPos < worldWidth - 1 && yPos < worldHeight - 1)
        {
            radiusSq = radius * radius; // get a square radius for this explosion
            explodePosition.x = xPos; // set the position where it will happen
            explodePosition.y = yPos;

            randomNumber = Random.Range(0, 99); // generate a random number between 0 and 99 to select a random sprite using...

            CreateExplodeSprite(explodePosition, 0); // this is for the graphical "explosion" sprite to show

            // loop through every x from xPos-radius to xPos+radius
            for (int x = xPos - radius; x < xPos + radius; x += GameMaster.GM.DestructionResolution) // this "DestructionResolution" is always 1
            {
                // first make sure that the x is within terrain's boundaries
                if (x >= 0 && x < worldWidth)//pD.SourceTexture.width)
                {
                    // next loop through every y pos from yPos-radius to yPos+radius, having a "box" of the terrain we will check for whether or not its "solid"
                    for (int y = yPos - radius; y < yPos + radius; y += GameMaster.GM.DestructionResolution)
                    {
                        if (y >= 0 && y < worldHeight)//pD.SourceTexture.height) // boundary check
                        {
                            // first determine if this pixel (or if any contained within its square area) is solid
                            int solidX = 0, solidY = 0;
                            bool solid = false;
                            // loop through every pixel from (xPos,yPos) to (xPos + destructionRes, yPos + destructionRes)
                            // to find whether this area is solid or not
                            for (int i = 0; i < GameMaster.GM.DestructionResolution && !solid; i++) // if destruction resolution wasn't 1, this would loop through the "resolution" of pixels, but its 1...
                            {
                                for (int j = 0; j < GameMaster.GM.DestructionResolution && !solid; j++) // same here, this loops only a single time!
                                {
                                    if (GameMaster.GM.pixelMagic.isPixelSolid(x + i, y + j)) // returns true if the pixel is not "air" or a clear pixel
                                    {
                                        solid = true;
                                        solidX = x + i; // keep a reference to where that solid pixel is
                                        solidY = y + j;
                                    }
                                }
                            }
                            if (solid) // we know this pixel is solid, now we need to find out if it's close enough
                            {
                                float xDiff = x - xPos; // get the difference on x
                                float yDiff = y - yPos; // and y
                                float distSq = xDiff * xDiff + yDiff * yDiff; // as well as the the square distance
                                                                              // if the distance squared is less than radius squared, then it's within the explosion radius
                                if (distSq < radiusSq)
                                {
                                    // finally calculate the distance
                                    float distance = Mathf.Sqrt(distSq);

                                    if (distance == 0) // error prevention!
                                        distance = 0.001f; // prevent divide by zero later

                                    // remove the static pixels
                                    for (int i = 0; i < GameMaster.GM.DestructionResolution; i++) // only loops once
                                    {
                                        for (int j = 0; j < GameMaster.GM.DestructionResolution; j++) // only loops once
                                        {
                                            GameMaster.GM.pixelMagic.removePixel(x + i, y + j); // remove the pixel from our terrain
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

So how could I go about considering how hard a pixel is, and then make the explosion radius shorter when it is hitting more “harder” pixels, and longer when hitting “softer” pixels, which would make my explosion have smaller or larger radius (to a certain maximum) depending how “hard” a pixel is… and not allow it to destroy “invincible” pixels?

If you made it this far through this wall of text and have any ideas please comment or answer with anything that might push me in the right direction, and psuedocode or whatever is totally acceptable for me! I am sure others have run into similar problems within voxel engines and 3D destruction games, and perhaps other situations. I’ll be here scratching my head about it.

If the value for a pixel is “air” then it doesn’t need to be eligible to be blasted out. An if condition would settle that easily enough.

For every other pixel, you could perform a [breadth-first search][1]. Rather than looking for anything in particular, though, simply use this as your means of working away from the starting pixel. Test the center pixel (the strongest point for the blast), then work your way out from there.

For any pixel that is not blown up, save a note of its position. Then, on subsequent pixels, determine whether it should be protected by the pixels not blown up. If not, determine whether they’re weak enough to be blown up.

// Pseudo-code C#
public float protectionAngle = 0.95f; (Dot Product-based angle
System.Collections.Generic.List<Vector2> safePixels;
float blastRange; // Maximum range of blast
float blastStrength; // How strong is the blast initially?

safePixels = new List<Vector2>();
// Perform breadth-first search from center
foreach pixel from center, radiating to a maximum of "blastRange" pixels' distance ((pixel - center).magnitude)
{
	bool safe = false;
	for(int i = 0; i < safePixels.Count; i++)
	{
		if(Vector2.Dot(safePixels *- center, currentPixel - center) > protectionAngle)*
  •  {*
    
  •   	safe = true;*
    
  •  }*
    
  • }*
  • if(blastStrength > pixelHardness)*
  • {*
  •  // Blow it up...*
    
  •  // Then...*
    

_ blastStrength -= (pixelHardness * falloffRate); // falloffRate should be tiny, unless you want most of the explosive force lost when the blast is barely strong enough_

  • }*
  • else*
  • {*
  •  safePixels.Add(current Vector2);*
    
  • }*
    }
    It’ll take some engineering to get it all working together, but here’s the concept in brief:
    Start in the middle of the blast.
    Radiate out toward the ends, determining whether…
    1) Is the pixel shielded from the blast? If so, you’re done. It’s safe.
    2) Is the blast is strong enough to destroy the pixel?
    –a) If not, that one will now shield others behind it.
    –b) If so, how much will the blast’s remaining strength be reduced?
    Perform these tests on each pixel until you reach the maximum range from the center (or any other boundaries) and make sure not to retest any pixel more than once.
    Also, I didn’t really add it in sooner, but the blast strength per pixel would basically be calculated as something like:
    Mathf.Lerp(0, blastStrength, (currentPixel - center).magnitude / blastRange);
    [1]: Breadth-first search - Wikipedia