Skip to content

Instantly share code, notes, and snippets.

@andrew-raphael-lukasik
Last active October 10, 2021 08:39
Show Gist options
  • Save andrew-raphael-lukasik/b1cba7b10407c7540e33f1c0a9930746 to your computer and use it in GitHub Desktop.
Save andrew-raphael-lukasik/b1cba7b10407c7540e33f1c0a9930746 to your computer and use it in GitHub Desktop.
tiny facade struct that provides [x,y] indexer accessors for arrays
// src* = https://gist.github.com/andrew-raphael-lukasik/b1cba7b10407c7540e33f1c0a9930746
using UnityEngine;
public struct Array2DView <T>
{
public T[] array;
public int width, height;
public T this [ int i ]
{
get
{
#if DEBUG
if( i<0 ) Debug.LogErrorFormat("index:{0} must be > 0",i);
if( i>=array.Length ) Debug.LogErrorFormat("index:{0} must be < {1} (array.Length)",i,array.Length);
#endif
return array[i];
}
set
{
#if DEBUG
if( i<0 ) Debug.LogErrorFormat("index:{0} must be > 0",i);
if( i>=array.Length ) Debug.LogErrorFormat("index:{0} must be < {1} (array.Length)",i,array.Length);
#endif
array[i] = value;
}
}
public T this [ int x , int y ]
{
get
{
#if DEBUG
if( x<0 ) Debug.LogErrorFormat("x:{0} must be > 0",x);
if( y<0 ) Debug.LogErrorFormat("y:{0} must be > 0",y);
if( x>=width ) Debug.LogErrorFormat("x:{0} must be < {1} (array width)",x,width);
if( y>=height ) Debug.LogErrorFormat("x:{0} must be < {1} (array height)",x,height);
#endif
return array[ y*width + x ];
}
set
{
#if DEBUG
if( x<0 ) Debug.LogErrorFormat("x:{0} must be > 0",x);
if( y<0 ) Debug.LogErrorFormat("y:{0} must be > 0",y);
if( x>=width ) Debug.LogErrorFormat("x:{0} must be < {1} (array width)",x,width);
if( y>=height ) Debug.LogErrorFormat("x:{0} must be < {1} (array height)",x,height);
#endif
array[ y*width + x ] = value;
}
}
public T this [ Vector2Int coord ]
{
get => this[ coord.x , coord.y ];
set => this[ coord.x , coord.y ] = value;
}
}
public static class ExtensionMethods_Array2DView
{
public static Array2DView<T> AsArray2D <T> ( this T[] array , int width , int height )
{
if( (width*height)!=array.Length ) Debug.LogErrorFormat("{0} (width) * {1} (height) != {2} (array.Length)",width,height,array.Length);
return new Array2DView<T>{ array=array , width=width , height=height };
}
public static Array2DView<T> AsArray2D <T> ( this T[] array , int width ) => array.AsArray2D( width , array.Length/width );
}
@andrew-raphael-lukasik
Copy link
Author

andrew-raphael-lukasik commented Oct 4, 2021

Example of usage:

using UnityEngine;

public class UnityPaint : MonoBehaviour
{
	// core of the concept:
	[SerializeField] Color32[] _data1D = null;// serializable array
	Array2DView<Color32> _data2D;// array 2D view, a facade

	// the rest of the script
	[SerializeField] Vector2Int _textureSize = new Vector2Int{ x=512 , y=512 };
	[SerializeField] Color32 _color = Color.yellow;
	Texture2D _texture = null;
	Vector2Int _prevPos;

	void OnEnable ()
	{
		int width = _textureSize.x;
		int height = _textureSize.y;
		_data1D = new Color32[ width * height ];
		_data2D = _data1D.AsArray2D( width );

		_texture = new Texture2D( width , height , TextureFormat.ARGB32 , 0 , true );
		for( int i=0 ; i<_data1D.Length ; i++ ) _data1D[i] = new Color32{ r=255 , g=255 , b=255 , a=0 };
        _texture.SetPixels32( _data1D );
		_texture.Apply();
	}

	void OnDisable () => Destroy( _texture );
    
	void Update ()
	{
        Vector2Int pos = Vector2Int.RoundToInt( Input.mousePosition / new Vector2{ x=Screen.width , y=Screen.height } * new Vector2{ x=_data2D.width , y=_data2D.height } );
        if( Input.GetMouseButtonDown(0) && pos!=_prevPos )
        {
            _prevPos = pos;
            _data2D[ pos ] = _color;
            
            _texture.SetPixels32( _data1D );
			_texture.Apply();
        }
		else if( Input.GetMouseButton(0) && pos!=_prevPos )
		{
			FillSegment( _data2D , _prevPos , pos , _color );
			_prevPos = pos;
			
            _texture.SetPixels32( _data1D );
			_texture.Apply();
		}
	}

	void OnGUI () => Graphics.DrawTexture( new Rect{ width=Screen.width , height=Screen.height } , _texture );

    // src: https://www.redblobgames.com/grids/line-drawing.html#orthogonal-steps
	public void FillSegment <T> ( Array2DView<T> gridView , Vector2Int a , Vector2Int b , T value )
	{
		float dx = b.x-a.x, dy = b.y-a.y;
		int nx = (int) Mathf.Abs(dx), ny = (int) Mathf.Abs(dy);
		int sign_x = dx > 0? 1 : -1, sign_y = dy > 0? 1 : -1;
		Vector2Int coord = a;
		gridView[coord] = value;
		for( int ix=0, iy=0 ; ix<nx || iy<ny; )
		{
			if( (0.5+ix)/nx < (0.5+iy)/ny ) { coord.x += sign_x; ix++; }
			else { coord.y += sign_y; iy++; }
			gridView[coord] = value;
		}
	}

}

preview

@Llama-W-2Ls
Copy link

Llama-W-2Ls commented Oct 10, 2021

Very nice. I usually implement my 2D arrays as 1D arrays and use some indexing math to convert Vector2 coords to their respective integer indexes.

You could extend this to 3D perhaps, using z * sizeY * sizeX + y * sizeX + x. This helped me a lot when I was doing a minecraft clone, which allowed me to store all blocks in the chunk in a single array, without needing an extra field in the block data to define its position in world space, since you can convert the integer index to a 3D coord by reversing the formula:

`int z = index / (sizeY * sizeX)

index -= z * sizeY * sizeX;
int y = index / sizeX

int x = index - y * sizeX`

Also, keeping data in a fixed 1D array has apparently faster lookup times than a multidimensional array or even a jagged array, and supposedly uses less memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment