1

Basically, I am creating a voxel-based game. I have an object Chunk with a 3 dimensional array of the object Voxel. In the array of Voxels, the Voxel currently has an isAir bool on it that signifies no voxel being there.

I am trying to add a property to Chunk which returns a 2 dimensional array of Voxel's that are on the surface of the chunk. Currently, this array will always have Voxel.isAir == false in the lower Y values and Voxel.isAir == true in the higher Y values, there is never a clear run on the "Y" axis of the array that is either all "air"s or all Voxel's, and there is never a "air" below a Voxel in the array.

To get only the "Surface" Voxel's, I have added this code to the Chunk:

public Voxel[,,] Voxels { get; set; }

private Voxel[,] _surfaceVoxels = null;
public Voxel[,] SurfaceVoxels
{
    get
    {
        if (_surfaceVoxels == null)
        {
            _surfaceVoxels = new Voxel[this.Voxels.GetLength(0), this.Voxels.GetLength(2)];
            for (var x = 0; x < this.Voxels.GetLength(0); x++)
            {
                for (var z = 0; z < this.Voxels.GetLength(2); z++)
                {
                    for (var y = 0; y < this.Voxels.GetLength(1); y++)
                    {
                        Voxel v = this.Voxels[x, y, z];

                        var solidAbove = false;
                        var solidBelow = false;
                        if (y - 1 >= 0)
                        {
                            Voxel vBelow = this.Voxels[x, y - 1, z];
                            solidBelow = !vBelow.isAir;
                        }
                        if (y + 1 < this.Voxels.GetLength(1))
                        {
                            Voxel vAbove = this.Voxels[x, y + 1, z];
                            solidAbove = !vAbove.isAir;
                        }
                        if (!v.isAir && !solidAbove && solidBelow)
                        {
                            _surfaceVoxels[x, z] = v;
                        }
                    }
                }

            }

        }
        return _surfaceVoxels;
    }
}

Calculating the surface voxels this way is computationally expensive, and i cannot see a faster way of doing this. Because of this I effectively "cache" the surface array, which is fine until the underlying 3 dimensional array is changed, as then the surface voxels will obviously need to be recalculated.

Is there some way on the get of public Voxel[,,] it only returns a clone of the array and not the array itself, and on the set set the entire array and set _surfaceVoxels to NULL, or even better is there some inexpensive way to execute code when values in an array are changed?

Is there some a more efficient way to accomplish what I have explained here that I have overlooked?

James T
  • 1,155
  • 4
  • 17
  • 39
  • You could optimize slightly by breaking out of the `y` loop once your condition evaluates to `true`--unless it's possible to get multiple matches, in which case you could get the same effect by searching from the top rather than the bottom and breaking on the first match. Not a huge improvement, but a start. – adv12 Dec 20 '16 at 14:23
  • @adv12 I meant to do this when i initially wrote the loop... thanks for reminding me. Currently its not possible for multiple matches. – James T Dec 20 '16 at 14:24
  • If you change your y loop to go backwards from the max index to 0 instead of up you could break out of the loop once you find the first matching condition. – juharr Dec 20 '16 at 14:28
  • 1
    You should expose an indexer instead of an array. That way when a specific value is updated you can update your internal surface area. Right now if you update a value in the array you will not know, you only know if the entire array is updated. – juharr Dec 20 '16 at 14:32
  • You might want to consider using jagged arrays instead of a multi-dimentional array. You can keep track of the dementions in separate fields. http://stackoverflow.com/questions/597720/what-are-the-differences-between-a-multidimensional-array-and-an-array-of-arrays – juharr Dec 20 '16 at 14:41
  • To expand on @juharr's comment, if you use an indexer and someone only modifies values in a single "column" (i.e. single x,z location), you only have to update your cached _surfaceVoxels for that column. Depending on what sort of modifications are happening, that could be a real time saver. – adv12 Dec 20 '16 at 14:42
  • @adv12 something like you are suggesting has already crossed my mind(the only having to update a single column when a value in that column has changed), but I do not know what you are talking about or how to implement it. – James T Dec 20 '16 at 14:46
  • @JamesTrotter, indexers are like properties but they take an "index." MSDN: https://msdn.microsoft.com/en-us/library/6x16t2tx.aspx You can use multi-dimensional indexers, but apparently they're frowned upon: https://msdn.microsoft.com/en-us/library/ms182152.aspx – adv12 Dec 20 '16 at 14:50
  • On a quick skim, I don't understand why multidimensional indexers are frowned upon, but instead you could just expose methods like `Voxel GetVoxel(int x, int y, int z)` and `void SetVoxel(int x, int y, int z, Voxel v)`. – adv12 Dec 20 '16 at 14:51
  • The point is that if you control access to the contents of the actual array, you can write whatever code you want to update your internal bookkeeping when the array is changed. This means that if you remove the `Voxels` property and expose a `SetVoxel` method, you could update your cached `_surfaceVoxels` array within `SetVoxel` and because there's no other means for a user to change the internal voxels array, your _surfaceVoxels array stays up to date. – adv12 Dec 20 '16 at 14:56

0 Answers0