Issues wrapping my brain around this math

This is a problem for you math wizards that I have been stumped on for a while even though I feel like I should be able to solve it.

What I am doing : I am taking an image, or rather, float[,] values from an image and putting them into an array. From that array I am “upressing” the values so that the range of values turns into a larger pool. From there I am dividing it up into chunks. This is for terrain generation.

So for this example.

  • N = 512 (the original input
    dimensions. 512*512)
  • Up = 5 (the upres amount)
  • Cs = 512 (The chunk dimensions.
    512*512)
  • Ms = N * Up (The total dimensions of
    the upresed array. 2560*2560)
  • Gs = Ms/Cs (The grid dimensions. 5*5
    chunks)

Currently I am upresing the entire thing all at once. What I want to be able to do is upress only the necessary areas. As you can imagine, upressing a 512512 image 50 times (2560025600) would take a while to process a lot of unnecessary data…especially when its going to be representing terrain that the player wont be near.

Where I fail: I need to be able to access the specific values that are getting upresed into the chunk dimensions (512x512) from the original input (N) and upres only those. I cant have float values and I cant be rounding. The values exist and are being upressed, I just cant think of a way to reverse this process.

Conclusion: I want to collect only the necessary values from the original input and pass those to be upresed instead of unpresing the entire original input array.

Here is the code I am currently working with.

float[,] originalInput; // an array containing the original values, im using 512*512 (N)
int upres = 5; // how many times the original values are upres (Up)
originalInput = UpresHeights(originalInput, upres); //upres the input 
int mapsize = originalInput.GetLength(0); // (Ms)
int chunkSize = 512; //(Cs)
int gridSize = (int)Mathf.Round((mapsize / (float)chunkSize)); //Grid size (Gs)

    float[,] UpresHeights(float[,] oldHeights, int upres)
    {
        //Duplicate the array input array
        int originalHeight = oldHeights.GetLength(0);
        float[,] heights = new float[originalHeight, originalHeight];
        for (int y = 0; y < originalHeight; y++)
        {
            for (int x = 0; x < originalHeight; x++)
            {
                float val = oldHeights[x, y];
                heights[x, y] = val;
            }
        }
        //Upres the new array
        int heightsLength = originalHeight;
        int resolution = (heightsLength * upres);
        float[,] newHeights = new float[resolution, resolution];
        for (int y = 0; y < originalHeight; y++)
        {
            for (int x = 0; x < originalHeight; x++)
            {
                //The original input is tileable, so the following
                //just makes sure to sample from the opposite side
                //of the array if at the border
                float right = 0.0f;
                if (x == originalHeight - 1)
                {
                    right = heights[0, y];
                }
                else {
                    right = heights[x + 1, y];
                }

                float top = 0.0f;
                if (y == originalHeight - 1)
                {
                    top = heights[x, 0];
                }
                else {
                    top = heights[x, y + 1];
                }

                for (int xx = 0; xx < upres; xx++)
                {
                    for (int yy = 0; yy < upres; yy++)
                    {
                        //smooth the values (could use vector2.distance, 
                        //but this isnt important right now)
                        //not that important since blur is applied later               
                        float a = Mathf.Lerp(heights[x, y], right, (xx / (float)upres));
                        float b = Mathf.Lerp(heights[x, y], top, (yy / (float)upres));
                        newHeights[x * upres + xx, y * upres + yy] = (a + b) / 2f;
                    }
                }
            }
        }
        return newHeights;
    }

Well, it all boils down how you want to interpolate the values across your new resolution. What you do currently is kinda strange:

top----unused
 |       |
 |       |
x/y----right

Currently you ignore the height at “unused” and only interpolate in the lower left triangle.

Maybe you want a bilinear interpolation?

As for how to find the right values, first need some additional information: The chunk x and y position in “chunk coordinates”. So the first chunk would have (0, 0) the second on the x axis would have (1, 0). The last chunk would be (4,4)

Now you could split the problem into smaller pieces but the easiest way is to simply iterate through your target array and work out the 4 required source coordinates.

// chunk coordinate
int cx;
int cy;

