简体   繁体   中英

How to calculate an average from 9 values placed around a square shape

在此输入图像描述

I have an evenly-spaced grid of numerical values, each in the center of a cell. For simplification, let's assume that every cell is 1.0 by 1.0 big. I am looking for a way to calculate an average from the neighboring cells and the center cell, given any arbitrary point. I tried to come up with an algorithm that used weights and distances, and it mostly works, but I get incorrect values on the edges of cells (the transition between cells wasn't smooth as I was moving p0 around; there is a sudden, steep change in value when it moves to an adjacent cell). That's probably because the values are not spaced out on a circle but on a square, and so diagonally they have different distances from the center point than horizontally or vertically.

In the picture above, there are 9 values designated as v0-v8 . v0 is the center value. p0 is an arbitrary point within the center cell. If p0 moves onto the range of another cell, then that cell will become the center cell (the perspective changes obviously).

Requirements:

  • If p0 is at the same location as v0 , then p0 == v0 .
  • No trigonometric, power, or root functions. This piece of code will be critical to performance. Only simple arithmetic operations.

The reason behind all this is that I want to create a faster way to access values that are very slow to calculate, but if they were precalculated with a specific granularity into a grid-like cache, I could just take an average of the nearest ones.

This is what I have come up with so far:

double value = 0;

// If we're closer to the northern edge
if (z < CellRadius)
{
  double centeredness = z / CellRadius;
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (
      centerWeight * Cells[i, j]
          + edgeWeight * Cells[i, j - 1]
  ) / 4.0;
}
// If we're closer to the southern edge
else if (z >= CellRadius)
{
  double centeredness = (
      CellRadius - (z - CellRadius)
  ) / CellRadius;
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (
      centerWeight * Cells[i, j]
          + edgeWeight * Cells[i, j + 1]
  ) / 4.0;
}

// If we're closer to the western edge
if (x < CellRadius)
{
  double centeredness = x / CellRadius;
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (
      centerWeight * Cells[i, j]
          + edgeWeight * Cells[i - 1, j]
  ) / 4.0;
}
// If we're closer to the eastern edge
else if (x >= CellRadius)
{
  double centeredness = (
      CellRadius - (x - CellRadius)
  ) / CellRadius;
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (
      centerWeight * Cells[i, j]
          + edgeWeight * Cells[i + 1, j]
  ) / 4.0;
}

// If we're closer to the north-western edge
if (x < CellRadius && z < CellRadius)
{
  double centeredness = (x / CellRadius) * (z / CellRadius);
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (centerWeight * Cells[i, j] + edgeWeight * (
      Cells[i - 1, j - 1]
  )) / 2.0;
}
// If we're closer to the south-eastern edge
else if (x >= CellRadius && z >= CellRadius)
{
  double centeredness = (
      CellRadius - (x - CellRadius)
  ) / CellRadius * (
      CellRadius - (z - CellRadius)
  ) / CellRadius;
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (centerWeight * Cells[i, j] + edgeWeight * (
      Cells[i + 1, j + 1]
  )) / 2.0;
}
// If we're closer to the north-eastern edge
else if (x >= CellRadius && z < CellRadius)
{
  double centeredness = (
      CellRadius - (x - CellRadius)
  ) / CellRadius * (z / CellRadius);
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (centerWeight * Cells[i, j] + edgeWeight * (
      Cells[i + 1, j - 1]
  )) / 2.0;
}
// If we're closer to the south-western edge
else if (x < CellRadius && z >= CellRadius)
{
  double centeredness = (x / CellRadius) * (
      CellRadius - (z - CellRadius)
  ) / CellRadius;
  double centerWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 1.0);
  double edgeWeight = Utils.Lerp(centeredness, 1.0 / 2.0, 0.0);

  value += (centerWeight * Cells[i, j] + edgeWeight * (
      Cells[i - 1, j + 1]
  )) / 2.0;
}

return value;

And as explained earlier, it doesn't work correctly.

I find it hard to understand what you're trying to accomplish:

If you want the average of these points, you could just take 1/10th of offset p0 from the center point v0. Since v0-v8 are 9 points total, and p0 is 1 point, that makes 10 points, hence the 1/10th.

Say we ignore p0 for now and try to calculate the average of v0-v8, it will be (0, 0) in the situation you're describing. Taking a "weight" into account for this situation, we could say that this is 9, because they are 9 points.