// this is the offset of this chunk inside the source image
Vector2 offset = new Vector2(cx * Up, cy * Up);

for(int y = 0; y < chunkSize; y++)
{
    for(int x = 0; x < chunkSize; x++)
    {
        // "p" is the exact point inside the source image including fraction
        var p = offset + new Vector2(x,y) / Up;
        // get the bottom left and upper right corners
        int x0 = Mathf.FloorToInt(p.x);
        int y0 = Mathf.FloorToInt(p.y);
        int x1 = (x0 >= originalHeight - 1)?0:x0 + 1;
        int y1 = (y0 >= originalHeight - 1)?0:y0 + 1;
        var t = p - new Vector2(x0, y0); // extract the fractional part
        float r0 = Mathf.Lerp(heights[x0, y0], heights[x1, y0], t.x);
        float r1 = Mathf.Lerp(heights[x0, y1], heights[x1, y1], t.x);
        newHeights[x,y] = Mathf.Lerp(r0, r1, t.y);
    }
}

Note: I’ve written this from scratch without testing or syntax checking, so use at your own risk ^^.

Heres the solution.

I needed to create two new variables

//The sample area "per chunk" of the lower resolution array
lowResWidth = (N) / (Gs);
//the value of a step. Since its being upresed and between each value in //the array many other values are being made, this would be a float
//example: between value 1 and value 2, theres 5 values with a pixel
//step of 0.2
pixelstep = lowResWidth / (CS);

My grid was already set up with x and y coordinates, so i needed to find the start value of the x and y in relation to the “downscaled” version

        float startX = lowResWidth * gridpoint.x;
        float startY = lowResWidth * gridpoint.y;

I created a new function to return an upresed array from that specific area of the downscaled array called with

float[,] segment = UpresSeg(originalValues(N), chunkDimensions(Cs), startX, startY, gridPoint.x, gridPoint.y);

and the meat:

    float[,] UpresSeg(float[,] originalHeight, int resolution, float startX, float startY, int gridX, int gridY)
    {
        //create a new array to return
        float[,] returnThis = new float[resolution, resolution];

        //Since its possible to start on a float (example 1.2), we need to floor the original start values
        int origStartX = Mathf.FloorToInt(startX);
        int origStartY = Mathf.FloorToInt(startY);

        //we also need to subtract the original start floored value from the given start so we can retieve our
        //xstep and ystep starting points
        float xStep = startX - origStartX;
        float yStep = startY - origStartY;      

        int xCounter = 0;
        int yCounter = 0;        

        for (int y = 0; y < resolution; y++)
        {
            //reset the x counter for the new row
            xCounter = 0;
            for (int x = 0; x < resolution; x++)
            {
                int origX = Mathf.FloorToInt(gridX * lowResWidth) + xCounter;
                int origY = Mathf.FloorToInt(gridY * lowResWidth) + yCounter;

                float right = 0.0f;
                float top = 0.0f;

                int xMod = 0;
                int yMod = 0;

                if (origX < originalHeight.GetLength(0) - 1)
                {
                    xMod = origX + 1;
                }
                else {
                    xMod = originalHeight.GetLength(0) - 1;
                }

                right = originalHeight[xMod, origY];

                if (origY < originalHeight.GetLength(1) - 1)
                {
                    yMod = origY + 1;
                }
                else {
                    yMod = originalHeight.GetLength(1) - 1;
                }

                top = originalHeight[origX, yMod];                    

                float a = Mathf.Lerp(originalHeight[origX, origY], right, xStep);
                float b = Mathf.Lerp(originalHeight[origX, origY], top, yStep);               
                returnThis[x, y] = (a + b) / 2f;

                xStep += pixelstep;                
                if (xStep >= pixelstep * tileSettings.upres)
                {
                    xStep = 0.0f;
                    xCounter++;                  
                }      
            }
            yStep += pixelstep;
            if (yStep >= pixelstep * tileSettings.upres)
            {
                yStep = 0.0f;                
                yCounter++;             
            }
            
        }
        return returnThis;
    }

There are some new issues that arise for me, but those are related to my generation script and what it does after this step. This might not be the best way to do this or even the “smartest” way, but it works