Even if we move both points of any of the pairs v2&v8, v4&v6, v3&v7 or v1&v5 the same distance away from v0, the average will still be (0, 0). Taking this into account means that it doesnt matter how far the distance is between the points of the mentioned pairs, away from v0, as long as (for example v2 and v8) the distance of both of the points is the same.

In your code i see that you treat "north-west" differently than "north" or "west" for example, because of above explanation i don't think this is necessary. Besides that I think the algorithm is pretty complicated while I described above that you could just take 1/10th of p0.

Hope this helps, if I misunderstood your question, please elaborate! :)

EDIT: In your question you made the assumption that because the values are not spaced out on a circle but on a square, diagonally they have different distances from the center point than horizontally or vertically. In theory, this is true.

About this assumption I want to point out that to even consider this distance in code, you would need a square root (Pythagorean theorem). The available data for this distance, I would assume is a difference in x and y.

Following up on your assumption, looking at the example image, the difference in y between v3 and p0/v2 and p0 is the same, and the difference in x between v2 and p0/v1 and p0 is also the same.

Either way, only difference in x and y between v0 and p0 is needed for the calculation i'd say.

I think this should do. I am using bi-linear interpolation.

public class Mesh
{
    public Mesh(int n)
    {
        this.Size = n;
        this.Cells = new double[n, n];
    }
    public int Size { get; }
    public double[,] Cells { get; }
    public double CellRadius { get; } = 1;

    /// <summary>
    /// Peform inertpolation among cells.
    /// </summary>
    public double Value(double x, double z)
    {
        //   |          |
        // --*----------*-----------> x
        //   | v(i,j)   | v(i,j+1)
        //   |          |
        //   |          |
        // --*----------*--
        //   | v(i+1,k) | v(i+1,j+1)
        //   |
        //   v 
        //   z

        // Find top left cell location
        double h = CellRadius;
        int i = (int)Math.Floor(z/h);
        if(i>=Size-1) { i=Size-2; }
        int j = (int)Math.Floor(x/h);
        if(j>=Size-1) { j=Size-2; }
        var v_11 = Cells[i, j];
        var v_12 = Cells[i, j+1];
        var v_21 = Cells[i+1, j];
        var v_22 = Cells[i+1, j+1];
        x -= j*h;
        z -= i*h;

        var ξ = x/h; //pre compute ratio for speed
        var v_1 = (1-ξ)*v_11 + (ξ)*v_12;
        var v_2 = (1-ξ)*v_21 + (ξ)*v_22;
        var ζ = z/h; //pre compute ratio for speed
        return (1-ζ)*v_1 + (ζ)*v_2;
    }
}
class Program
{
    static void Main(string[] args)
    {
        var mesh = new Mesh(3);
        mesh.Cells[0, 0] = 1;
        mesh.Cells[0, 1] = 2;
        mesh.Cells[0, 2] = 2;

        mesh.Cells[1, 0] = 1;
        mesh.Cells[1, 1] = 1;
        mesh.Cells[1, 2] = 2;

        mesh.Cells[2, 0] = 0;
        mesh.Cells[2, 1] = 1;
        mesh.Cells[2, 2] = 0;

        Console.WriteLine($"{"x",12} {"z",12} {"v",12}");
        for(int i = 0; i < 10; i++)
        {
            var x = 1.2;
            var z = 3.0*i/9;
            var v = mesh.Value(x, z);
            Console.WriteLine($"{x,12:g6} {z,12:g6} {v,12:g6}");
        }
    }
}

with sample output:

       x            z            v
     1.2            0            2
     1.2     0.333333      1.73333
     1.2     0.666667      1.46667
     1.2            1          1.2
     1.2      1.33333      1.06667
     1.2      1.66667     0.933333
     1.2            2          0.8
     1.2      2.33333     0.666667
     1.2      2.66667     0.533333
     1.2            3          0.4

What average are you trying to find? A linear average? That really doesn't make to much sense when you are basically defining a non linear surface.

One way to go about this would be to evaluate linear averages on all verticees; numerating left to right and top - down you'd have:

  • 1: v2
  • 2: (v2 + v3) / 2
  • 3: (v3 + v4) / 2
  • 4: v4
  • 5: (v1 + v2) / 2
  • 6: (v0 + v1 + v2 + v3) / 4
  • etc.

And now you have 16 (x, y, z) coordinates. Find a least squares algorithm that produces the best fit with simple functions x, y, x^2, y^2, x^3, y^3 (shouldn't need more than cubic functions) and once you have the surface equation, evaluating the elevation of any valid point inside the problem's domain is straightforward.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM