├── LICENSE ├── LICENSE.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── FLOAT.cs ├── FLOAT.cs.meta ├── INT.cs ├── INT.cs.meta ├── NativeGrid core assertions.cs ├── NativeGrid core assertions.cs.meta ├── NativeGrid core functions.cs ├── NativeGrid core functions.cs.meta ├── NativeGrid core jobs.cs ├── NativeGrid core jobs.cs.meta ├── NativeGrid core methods.cs ├── NativeGrid core methods.cs.meta ├── NativeGrid enumerators.cs ├── NativeGrid enumerators.cs.meta ├── NativeGrid pathfinding.cs ├── NativeGrid pathfinding.cs.meta ├── NativeGrid tracing.cs ├── NativeGrid tracing.cs.meta ├── NativeGrid.cs ├── NativeGrid.cs.meta ├── NativeMinHeap.cs └── NativeMinHeap.cs.meta ├── Samples~ └── Texture2D │ └── NativeGridPaint.cs ├── Tests.meta └── Tests ├── Editor.meta └── Editor ├── Test NativeGrid Pathfinding.cs ├── Test NativeGrid Pathfinding.cs.meta ├── Test NativeGrid.cs ├── Test NativeGrid.cs.meta ├── Test NativeMinHeap.cs ├── Test NativeMinHeap.cs.meta ├── Test enumerators.cs └── Test enumerators.cs.meta /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrew Raphael Lukasik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab56ea76a6e3f55448a4277df6883226 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NativeGrid 2 | 3 | ### NativeGrid: 4 | A bunch of 2D data utilities for ECS and `Unity.Jobs`. 5 | 6 | ### NativeGrid<T>: 7 | Managed container that stores `NativeArray` with it's basic info to use in 2d/grid manner. 8 | 9 | --- 10 | - Decent A*/AStar implementation 11 |

12 | 13 | 14 |

15 | 16 | > Test window available under: Test>NativeGrid>Pathfinding 17 | 18 | - Here is an example how you can store store `RawTextureData` (pointer to a CPU-side texture buffer) inside this `NativeGrid`-thing and do something ambiguously useful with it, like idk, trace and draw lines/paths on that texture: 19 | ```csharp 20 | var fillLineJob = GRID.FillLine( startPixel , endPixel , fillColor ); 21 | fillLineJob.Complete(); 22 | ``` 23 | Full example code: [a relative link](/Samples~/Texture2D/NativeGridPaint.cs) 24 | Note: this saves RAM because texture memory is not being duplicated on CPU-side anymore. 25 | - Performant, allocation-free enumerator to find all neighbouring cells (`NeighbourEnumerator : INativeEnumerator`) 26 | ``` 27 | var enumerator = new NeighbourEnumerator( coord:new int2(0,1) , gridWidth:128 , gridHeight:128 ); 28 | while( enumerator.MoveNext(out int2 neighbourCoord) ) 29 | { 30 | /* wow, it's so easy :O */ 31 | } 32 | ``` 33 | - Trace lines (Bresenham's algorithm) 34 | - Schedule your jobs to read/write to grid.Array using grid.Dependency JobHandle 35 | 36 | # installation 37 | Add this line in `manifest.json` / `dependencies`: 38 | ``` 39 | "com.andrewraphaellukasik.nativegrid": "https://github.com/andrew-raphael-lukasik/NativeGrid.git#upm", 40 | ``` 41 | 42 | Or via `Package Manager` / `Add package from git URL`: 43 | ``` 44 | https://github.com/andrew-raphael-lukasik/NativeGrid.git#upm 45 | ``` 46 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fc2cb86b275fc504480426f2a0b4f393 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19029d686cb09364db2018b1e3971002 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/FLOAT.cs: -------------------------------------------------------------------------------- 1 | /// src: https://github.com/andrew-raphael-lukasik/Unity.Mathematics.Explicit 2 | using UnityEngine; 3 | using Unity.Mathematics; 4 | 5 | namespace NativeGridNamespace 6 | { 7 | public struct FLOAT4 8 | { 9 | float4 Value; 10 | 11 | public float x => Value.x; 12 | public float y => Value.y; 13 | public float z => Value.z; 14 | public float w => Value.w; 15 | 16 | public static implicit operator float4 ( FLOAT4 F4 ) => F4.Value; 17 | public static implicit operator FLOAT4 ( float4 f4 ) => new FLOAT4{ Value=f4 }; 18 | public static implicit operator FLOAT4 ( Vector4 v4 ) => new FLOAT4{ Value=v4 }; 19 | public static implicit operator Vector4 ( FLOAT4 F4 ) => F4.Value; 20 | public static implicit operator FLOAT4 ( (float x,float y,float z,float w) tuple ) => new FLOAT4{ Value=new float4{ x=tuple.x , y=tuple.y , z=tuple.z , w=tuple.w } }; 21 | 22 | public static float4 operator + ( FLOAT4 a , FLOAT4 b ) => a.Value + b.Value; 23 | public static float4 operator - ( FLOAT4 a , FLOAT4 b ) => a.Value - b.Value; 24 | public static float4 operator * ( FLOAT4 a , FLOAT4 b ) => a.Value * b.Value; 25 | public static float4 operator / ( FLOAT4 a , FLOAT4 b ) => a.Value / b.Value; 26 | public static float4 operator * ( FLOAT4 F4 , float f ) => F4.Value * f; 27 | 28 | override public string ToString () => this.Value.ToString(); 29 | } 30 | 31 | public struct FLOAT3 32 | { 33 | float3 Value; 34 | 35 | public float x => Value.x; 36 | public float y => Value.y; 37 | public float z => Value.z; 38 | 39 | public float2 XZ () => new float2{ x=this.Value.x , y=this.Value.z }; 40 | 41 | public static implicit operator float3 ( FLOAT3 F3 ) => F3.Value; 42 | public static implicit operator FLOAT3 ( float3 f3 ) => new FLOAT3{ Value=f3 }; 43 | public static implicit operator FLOAT3 ( Vector3 v3 ) => new FLOAT3{ Value=v3 }; 44 | public static implicit operator Vector3 ( FLOAT3 F3 ) => F3.Value; 45 | public static implicit operator FLOAT3 ( (float x,float y,float z) tuple ) => new FLOAT3{ Value=new float3{ x=tuple.x , y=tuple.y , z=tuple.z } }; 46 | 47 | public static float3 operator + ( FLOAT3 a , FLOAT3 b ) => a.Value + b.Value; 48 | public static float3 operator - ( FLOAT3 a , FLOAT3 b ) => a.Value - b.Value; 49 | public static float3 operator * ( FLOAT3 a , FLOAT3 b ) => a.Value * b.Value; 50 | public static float3 operator / ( FLOAT3 a , FLOAT3 b ) => a.Value / b.Value; 51 | public static float3 operator * ( FLOAT3 F3 , float f ) => F3.Value * f; 52 | 53 | override public string ToString () => this.Value.ToString(); 54 | } 55 | 56 | public struct FLOAT2 57 | { 58 | float2 Value; 59 | 60 | public float x => Value.x; 61 | public float y => Value.y; 62 | 63 | public static implicit operator float2 ( FLOAT2 F2 ) => F2.Value; 64 | public static implicit operator FLOAT2 ( float2 f2 ) => new FLOAT2{ Value=f2 }; 65 | public static implicit operator FLOAT2 ( Vector2 v2 ) => new FLOAT2{ Value=v2 }; 66 | public static implicit operator Vector2 ( FLOAT2 F2 ) => F2.Value; 67 | public static implicit operator FLOAT2 ( (float x,float y) tuple ) => new FLOAT2{ Value=new float2{ x=tuple.x , y=tuple.y } }; 68 | 69 | public static float2 operator + ( FLOAT2 a , FLOAT2 b ) => a.Value + b.Value; 70 | public static float2 operator - ( FLOAT2 a , FLOAT2 b ) => a.Value - b.Value; 71 | public static float2 operator * ( FLOAT2 a , FLOAT2 b ) => a.Value * b.Value; 72 | public static float2 operator / ( FLOAT2 a , FLOAT2 b ) => a.Value / b.Value; 73 | public static float2 operator * ( FLOAT2 F2 , float f ) => F2.Value * f; 74 | 75 | override public string ToString () => this.Value.ToString(); 76 | } 77 | 78 | public struct FLOAT 79 | { 80 | float Value; 81 | 82 | public static implicit operator float ( FLOAT F ) => F.Value; 83 | public static implicit operator FLOAT ( float f ) => new FLOAT{ Value=f }; 84 | 85 | public static float operator + ( FLOAT a , FLOAT b ) => a.Value + b.Value; 86 | public static float operator - ( FLOAT a , FLOAT b ) => a.Value - b.Value; 87 | public static float operator * ( FLOAT a , FLOAT b ) => a.Value * b.Value; 88 | public static float operator / ( FLOAT a , FLOAT b ) => a.Value / b.Value; 89 | public static float operator * ( FLOAT F2 , float f ) => F2.Value * f; 90 | 91 | override public string ToString () => this.Value.ToString(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /Runtime/FLOAT.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d638e595cc955774e8d859573973fff1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/INT.cs: -------------------------------------------------------------------------------- 1 | /// src: https://github.com/andrew-raphael-lukasik/Unity.Mathematics.Explicit 2 | using UnityEngine; 3 | using Unity.Mathematics; 4 | 5 | namespace NativeGridNamespace 6 | { 7 | public struct INT4 8 | { 9 | int4 Value; 10 | 11 | public int x => Value.x; 12 | public int y => Value.y; 13 | public int z => Value.z; 14 | public int w => Value.w; 15 | 16 | public static implicit operator int4 ( INT4 I4 ) => I4.Value; 17 | public static implicit operator INT4 ( int4 i4 ) => new INT4{ Value=i4 }; 18 | public static implicit operator INT4 ( (int x,int y,int z,int w) tuple ) => new INT4{ Value=new int4{ x=tuple.x , y=tuple.y , z=tuple.z , w=tuple.w } }; 19 | 20 | public static int4 operator + ( INT4 a , INT4 b ) => a.Value + b.Value; 21 | public static int4 operator - ( INT4 a , INT4 b ) => a.Value - b.Value; 22 | public static int4 operator * ( INT4 a , INT4 b ) => a.Value * b.Value; 23 | public static int4 operator / ( INT4 a , INT4 b ) => a.Value / b.Value; 24 | public static int4 operator * ( INT4 F3 , int f ) => F3.Value * f; 25 | 26 | override public string ToString () => this.Value.ToString(); 27 | } 28 | 29 | public struct INT3 30 | { 31 | int3 Value; 32 | 33 | public int x => Value.x; 34 | public int y => Value.y; 35 | public int z => Value.z; 36 | 37 | public int2 XZ () => new int2{ x=this.Value.x , y=this.Value.z }; 38 | 39 | public static implicit operator int3 ( INT3 I3 ) => I3.Value; 40 | public static implicit operator INT3 ( int3 i3 ) => new INT3{ Value=i3 }; 41 | public static implicit operator INT3 ( Vector3Int v3 ) => new INT3{ Value=new int3{ x=v3.x , y=v3.y , z=v3.z } }; 42 | public static implicit operator Vector3Int ( INT3 I3 ) => new Vector3Int{ x=I3.Value.x , y=I3.Value.y , z=I3.Value.z }; 43 | public static implicit operator INT3 ( (int x,int y,int z) tuple ) => new INT3{ Value=new int3{ x=tuple.x , y=tuple.y , z=tuple.z } }; 44 | 45 | public static int3 operator + ( INT3 a , INT3 b ) => a.Value + b.Value; 46 | public static int3 operator - ( INT3 a , INT3 b ) => a.Value - b.Value; 47 | public static int3 operator * ( INT3 a , INT3 b ) => a.Value * b.Value; 48 | public static int3 operator / ( INT3 a , INT3 b ) => a.Value / b.Value; 49 | public static int3 operator * ( INT3 F3 , int f ) => F3.Value * f; 50 | 51 | override public string ToString () => this.Value.ToString(); 52 | } 53 | 54 | public struct INT2 55 | { 56 | int2 Value; 57 | 58 | public int x => Value.x; 59 | public int y => Value.y; 60 | 61 | public static implicit operator int2 ( INT2 I2 ) => I2.Value; 62 | public static implicit operator INT2 ( int2 i2 ) => new INT2{ Value=i2 }; 63 | public static implicit operator INT2 ( Vector2Int v2 ) => new INT2{ Value=new int2{ x=v2.x , y=v2.y } }; 64 | public static implicit operator Vector2Int ( INT2 I2 ) => new Vector2Int{ x=I2.Value.x , y=I2.Value.y }; 65 | public static implicit operator INT2 ( (int x,int y) tuple ) => new INT2{ Value = new int2{ x=tuple.x , y=tuple.y } }; 66 | 67 | public static int2 operator + ( INT2 a , INT2 b ) => a.Value + b.Value; 68 | public static int2 operator - ( INT2 a , INT2 b ) => a.Value - b.Value; 69 | public static int2 operator * ( INT2 a , INT2 b ) => a.Value * b.Value; 70 | public static int2 operator / ( INT2 a , INT2 b ) => a.Value / b.Value; 71 | public static int2 operator * ( INT2 F2 , int f ) => F2.Value * f; 72 | 73 | override public string ToString () => this.Value.ToString(); 74 | } 75 | 76 | public struct INT 77 | { 78 | int Value; 79 | 80 | public static implicit operator int ( INT I ) => I.Value; 81 | public static implicit operator INT ( int i ) => new INT{ Value=i }; 82 | 83 | public static int operator + ( INT a , INT b ) => a.Value + b.Value; 84 | public static int operator - ( INT a , INT b ) => a.Value - b.Value; 85 | public static int operator * ( INT a , INT b ) => a.Value * b.Value; 86 | public static int operator / ( INT a , INT b ) => a.Value / b.Value; 87 | public static int operator * ( INT val , int f ) => val.Value * f; 88 | 89 | override public string ToString () => this.Value.ToString(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Runtime/INT.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c6ff666067360c459aa6704c14164b9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeGrid core assertions.cs: -------------------------------------------------------------------------------- 1 | /// homepage: https://github.com/andrew-raphael-lukasik/NativeGrid 2 | using UnityEngine; 3 | using Unity.Mathematics; 4 | using Unity.Collections; 5 | 6 | using Conditional = System.Diagnostics.ConditionalAttribute; 7 | 8 | namespace NativeGridNamespace 9 | { 10 | /// Non-generic, abstract parent class for NativeGrid. 11 | public abstract partial class NativeGrid 12 | { 13 | #region ASSERTIONS 14 | 15 | 16 | [Conditional("UNITY_ASSERTIONS")] 17 | static void ASSERT_TRUE ( in bool b , in FixedString128Bytes text ) 18 | { 19 | if( !b ) Debug.LogError(text); 20 | } 21 | 22 | 23 | [Conditional("UNITY_ASSERTIONS")] 24 | static void Assert_IndexTranslate ( RectInt r , int rx , int ry , int R_width ) 25 | { 26 | ASSERT_TRUE( R_width>0 , $"FAILED: R_width ({R_width}) > 0" ); 27 | ASSERT_TRUE( r.width<=R_width , $"FAILED: r.width ({r.width}) > ({R_width}) R_width" ); 28 | Assert_IndexTranslate( r , rx , ry ); 29 | } 30 | [Conditional("UNITY_ASSERTIONS")] 31 | static void Assert_IndexTranslate ( RectInt r , int rx , int ry ) 32 | { 33 | ASSERT_TRUE( rx>=0 , $"FAILED: rx ({rx}) >= 0" ); 34 | ASSERT_TRUE( ry>=0 , $"FAILED: ry ({ry}) >= 0" ); 35 | 36 | ASSERT_TRUE( r.width>0 , $"FAILED: r.width ({r.width}) > 0" ); 37 | ASSERT_TRUE( r.height>0 , $"FAILED: r.height ({r.height}) > 0" ); 38 | ASSERT_TRUE( r.x>=0 , $"FAILED: r.x ({r.x}) >= 0" ); 39 | ASSERT_TRUE( r.y>=0 , $"FAILED: r.y ({r.y}) >= 0" ); 40 | 41 | ASSERT_TRUE( rx>=0 && rx=0 && ry0 , $"FAILED: width ({width}) > 0" ); 49 | ASSERT_TRUE( i>=0 , $"FAILED: i ({i}) >= 0" ); 50 | } 51 | 52 | [Conditional("UNITY_ASSERTIONS")] 53 | static void Assert_CoordToIndex ( int x , int y , int width ) 54 | { 55 | ASSERT_TRUE( width>0 , $"FAILED: width ({width}) > 0" ); 56 | ASSERT_TRUE( x>=0 , $"FAILED: x ({x}) >= 0" ); 57 | ASSERT_TRUE( y>=0 , $"FAILED: y ({y}) >= 0" ); 58 | ASSERT_TRUE( x Assert_CoordToIndex( coord.x , coord.y , width ); 61 | 62 | 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Runtime/NativeGrid core assertions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c1e6571a0bf12534bb9a999184a077ef 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeGrid core functions.cs: -------------------------------------------------------------------------------- 1 | /// homepage: https://github.com/andrew-raphael-lukasik/NativeGrid 2 | using UnityEngine; 3 | using Unity.Mathematics; 4 | using Unity.Collections; 5 | 6 | namespace NativeGridNamespace 7 | { 8 | /// Non-generic, abstract parent class for NativeGrid. 9 | public abstract partial class NativeGrid 10 | { 11 | #region PUBLIC METHODS 12 | 13 | 14 | /// Converts int index to int2 coord 15 | public static int2 IndexToCoord ( INT i , INT width ) 16 | { 17 | Assert_IndexToCoord( i , width ); 18 | 19 | return new int2{ x=i%width , y=i/width }; 20 | } 21 | 22 | 23 | /// Converts coord to it's index equivalent 24 | public static int CoordToIndex ( INT x , INT y , INT width ) 25 | { 26 | Assert_CoordToIndex( x , y , width ); 27 | 28 | return y * width + x; 29 | } 30 | public static int CoordToIndex ( INT2 coord , INT width ) => CoordToIndex( coord.x , coord.y , width ); 31 | 32 | 33 | /// Translate regional coordinate to outer array index 34 | /// Outer RectInt 35 | /// Inner, smaller RectInt 36 | /// Inner x coordinate 37 | /// Inner y coordinate 38 | /// Outer RectInt.width 39 | public static int IndexTranslate ( RectInt r , INT rx , INT ry , INT R_width ) 40 | { 41 | Assert_IndexTranslate( r , rx , ry , R_width ); 42 | 43 | return CoordToIndex( r.x+rx , r.y+ry , R_width ); 44 | } 45 | 46 | /// Translate regional coordinate to outer array index 47 | /// Outer RectInt 48 | /// Inner, smaller RectInt 49 | /// Inner x coordinate 50 | /// Inner y coordinate 51 | /// Outer RectInt.width 52 | public static int2 IndexTranslate ( RectInt r , INT2 rxy ) 53 | { 54 | Assert_IndexTranslate( r , rxy.x , rxy.y ); 55 | 56 | return new int2{ x=r.x , y=r.y } + (int2)rxy; 57 | } 58 | 59 | /// Translate regional index to outer one 60 | /// Outer RectInt 61 | /// Inner, smaller RectInt 62 | /// Index in inner rect 63 | /// Outer RectInt.width 64 | public static int IndexTranslate ( RectInt r , INT ri , INT R_width ) 65 | { 66 | int2 ri2d = IndexToCoord( ri , r.width ); 67 | return IndexTranslate( r , ri2d.x , ri2d.y , R_width ); 68 | } 69 | 70 | 71 | /// Determines whether int2 coord is inside array bounds 72 | public static bool IsCoordValid ( INT x , INT y , INT w , INT h ) => x>=0 && x=0 && y IsCoordValid( coord.x , coord.y , w , h ); 74 | 75 | 76 | /// Determines whether index is inside array bounds 77 | public static bool IsIndexValid ( INT i , INT len ) => 0>=0 && i Point from a coordinate 81 | public static float2 CoordToPoint ( INT x , INT y , FLOAT stepX , FLOAT stepY ) => new float2{ x=(float)x*stepX , y=(float)y*stepY }; 82 | public static float2 CoordToPoint ( INT2 coord , FLOAT stepX , FLOAT stepY ) => CoordToPoint( coord.x , coord.y , stepX , stepY ); 83 | public static float2 CoordToPoint ( INT2 coord , FLOAT2 step ) => CoordToPoint( coord.x , coord.y , step.x , step.y ); 84 | 85 | 86 | /// Value at point 87 | public static T PointToValue ( FLOAT2 point , FLOAT2 worldSize , NativeArray array , INT width , INT height ) where T : unmanaged 88 | { 89 | return array[ PointToIndex( point , worldSize , width , height ) ]; 90 | } 91 | 92 | 93 | /// Index from point 94 | public static int PointToIndex ( FLOAT2 point , FLOAT2 worldSize , INT width , INT height ) 95 | { 96 | int2 coord = PointToCoord( point , worldSize , width , height ); 97 | return CoordToIndex( coord , width ); 98 | } 99 | public static int2 PointToCoord ( FLOAT2 point , FLOAT2 worldSize , INT width , INT height ) 100 | { 101 | GetPositionInsideCell( point , new int2{ x=width , y=height } , worldSize , out int2 lo , out int2 hi , out float2 f ); 102 | int2 index = new int2{ 103 | x = AboutEqual( f.x , 1f ) ? hi.x : lo.x , 104 | y = AboutEqual( f.y , 1f ) ? hi.y : lo.y 105 | }; 106 | return math.clamp( index , 0 , new int2{ x=width-1 , y=height-1 } ); 107 | } 108 | 109 | 110 | /// 111 | public static void GetPositionInsideCell 112 | ( 113 | FLOAT point , INT numCells , FLOAT worldSize , 114 | out int lowerCell , out int upperCell , out float normalizedPositionBetweenThoseTwoPoints 115 | ) 116 | { 117 | float cellSize = worldSize / (float)numCells; 118 | float cellFraction = point / cellSize; 119 | lowerCell = cellSize<0f ? (int)math.ceil( cellFraction ) : (int)math.floor( cellFraction ); 120 | upperCell = lowerCell<0 ? lowerCell-1 : lowerCell+1; 121 | normalizedPositionBetweenThoseTwoPoints = ( point - (float)lowerCell*cellSize ) / cellSize; 122 | } 123 | public static void GetPositionInsideCell 124 | ( 125 | FLOAT2 point , INT2 numCells , FLOAT2 worldSize , 126 | out int2 lowerCell , out int2 upperCell , out float2 normalizedPositionBetweenThoseTwoPoints 127 | ) 128 | { 129 | GetPositionInsideCell( point.x , numCells.x , worldSize.x , out int xlo , out int xhi , out float xf ); 130 | GetPositionInsideCell( point.y , numCells.y , worldSize.y , out int ylo , out int yhi , out float yf ); 131 | lowerCell = new int2{ x=xlo , y=ylo }; 132 | upperCell = new int2{ x=xhi , y=yhi }; 133 | normalizedPositionBetweenThoseTwoPoints = new float2{ x=xf , y=yf }; 134 | } 135 | 136 | 137 | /// 138 | /// Result other than 0 means that this point lies directly on a border between two neighbouring cells. 139 | /// And thus it must be determined to which of the cells this point will fall into. This method provides an index offset to solve just that. 140 | /// 141 | public static int IsPointBetweenCells ( FLOAT point , INT width , FLOAT worldSize ) 142 | { 143 | float cellSize = worldSize / (float)width; 144 | float cellFraction = (point%cellSize) / cellSize; 145 | bool isMiddlePoint = AboutEqual( cellFraction , 1f ); 146 | return isMiddlePoint ? (point<0f?-1:1) : 0; 147 | } 148 | 149 | public static bool AboutEqual ( double x , double y ) => math.abs(x-y) <= math.max( math.abs(x) , math.abs(y) ) * 1E-15; 150 | 151 | 152 | public static int ClampIndex ( INT i , INT length ) => math.clamp( i , 0 , length-1 ); 153 | public static int2 ClampCoord ( INT x , INT y , INT width , INT height ) => math.clamp( new int2{ x=x , y=y } , int2.zero , new int2{ x=width-1 , y=height-1 } ); 154 | public static int2 ClampCoord ( INT2 coord , INT width , INT height ) => ClampCoord( coord.x , coord.y , width , height ); 155 | 156 | 157 | /// System.Math.Round( value, System.MidpointRounding.AwayFromZero ) equivalent 158 | public static int MidpointRoundingAwayFromZero ( FLOAT value ) => (int)( value + (value<0f ? -0.5f : 0.5f) ); 159 | /// System.Math.Round( value, System.MidpointRounding.AwayFromZero ) equivalent 160 | public static float MidpointRoundingAwayFromZero ( FLOAT value , FLOAT step ) => (float)MidpointRoundingAwayFromZero(value/step) * step; 161 | /// System.Math.Round( value , System.MidpointRounding.AwayFromZero ) equivalent 162 | public static int2 MidpointRoundingAwayFromZero ( FLOAT2 value ) => new int2{ x=MidpointRoundingAwayFromZero(value.x) , y=MidpointRoundingAwayFromZero(value.y) }; 163 | /// System.Math.Round( value, System.MidpointRounding.AwayFromZero ) equivalent 164 | public static float2 MidpointRoundingAwayFromZero ( FLOAT2 value , FLOAT2 step ) => (float2)MidpointRoundingAwayFromZero(value/step) * (float2)step; 165 | 166 | 167 | #endregion 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Runtime/NativeGrid core functions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 78e501616c3b254439e37e390cd6da41 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeGrid core jobs.cs: -------------------------------------------------------------------------------- 1 | /// homepage: https://github.com/andrew-raphael-lukasik/NativeGrid 2 | using UnityEngine; 3 | using Unity.Mathematics; 4 | using Unity.Collections; 5 | using Unity.Collections.LowLevel.Unsafe; 6 | using Unity.Jobs; 7 | 8 | using BurstCompile = Unity.Burst.BurstCompileAttribute; 9 | 10 | namespace NativeGridNamespace 11 | { 12 | /// Non-generic, abstract parent class for NativeGrid. 13 | public abstract partial class NativeGrid 14 | { 15 | #region JOBS 16 | 17 | 18 | [BurstCompile] 19 | public static JobHandle Copy 20 | ( 21 | NativeGrid source , 22 | RectInt region , 23 | out NativeGrid copy , 24 | JobHandle dependency = default(JobHandle) 25 | ) where T : unmanaged 26 | { 27 | copy = new NativeGrid( region.width , region.height , Allocator.TempJob ); 28 | var job = new CopyRegionJob( 29 | src: source.Array , 30 | dst: copy.Array , 31 | src_region: region , 32 | src_width: source.Width 33 | ); 34 | return job.Schedule( 35 | region.width*region.height , 1024 , 36 | JobHandle.CombineDependencies( source.Dependency , dependency ) 37 | ); 38 | } 39 | 40 | [BurstCompile] 41 | public unsafe struct CopyJob : IJob where T : unmanaged 42 | { 43 | [ReadOnly] NativeArray src; 44 | void* dst; 45 | public CopyJob ( NativeArray src , void* dst ) 46 | { 47 | this.src = src; 48 | this.dst = dst; 49 | } 50 | unsafe void IJob.Execute () 51 | { 52 | //ASSERTION: sizeof(SRC)==sizeof(DST) 53 | UnsafeUtility.MemCpy( 54 | dst , 55 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks( src ) , 56 | src.Length * (long)UnsafeUtility.SizeOf() 57 | ); 58 | } 59 | } 60 | 61 | [BurstCompile] 62 | public struct CopyJob : IJob 63 | where SRC : unmanaged 64 | where DST : unmanaged 65 | { 66 | [ReadOnly] NativeArray src; 67 | [WriteOnly] NativeArray dst; 68 | public CopyJob ( NativeArray src , NativeArray dst ) 69 | { 70 | this.src = src; 71 | this.dst = dst; 72 | } 73 | unsafe void IJob.Execute () 74 | { 75 | //ASSERTION: sizeof(SRC)==sizeof(DST) 76 | UnsafeUtility.MemCpy( 77 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks( dst ) , 78 | NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks( src ) , 79 | dst.Length * (long)UnsafeUtility.SizeOf() 80 | ); 81 | } 82 | } 83 | 84 | [BurstCompile] 85 | public struct CopyRegionJob : IJobParallelFor where T : unmanaged 86 | { 87 | [ReadOnly] NativeArray src; 88 | [WriteOnly] NativeArray dst; 89 | readonly RectInt src_region; 90 | readonly int src_width; 91 | public CopyRegionJob ( NativeArray src , NativeArray dst , RectInt src_region , int src_width ) 92 | { 93 | this.src = src; 94 | this.dst = dst; 95 | this.src_region = src_region; 96 | this.src_width = src_width; 97 | } 98 | void IJobParallelFor.Execute ( int regionIndex ) => dst[regionIndex] = src[IndexTranslate(src_region,regionIndex,src_width)]; 99 | } 100 | 101 | [BurstCompile] 102 | public struct FillJob : IJobParallelFor where T : unmanaged 103 | { 104 | [WriteOnly] NativeArray array; 105 | readonly T value; 106 | public FillJob ( NativeArray array , T value ) 107 | { 108 | this.array = array; 109 | this.value = value; 110 | } 111 | void IJobParallelFor.Execute ( int i ) => array[i] = value; 112 | } 113 | 114 | [BurstCompile] 115 | public struct FillRegionJob : IJobParallelFor where T : unmanaged 116 | { 117 | [WriteOnly][NativeDisableParallelForRestriction] 118 | NativeArray array; 119 | readonly int array_width; 120 | readonly RectInt region; 121 | readonly T value; 122 | public FillRegionJob ( NativeArray array , int array_width , RectInt region , T value ) 123 | { 124 | this.array = array; 125 | this.array_width = array_width; 126 | this.region = region; 127 | this.value = value; 128 | } 129 | void IJobParallelFor.Execute ( int regionIndex ) => array[IndexTranslate( region , regionIndex , array_width )] = value; 130 | } 131 | 132 | [BurstCompile] 133 | public struct FillLineJob : IJob 134 | where T : unmanaged 135 | { 136 | public NativeArray Array; 137 | public int ArrayWidth; 138 | public int2 Start, End; 139 | public T Fill; 140 | public FillLineJob ( NativeArray array , int arrayWidth , int2 start , int2 end , T fill ) 141 | { 142 | this.Array = array; 143 | this.ArrayWidth = arrayWidth; 144 | this.Start = start; 145 | this.End = end; 146 | this.Fill = fill; 147 | } 148 | void IJob.Execute () 149 | { 150 | var indices = new NativeList( (int)( math.distance(Start,End) * math.SQRT2 ) , Allocator.Temp ); 151 | TraceLine( A:Start , B:End , results:indices , min:int2.zero , max:new int2{ x=this.ArrayWidth-1 , y=(this.Array.Length/this.ArrayWidth)-1 } ); 152 | foreach( int2 coord in indices ) 153 | { 154 | int i = CoordToIndex( coord:coord , width:ArrayWidth ); 155 | Array[i] = Fill; 156 | } 157 | } 158 | } 159 | 160 | [BurstCompile] 161 | public struct FillBordersJob : IJob where T : unmanaged 162 | { 163 | [WriteOnly][NativeDisableParallelForRestriction] 164 | NativeArray array; 165 | readonly int width; 166 | readonly int height; 167 | readonly T fill; 168 | public FillBordersJob ( NativeArray array , int width , int height , T fill ) 169 | { 170 | this.array = array; 171 | this.width = width; 172 | this.height = height; 173 | this.fill = fill; 174 | } 175 | void IJob.Execute () 176 | { 177 | // fill horizontal border lines: 178 | int yMax = height-1; 179 | for( int x=0 ; x 11 | /// NativeGrid is grid data layout class. Parent NativeGrid class is for static functions and nested types. 12 | /// 13 | public partial class NativeGrid 14 | : NativeGrid, System.IDisposable 15 | where T : unmanaged 16 | { 17 | #region index transformation methods 18 | 19 | 20 | /// Converts coord to it's index equivalent 21 | public int CoordToIndex ( int x , int y ) 22 | { 23 | #if DEBUG 24 | if( IsCoordValid(x,y)==false ) { Debug.LogWarningFormat( "[{0},{1}] index is invalid for this grid" , x , y ); } 25 | #endif 26 | return y * Width + x; 27 | } 28 | public int CoordToIndex ( INT2 coord ) => CoordToIndex( coord.x , coord.y ); 29 | public int CoordToIndex ( int2 coord ) => CoordToIndex( coord.x , coord.y ); 30 | 31 | 32 | /// Converts index to coord 33 | public int2 IndexToCoord ( int i ) => new int2 { x=i%Width , y=i/Width }; 34 | 35 | 36 | /// Transforms local position to cell index 37 | public bool LocalPointToCoord ( FLOAT3 localPoint , float spacing , out int2 result ) 38 | { 39 | int x = (int)( ( localPoint.x+(float)Width*0.5f*spacing )/spacing ); 40 | int z = (int)( ( localPoint.z+(float)Height*0.5f*spacing )/spacing ); 41 | if( IsCoordValid(x,z) ) 42 | { 43 | result = new int2{ x=x , y=z }; 44 | return true; 45 | } else { 46 | result = new int2{ x=-1 , y=-1 }; 47 | return false; 48 | } 49 | } 50 | 51 | 52 | /// Determines whether coord is inside array bounds 53 | public bool IsCoordValid ( int x , int y ) => IsCoordValid( x , y , Width , Height ); 54 | public bool IsCoordValid ( INT2 coord ) => IsCoordValid( coord.x , coord.y ); 55 | 56 | 57 | /// Determines whether index is inside array bounds 58 | public bool IsIndexValid ( int i ) => IsIndexValid( i , this.Length ); 59 | 60 | 61 | /// Transforms index to local position. 62 | public float3 IndexToLocalPoint ( int x , int y , float spacing ) 63 | { 64 | return new float3( 65 | ( (float)x*spacing )+( -Width*spacing*0.5f )+( spacing*0.5f ) , 66 | 0f , 67 | ( (float)y*spacing )+( -Height*spacing*0.5f )+( spacing*0.5f ) 68 | ); 69 | } 70 | public float3 IndexToLocalPoint ( int index , float spacing ) 71 | { 72 | int2 coord = IndexToCoord( index ); 73 | return new float3( 74 | ( coord.x*spacing )+( -Width*spacing*0.5f )+( spacing*0.5f ) , 75 | 0f , 76 | ( coord.y*spacing )+( -Height*spacing*0.5f )+( spacing*0.5f ) 77 | ); 78 | } 79 | 80 | 81 | /// Rect center position 82 | public float3 IndexToLocalPoint ( int x , int y , int w , int h , float spacing ) 83 | { 84 | float3 cornerA = IndexToLocalPoint( x , y , spacing ); 85 | float3 cornerB = IndexToLocalPoint( x+w-1 , y+h-1 , spacing ); 86 | return cornerA+(cornerB-cornerA)*0.5f; 87 | } 88 | 89 | 90 | #endregion 91 | #region ref return methods 92 | 93 | 94 | /// Get ref to array element 95 | /// Make sure index is in bound 96 | public unsafe ref T AsRef ( int x , int y ) => ref AsRef( CoordToIndex( x , y , this.Width ) ); 97 | public unsafe ref T AsRef ( INT2 coord ) => ref AsRef( CoordToIndex( coord , this.Width ) ); 98 | public unsafe ref T AsRef ( int i ) => ref ( (T*)_array.GetUnsafePtr() )[i]; 99 | 100 | 101 | #endregion 102 | #region marching squares methods 103 | 104 | 105 | /// Gets the surrounding field values 106 | /// 107 | /// 8-bit clockwise-enumerated bit values 108 | /// 7 0 1 [x-1,y+1] [x,y+1] [x+1,y+1] 109 | /// 6 ^ 2 == [x-1,y] [x,y] [x+1,y] 110 | /// 5 4 3 [x-1,y-1] [x,y-1] [x+1,y-1] 111 | /// for example: 1<<0 is top, 1<<1 is top-right, 1<<2 is right, 1<<6|1<<4|1<<2 is both left,down and right 112 | /// 113 | public byte GetMarchingSquares ( INT2 coord , System.Predicate predicate ) 114 | { 115 | int x = coord.x; 116 | int y = coord.y; 117 | 118 | const byte zero = 0b_0000_0000; 119 | byte result = zero; 120 | 121 | //out of bounds test: 122 | bool xPlus = x+1 < Width; 123 | bool yPlus = y+1 < Height; 124 | bool xMinus = x-1 >= 0; 125 | bool yMinus = y-1 >= 0; 126 | 127 | //top, down: 128 | result |= yPlus && predicate(this[x,y+1]) ? (byte)0b_0000_0001 : zero; 129 | result |= yMinus && predicate(this[x,y-1]) ? (byte)0b_0001_0000 : zero; 130 | 131 | //right side: 132 | result |= xPlus && yPlus && predicate(this[x+1,y+1]) ? (byte)0b_0000_0010 : zero; 133 | result |= xPlus && predicate(this[x+1,y]) ? (byte)0b_0000_0100 : zero; 134 | result |= xPlus && yMinus && predicate(this[x+1,y-1]) ? (byte)0b_0000_1000 : zero; 135 | 136 | //left side: 137 | result |= xMinus && yPlus && predicate(this[x-1,y+1]) ? (byte)0b_0010_0000 : zero; 138 | result |= xMinus && predicate(this[x-1,y]) ? (byte)0b_0000_0100 : zero; 139 | result |= xMinus && yMinus && predicate(this[x-1,y-1]) ? (byte)0b_1000_0000 : zero; 140 | 141 | return result; 142 | } 143 | 144 | 145 | #endregion 146 | #region fill methods 147 | 148 | 149 | /// Fill 150 | public JobHandle Fill ( T value , JobHandle dependency = default(JobHandle) ) 151 | { 152 | var job = new FillJob( 153 | array: _array , 154 | value: value 155 | ); 156 | return Dependency = job.Schedule( 157 | _array.Length , 1024 , 158 | JobHandle.CombineDependencies( dependency , Dependency ) 159 | ); 160 | } 161 | 162 | /// Fill rectangle 163 | public JobHandle Fill ( RectInt region , T value , JobHandle dependency = default(JobHandle) ) 164 | { 165 | var job = new FillRegionJob( 166 | region: region , 167 | array: this._array , 168 | value: value , 169 | array_width: this.Width 170 | ); 171 | return Dependency = job.Schedule( 172 | region.width*region.height , 1024 , 173 | JobHandle.CombineDependencies( dependency , Dependency ) 174 | ); 175 | } 176 | 177 | /// Fills a line trace between 2 coords. 178 | public JobHandle FillLine ( INT2 a , INT2 b , T fill , JobHandle dependency = default(JobHandle) ) 179 | { 180 | return new FillLineJob( 181 | array: this._array , 182 | arrayWidth: this.Width , 183 | start: a , 184 | end: b , 185 | fill: fill 186 | ).Schedule( JobHandle.CombineDependencies( this.Dependency, dependency ) ); 187 | } 188 | 189 | /// Fills grid border cells. 190 | public JobHandle FillBorders ( T fill , JobHandle dependency = default(JobHandle) ) 191 | { 192 | var job = new FillBordersJob( 193 | array: this._array , 194 | width: this.Width , 195 | height: this.Height , 196 | fill: fill 197 | ); 198 | return Dependency = job.Schedule( JobHandle.CombineDependencies(dependency,Dependency) ); 199 | } 200 | 201 | 202 | #endregion 203 | #region copy methods 204 | 205 | 206 | public JobHandle Copy ( RectInt region , out NativeGrid copy ) => Copy( this , region , out copy ); 207 | 208 | 209 | #endregion 210 | #region deallocation methods 211 | 212 | 213 | public void Dispose () 214 | { 215 | if( _array.IsCreated ) 216 | { 217 | Dependency.Complete(); 218 | _array.Dispose(); 219 | } 220 | } 221 | 222 | 223 | #endregion 224 | } 225 | } -------------------------------------------------------------------------------- /Runtime/NativeGrid core methods.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 73f6fc6af559aeb498b395300a33f252 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeGrid enumerators.cs: -------------------------------------------------------------------------------- 1 | /// homepage: https://github.com/andrew-raphael-lukasik/NativeGrid 2 | using Unity.Mathematics; 3 | 4 | namespace NativeGridNamespace 5 | { 6 | /// Non-generic, abstract parent class for NativeGrid. 7 | public abstract partial class NativeGrid 8 | { 9 | 10 | public interface INativeEnumerator where T : unmanaged 11 | { 12 | T Current { get; } 13 | bool MoveNext(); 14 | bool MoveNext( out T current ); 15 | void Reset(); 16 | } 17 | 18 | public struct NeighbourEnumerator : INativeEnumerator 19 | { 20 | readonly int2 _coord; 21 | readonly int _xMax, _yMax; 22 | int2 _current; 23 | byte _tick; 24 | 25 | public NeighbourEnumerator ( int2 coord , int gridWidth , int gridHeight ) 26 | { 27 | this._coord = coord; 28 | this._xMax = gridWidth-1; 29 | this._yMax = gridHeight-1; 30 | this._current = new int2(-1,-1); 31 | this._tick = 0; 32 | } 33 | 34 | public int2 Current => _current; 35 | 36 | public bool MoveNext () 37 | { 38 | if( _tick>7 ) return false; 39 | int2 candidate = _current; 40 | switch( _tick++ ) 41 | { 42 | case 0: candidate = _coord + new int2{ x=-1 , y=-1 }; break;// y-1 43 | case 1: candidate = _coord + new int2{ y=-1 }; break; 44 | case 2: candidate = _coord + new int2{ x=+1 , y=-1 }; break; 45 | case 3: candidate = _coord + new int2{ x=-1 }; break;// y 46 | case 4: candidate = _coord + new int2{ x=+1 }; break; 47 | case 5: candidate = _coord + new int2{ x=-1 , y=+1 }; break;// y+1 48 | case 6: candidate = _coord + new int2{ y=+1 }; break; 49 | case 7: candidate = _coord + new int2{ x=+1 , y=+1 }; break; 50 | default: return false; 51 | } 52 | bool4 isOutOfBounds = new bool4{ x=candidate.x<0 , y=candidate.y<0 , z=candidate.x>_xMax , w=candidate.y>_yMax }; 53 | if( math.any(isOutOfBounds) ) return MoveNext(); 54 | _current = candidate; 55 | return true; 56 | } 57 | 58 | public bool MoveNext ( out int2 neighbourCoord ) 59 | { 60 | bool success = MoveNext(); 61 | neighbourCoord = _current; 62 | return success; 63 | } 64 | 65 | public void Reset () 66 | { 67 | _current = _coord; 68 | _tick = 0; 69 | } 70 | 71 | } 72 | 73 | public struct LineTraceEnumerator : INativeEnumerator 74 | { 75 | readonly int2 _src, _dst; 76 | int2 _current; 77 | 78 | public LineTraceEnumerator ( INT2 src , INT2 dst ) 79 | { 80 | this._src = src; 81 | this._dst = dst; 82 | this._current = src; 83 | } 84 | // public LineTraceEnumerator ( INT2 src , INT2 dst , INT2 min , INT2 max ) 85 | // : this( src:src , dst:dst ) 86 | // { 87 | // if( ) 88 | // { 89 | 90 | // } 91 | // } 92 | 93 | public int2 Current => _current; 94 | 95 | public bool MoveNext () 96 | { 97 | int d, dx, dy, ai, bi, xi, yi; 98 | 99 | if( _current.x< _dst.x ) 100 | { 101 | xi = 1; 102 | dx = _dst.x - _current.x; 103 | } 104 | else 105 | { 106 | xi = -1; 107 | dx = _current.x - _dst.x; 108 | } 109 | 110 | if( _current.y< _dst.y ) 111 | { 112 | yi = 1; 113 | dy = _dst.y - _current.y; 114 | } 115 | else 116 | { 117 | yi = -1; 118 | dy = _current.y - _dst.y; 119 | } 120 | 121 | if( dx>dy ) 122 | { 123 | ai = (dy - dx) * 2; 124 | bi = dy * 2; 125 | d = bi - dx; 126 | 127 | while( _current.x!=_dst.x ) 128 | { 129 | if( d>=0 ) 130 | { 131 | _current.x += xi; 132 | _current.y += yi; 133 | d += ai; 134 | } 135 | else 136 | { 137 | d += bi; 138 | _current.x += xi; 139 | } 140 | return true; 141 | } 142 | } 143 | else 144 | { 145 | ai = ( dx - dy ) * 2; 146 | bi = dx * 2; 147 | d = bi - dy; 148 | 149 | while( _current.y!=_dst.y ) 150 | { 151 | if( d>=0 ) 152 | { 153 | _current.x += xi; 154 | _current.y += yi; 155 | d += ai; 156 | } 157 | else 158 | { 159 | d += bi; 160 | _current.y += yi; 161 | } 162 | return true; 163 | } 164 | } 165 | return false; 166 | } 167 | 168 | public bool MoveNext ( out int2 next ) 169 | { 170 | bool success = MoveNext(); 171 | next = _current; 172 | return success; 173 | } 174 | 175 | public void Reset () 176 | { 177 | _current = _src; 178 | } 179 | 180 | } 181 | 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /Runtime/NativeGrid enumerators.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7272ac6b4d670b49832a1a57b79fbdb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeGrid pathfinding.cs: -------------------------------------------------------------------------------- 1 | /// homepage: https://github.com/andrew-raphael-lukasik/NativeGrid 2 | #if UNITY_ASSERTIONS 3 | using UnityEngine.Assertions; 4 | #endif 5 | using Unity.Mathematics; 6 | using Unity.Collections; 7 | using Unity.Jobs; 8 | using Unity.Profiling; 9 | 10 | using Debug = UnityEngine.Debug; 11 | using BurstCompile = Unity.Burst.BurstCompileAttribute; 12 | 13 | namespace NativeGridNamespace 14 | { 15 | /// Non-generic, abstract parent class for NativeGrid. 16 | public abstract partial class NativeGrid 17 | { 18 | #region PUBLIC METHODS 19 | 20 | 21 | /// Traces path using A* algorithm 22 | /// Format weights to 0.0 to 1.0 range. Heuristic can cease to work otherwise. 23 | [BurstCompile] 24 | public struct AStarJob : IJob, System.IDisposable 25 | { 26 | 27 | /// Job results goes here. List of indices to form a path. 28 | public NativeList Results; 29 | 30 | public readonly int2 Start; 31 | public readonly int2 Destination; 32 | [ReadOnly] public readonly NativeArray MoveCost; 33 | public readonly int MoveCostWidth; 34 | public readonly float HMultiplier; 35 | public readonly float MoveCostSensitivity; 36 | public readonly int StepBudget; 37 | public readonly bool ResultsStartAtIndexZero; 38 | 39 | public NativeArray G; 40 | public NativeArray F; 41 | public NativeArray Solution; 42 | public NativeMinHeap Frontier; 43 | public NativeHashSet Visited; 44 | 45 | ProfilerMarker _PM_Initialization, _PM_Search, _PM_Neighbours, _PM_FrontierPush, _PM_FrontierPop, _PM_UpdateFG, _PM_Trace; 46 | 47 | /// Traces path using some kind of A* algorithm 48 | /// Start index 2d 49 | /// Destination index 2d 50 | /// Move cost data 2d array in 0.0-1.0 range format. Cells with value >= 1.0 are considered impassable. 51 | /// 2d array's width 52 | /// Resulting path goes here 53 | /// Heuristic factor multiplier. Increasing this over 1.0 makes lines more straight and decrease cpu usage. 54 | /// Makes algorith evade cells with move cost > 0 more. 55 | /// CPU time budget you give this job. Expressind in number steps search algorihm is allowed to take. 56 | /// When this is true then path indices in will be ordered starting from index 0 and eding at index length-1. False reverses this order. 57 | public AStarJob 58 | ( 59 | INT2 start , 60 | INT2 destination , 61 | NativeArray moveCost , 62 | int moveCostWidth , 63 | NativeList results , 64 | float hMultiplier = 1 , 65 | float moveCostSensitivity = 1 , 66 | int stepBudget = int.MaxValue , 67 | bool resultsStartAtIndexZero = true 68 | ) 69 | { 70 | this.Start = start; 71 | this.Destination = destination; 72 | this.MoveCost = moveCost; 73 | this.MoveCostWidth = moveCostWidth; 74 | this.Results = results; 75 | this.HMultiplier = hMultiplier; 76 | this.MoveCostSensitivity = moveCostSensitivity; 77 | this.StepBudget = stepBudget; 78 | this.ResultsStartAtIndexZero = resultsStartAtIndexZero; 79 | 80 | int length = moveCost.Length; 81 | int startIndex = CoordToIndex( start , moveCostWidth ); 82 | this.G = new NativeArray( length , Allocator.TempJob , NativeArrayOptions.UninitializedMemory ); 83 | this.F = new NativeArray( length , Allocator.TempJob , NativeArrayOptions.UninitializedMemory ); 84 | this.Solution = new NativeArray( length , Allocator.TempJob ); 85 | this.Frontier = new NativeMinHeap( length , Allocator.TempJob , new Comparer( moveCostWidth ) , this.F ); 86 | this.Visited = new NativeHashSet( length , Allocator.TempJob ); 87 | 88 | this._PM_Initialization = new ProfilerMarker("initialization"); 89 | this._PM_Search = new ProfilerMarker("search"); 90 | this._PM_Neighbours = new ProfilerMarker("scan neighbors"); 91 | this._PM_FrontierPush = new ProfilerMarker("frontier.push"); 92 | this._PM_FrontierPop = new ProfilerMarker("frontier.pop"); 93 | this._PM_UpdateFG = new ProfilerMarker("update f & g"); 94 | this._PM_Trace = new ProfilerMarker("trace path"); 95 | } 96 | public void Execute () 97 | { 98 | _PM_Initialization.Begin(); 99 | int startIndex = CoordToIndex( Start , MoveCostWidth ); 100 | int destIndex = CoordToIndex( Destination , MoveCostWidth ); 101 | { 102 | // early test for unsolvable input: 103 | if( (MoveCost[startIndex]/255f)>=1 ) return; 104 | if( (MoveCost[destIndex]/255f)>=1 ) return; 105 | } 106 | { 107 | // initialize GData array: 108 | for( int i=G.Length-1 ; i!=-1 ; i-- ) 109 | G[i] = (half) half.MaxValue; 110 | G[startIndex] = half.zero; 111 | } 112 | { 113 | // initialize FData array: 114 | for( int i=F.Length-1 ; i!=-1 ; i-- ) 115 | F[i] = (half) half.MaxValue; 116 | F[startIndex] = half.zero; 117 | } 118 | Solution[startIndex] = Start; 119 | Frontier.Push( Start ); 120 | Visited.Add( Start ); 121 | _PM_Initialization.End(); 122 | 123 | // solve 124 | _PM_Search.Begin(); 125 | int moveCostHeight = MoveCost.Length / MoveCostWidth; 126 | int2 currentCoord = -1; 127 | int numSearchSteps = 0; 128 | bool destinationReached = false; 129 | while( 130 | Frontier.Length!=0 131 | && !( destinationReached = math.all(currentCoord==Destination) ) 132 | && numSearchSteps++no path found."); 199 | 200 | Results.Clear();// make sure to communite there is no path 201 | } 202 | _PM_Trace.End(); 203 | } 204 | public void Dispose () 205 | { 206 | this.G.Dispose(); 207 | this.F.Dispose(); 208 | this.Solution.Dispose(); 209 | this.Frontier.Dispose(); 210 | this.Visited.Dispose(); 211 | } 212 | public struct Comparer : INativeMinHeapComparer 213 | { 214 | public int Width; 215 | public Comparer ( int width ) => this.Width = width; 216 | public int Compare( int2 lhs , int2 rhs , NativeSlice comparables ) 217 | { 218 | float lhsValue = comparables[ CoordToIndex(lhs,Width) ]; 219 | float rhsValue = comparables[ CoordToIndex(rhs,Width) ]; 220 | return lhsValue.CompareTo(rhsValue); 221 | } 222 | } 223 | } 224 | 225 | public static float EuclideanHeuristic ( INT2 a , INT2 b ) => math.length( a-b ); 226 | public static float EuclideanHeuristicNormalized ( INT2 a , INT2 b , float maxLength ) => math.length( a-b ) / maxLength; 227 | public static float EuclideanHeuristicMaxLength ( INT arrayLength , INT arrayWidth ) => EuclideanHeuristic( int2.zero , new int2{ x=arrayWidth-1 , y=arrayLength/arrayWidth-1 } ); 228 | 229 | 230 | /// Finds sequence of indices (a path) for given AStar solution 231 | /// Was destination reached 232 | public static bool BacktrackToPath 233 | ( 234 | NativeArray solution , 235 | INT width , 236 | INT2 destination , 237 | NativeList results , 238 | bool resultsStartAtIndexZero 239 | ) 240 | { 241 | results.Clear(); 242 | if( results.Capacity( results.AsArray() ); 259 | 260 | return wasDestinationReached; 261 | } 262 | /// Finds sequence of indices (a path) for given AStar solution 263 | /// Uses segmented array for output 264 | /// Was destination reached 265 | public static bool BacktrackToPath 266 | ( 267 | NativeArray solvedGrid , 268 | INT solvedGridWidth , 269 | INT2 destination , 270 | NativeArray segmentedIndices , // array segmented to store multiple paths 271 | INT segmentStart , // position for first path coord 272 | INT segmentEnd , // position for last path coord 273 | out int pathLength 274 | ) 275 | { 276 | #if UNITY_ASSERTIONS 277 | ASSERT_TRUE( destination.x>=0 && destination.y>=0 , $"destination: {destination} >= 0" ); 278 | ASSERT_TRUE( destination.xsegmentEnd ) 299 | { 300 | // throw new System.Exception 301 | Debug.LogError($"segmentedArrayIndex {segmentedArrayIndex} is outside it's range of {{{segmentStart}...{segmentEnd}}}"); 302 | } 303 | #endif 304 | 305 | segmentedIndices[segmentedArrayIndex] = posCoord; 306 | posCoord = solvedGrid[posIndex]; 307 | posIndex = CoordToIndex( posCoord , solvedGridWidth ); 308 | 309 | #if UNITY_ASSERTIONS 310 | localAssertions(); 311 | #endif 312 | 313 | step++; 314 | } 315 | pathLength = step; 316 | bool wasDestinationReached = math.all( posCoord==solvedGrid[posIndex] ); 317 | 318 | #if UNITY_ASSERTIONS 319 | for( int n=0 ; nsegmentEnd ) 338 | { 339 | // throw new System.Exception 340 | Debug.LogError($"last {last} > {segmentEnd} segmentEnd"); 341 | } 342 | #endif 343 | 344 | ReverseArraySegment( segmentedIndices , first , last ); 345 | } 346 | 347 | #if UNITY_ASSERTIONS 348 | void localAssertions () 349 | { 350 | FixedString128Bytes debugInfo = $"posCoord: {posCoord}, posIndex:{posIndex}, solution.Length:{solvedGrid.Length}, solutionWidth:{solvedGridWidth} squared: {solvedGridWidth}"; 351 | ASSERT_TRUE( posIndex>=0 , debugInfo ); 352 | ASSERT_TRUE( posIndex ( NativeArray array ) where T : unmanaged 360 | { 361 | int length = array.Length; 362 | int lengthHalf = length / 2; 363 | int last = length-1; 364 | for( int i=0 ; i ( NativeArray array , INT first , INT last ) where T : unmanaged 373 | { 374 | int length = last - first; 375 | int lengthHalf = length / 2; 376 | for( int step=0 ; step Non-generic, abstract parent class for NativeGrid. 8 | public abstract partial class NativeGrid 9 | { 10 | #region PUBLIC METHODS 11 | 12 | 13 | /// Bresenham's line drawing algorithm (https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm). 14 | public static void TraceLine ( INT2 A , INT2 B , NativeList results ) 15 | { 16 | results.Clear(); 17 | { 18 | int2 dir = math.abs( A - B ); 19 | int capacity = math.max( dir.x , dir.y ); 20 | if( results.Capacitydy ) 54 | { 55 | ai = (dy - dx) * 2; 56 | bi = dy * 2; 57 | d = bi - dx; 58 | 59 | while( coord.x!=B.x ) 60 | { 61 | if( d>=0 ) 62 | { 63 | coord.x += xi; 64 | coord.y += yi; 65 | d += ai; 66 | } 67 | else 68 | { 69 | d += bi; 70 | coord.x += xi; 71 | } 72 | 73 | results.Add( coord ); 74 | } 75 | } 76 | else 77 | { 78 | ai = ( dx - dy ) * 2; 79 | bi = dx * 2; 80 | d = bi - dy; 81 | 82 | while( coord.y!=B.y ) 83 | { 84 | if( d>=0 ) 85 | { 86 | coord.x += xi; 87 | coord.y += yi; 88 | d += ai; 89 | } 90 | else 91 | { 92 | d += bi; 93 | coord.y += yi; 94 | } 95 | 96 | results.Add( coord ); 97 | } 98 | } 99 | } 100 | public static void TraceLine ( INT2 A , INT2 B , NativeList results , int2 min , int2 max ) 101 | { 102 | results.Clear(); 103 | { 104 | int2 dir = math.abs( A - B ); 105 | int capacity = math.max( dir.x , dir.y ); 106 | if( results.Capacitydy ) 140 | { 141 | ai = (dy - dx) * 2; 142 | bi = dy * 2; 143 | d = bi - dx; 144 | 145 | while( coord.x!=B.x ) 146 | { 147 | if( d>=0 ) 148 | { 149 | coord.x += xi; 150 | coord.y += yi; 151 | d += ai; 152 | } 153 | else 154 | { 155 | d += bi; 156 | coord.x += xi; 157 | } 158 | 159 | // test for out of bounds: 160 | if( math.any(new bool4{ x=coord.xmax.x , w=coord.y>max.y }) ) return; 161 | 162 | results.Add( coord ); 163 | } 164 | } 165 | else 166 | { 167 | ai = ( dx - dy ) * 2; 168 | bi = dx * 2; 169 | d = bi - dy; 170 | 171 | while( coord.y!=B.y ) 172 | { 173 | if( d>=0 ) 174 | { 175 | coord.x += xi; 176 | coord.y += yi; 177 | d += ai; 178 | } 179 | else 180 | { 181 | d += bi; 182 | coord.y += yi; 183 | } 184 | 185 | // test for out of bounds: 186 | if( math.any(new bool4{ x=coord.xmax.x , w=coord.y>max.y }) ) return; 187 | 188 | results.Add( coord ); 189 | } 190 | } 191 | } 192 | 193 | 194 | #endregion 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Runtime/NativeGrid tracing.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5c2abb64b7558a64dae1a2cafa838163 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeGrid.cs: -------------------------------------------------------------------------------- 1 | /// homepage: https://github.com/andrew-raphael-lukasik/NativeGrid 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | 5 | namespace NativeGridNamespace 6 | { 7 | /// NativeGrid holds NativeArray inside. 8 | public partial class NativeGrid 9 | : NativeGrid, System.IDisposable 10 | where T : unmanaged 11 | { 12 | #region FIELDS & PROPERTIES 13 | 14 | 15 | public NativeArray Array => _array; 16 | protected NativeArray _array; 17 | 18 | public readonly int Width; 19 | public readonly int Height; 20 | public readonly int Length; 21 | 22 | public bool IsCreated => _array.IsCreated; 23 | public JobHandle Dependency = default(JobHandle); 24 | 25 | 26 | #endregion 27 | #region CONSTRUCTORS 28 | 29 | 30 | public NativeGrid ( int width , int height , Allocator allocator ) 31 | { 32 | this._array = new NativeArray( width * height , allocator ); 33 | this.Width = width; 34 | this.Height = height; 35 | this.Length = width * height; 36 | } 37 | public NativeGrid ( int width , int height , NativeArray nativeArray ) 38 | { 39 | this._array = nativeArray; 40 | this.Width = width; 41 | this.Height = height; 42 | this.Length = width * height; 43 | } 44 | public NativeGrid ( int width , int height ) 45 | : this( width:width , height:height , allocator:Allocator.Persistent ) 46 | {} 47 | 48 | #region factory pattern 49 | public static NativeGrid Factory ( int width , int height , Allocator allocator ) => new NativeGrid( width , height , allocator ); 50 | public static NativeGrid Factory ( int width , int height , NativeArray nativeArrayToNest ) => new NativeGrid( width , height , nativeArrayToNest ); 51 | #endregion 52 | 53 | 54 | #endregion 55 | #region OPERATORS 56 | 57 | 58 | public T this [ int i ] 59 | { 60 | get { return _array[i]; } 61 | set { _array[i] = value; } 62 | } 63 | 64 | public T this [ int x , int y ] 65 | { 66 | get { return _array[CoordToIndex(x,y)]; } 67 | set { _array[CoordToIndex(x,y)] = value; } 68 | } 69 | 70 | public T this [ INT2 coord ] 71 | { 72 | get { return _array[CoordToIndex(coord)]; } 73 | set { _array[CoordToIndex(coord)] = value; } 74 | } 75 | 76 | #endregion 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Runtime/NativeGrid.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a89c4efa78ed53448bd398b9d8e47296 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/NativeMinHeap.cs: -------------------------------------------------------------------------------- 1 | using Unity.Collections; 2 | using Unity.Collections.LowLevel.Unsafe; 3 | using Unity.Jobs; 4 | 5 | namespace NativeGridNamespace 6 | { 7 | public interface INativeMinHeapComparer 8 | where INDEX : unmanaged 9 | where VALUE : unmanaged 10 | { 11 | int Compare ( INDEX lhs , INDEX rhs , NativeSlice comparables ); 12 | } 13 | 14 | public struct NativeMinHeap : INativeDisposable 15 | where INDEX : unmanaged, System.IEquatable 16 | where COMPARER : unmanaged, INativeMinHeapComparer 17 | where VALUE : unmanaged 18 | { 19 | 20 | NativeList _stack; 21 | COMPARER _comparer; 22 | [NativeDisableContainerSafetyRestriction]// oh boi, here comes trouble! 23 | NativeSlice _comparables; 24 | 25 | public bool IsCreated => _stack.IsCreated; 26 | public int Length => _stack.Length; 27 | public int Count => _stack.Length; 28 | 29 | public NativeMinHeap ( int capacity , Allocator allocator , COMPARER coparer , NativeSlice comparables ) 30 | { 31 | this._stack = new NativeList( capacity , allocator ); 32 | this._comparer = coparer; 33 | this._comparables = comparables; 34 | } 35 | 36 | public void Push ( INDEX item ) 37 | { 38 | _stack.Add( item ); 39 | MinHeapifyUp( _stack.Length-1 ); 40 | } 41 | public INDEX Pop () 42 | { 43 | INDEX removedItem = _stack[0]; 44 | _stack.RemoveAtSwapBack(0); 45 | MinHeapifyDown( 0 ); 46 | return removedItem; 47 | } 48 | 49 | public INDEX Peek () => _stack[0]; 50 | public void Clear () => _stack.Clear(); 51 | 52 | void MinHeapifyUp ( int childIndex ) 53 | { 54 | if( childIndex==0 ) return; 55 | int parentIndex = (childIndex-1)/2; 56 | INDEX childVal = _stack[childIndex]; 57 | INDEX parentVal = _stack[parentIndex]; 58 | if( _comparer.Compare(childVal,parentVal,_comparables)<0 ) 59 | { 60 | // swap the parent and the child 61 | _stack[childIndex] = parentVal; 62 | _stack[parentIndex] = childVal; 63 | MinHeapifyUp( parentIndex ); 64 | } 65 | } 66 | 67 | void MinHeapifyDown ( int index ) 68 | { 69 | int leftChildIndex = index * 2 + 1; 70 | int rightChildIndex = index * 2 + 2; 71 | int smallestItemIndex = index;// The index of the parent 72 | if( 73 | leftChildIndex<=this._stack.Length-1 74 | && _comparer.Compare(_stack[leftChildIndex],_stack[smallestItemIndex],_comparables)<0 ) 75 | { 76 | smallestItemIndex = leftChildIndex; 77 | } 78 | if( 79 | rightChildIndex<=this._stack.Length-1 80 | && _comparer.Compare(_stack[rightChildIndex],_stack[smallestItemIndex],_comparables)<0 ) 81 | { 82 | smallestItemIndex = rightChildIndex; 83 | } 84 | if( smallestItemIndex!=index ) 85 | { 86 | // swap the parent with the smallest of the child items 87 | INDEX temp = _stack[index]; 88 | _stack[index] = _stack[smallestItemIndex]; 89 | _stack[smallestItemIndex] = temp; 90 | MinHeapifyDown( smallestItemIndex ); 91 | } 92 | } 93 | 94 | public int Parent ( int key ) => (key-1)/2; 95 | public int Left ( int key ) => 2*key + 1; 96 | public int Right ( int key ) => 2*key + 2; 97 | 98 | public NativeArray AsArray () => _stack.AsArray(); 99 | 100 | public void Dispose () 101 | { 102 | if( _stack.IsCreated ) _stack.Dispose(); 103 | } 104 | public JobHandle Dispose ( JobHandle inputDeps) => _stack.Dispose( inputDeps ); 105 | 106 | public override string ToString () 107 | { 108 | var sb = new System.Text.StringBuilder("{ "); 109 | var array = _stack.AsArray().ToArray(); 110 | if( array.Length!=0 ) 111 | { 112 | sb.Append($"{array[0]}"); 113 | for( int i=1 ; i GRID; 8 | [SerializeField] int _width = 512, _height = 512; 9 | [SerializeField] Color32 _color = Color.yellow; 10 | Texture2D _texture = null; 11 | int2 _prevCoord; 12 | 13 | void OnEnable () 14 | { 15 | _texture = new Texture2D( _width , _height , TextureFormat.ARGB32 , 0 , true ); 16 | GRID = new NativeGrid( width:_width , height:_height , _texture.GetRawTextureData() ); 17 | var fillJobHandle = GRID.Fill( new ARGB32{ A=0 , R=255 , G=255 , B=255 } , GRID.Dependency ); 18 | fillJobHandle.Complete(); 19 | _texture.Apply(); 20 | } 21 | 22 | void OnDisable () 23 | { 24 | Destroy( _texture ); 25 | GRID.Dispose(); 26 | } 27 | 28 | void Update () 29 | { 30 | int2 coord = (int2) math.round( Input.mousePosition / new Vector2{ x=Screen.width , y=Screen.height } * new Vector2{ x=_texture.width , y=_texture.height } ); 31 | if( math.any(coord!=_prevCoord) && math.all(new bool4{ x=coord.x>=0 , y=coord.y>=0 , z=coord.x<_texture.width , w=coord.y<_texture.height }) ) 32 | { 33 | if( !Input.GetKey(KeyCode.LeftAlt) ) 34 | { 35 | if( Input.GetMouseButtonDown(0) ) 36 | { 37 | _prevCoord = coord; 38 | GRID[coord] = _color; 39 | _texture.Apply(); 40 | } 41 | else if( Input.GetMouseButton(0) ) 42 | { 43 | var fillLineJob = GRID.FillLine( _prevCoord , coord , _color ); 44 | fillLineJob.Complete(); 45 | _prevCoord = coord; 46 | _texture.Apply(); 47 | } 48 | } 49 | else 50 | { 51 | if( GRID.IsCoordValid(coord) ) 52 | _color = GRID[coord]; 53 | } 54 | } 55 | } 56 | 57 | void OnGUI () => Graphics.DrawTexture( new Rect{ width=Screen.width , height=Screen.height } , _texture ); 58 | 59 | public struct ARGB32 60 | { 61 | public byte A,R,G,B; 62 | public static implicit operator ARGB32 ( Color32 rgba32 ) => new ARGB32{ A=rgba32.a , R=rgba32.r , G=rgba32.g , B=rgba32.b }; 63 | public static implicit operator Color32 ( ARGB32 argb32 ) => new Color32{ r=argb32.R , g=argb32.G , b=argb32.B , a=argb32.A }; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aad2583f4b9f809459053531037da1b0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c5e5513041485944ba05ea866c639e9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/Test NativeGrid Pathfinding.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using UnityEngine.UIElements; 4 | using UnityEditor.UIElements; 5 | 6 | using Unity.Mathematics; 7 | using Unity.Collections; 8 | using Unity.Jobs; 9 | 10 | namespace NativeGridNamespace.Tests 11 | { 12 | public class PathfindingTester : EditorWindow 13 | { 14 | 15 | VisualElement[] _grid; 16 | int _resolution = 128; 17 | float2 _offset = 0f;// perlin noise pos offset 18 | float2 _start01 = new float2{ x=0.1f , y=0.1f }; 19 | int2 _startI2 => (int2)( _start01 * (_resolution-1) ); 20 | float2 _dest01 = new float2{ x=0.9f , y=0.9f }; 21 | int2 _destI2 => (int2)( _dest01 * (_resolution-1) ); 22 | float2 _smoothstep = new float2{ x=0f , y=0.8f };// perlin noise post process 23 | float _hMultiplier = 1.5f; 24 | float _moveCostSensitivity = 10f; 25 | int _stepBudget = int.MaxValue; 26 | 27 | const int _drawTextMaxResolution = 50; 28 | bool labelsExist => _resolution<=_drawTextMaxResolution; 29 | 30 | public void OnEnable () 31 | { 32 | var ROOT = rootVisualElement; 33 | var GRID = new VisualElement(); 34 | var TOOLBAR = new VisualElement(); 35 | var TOOLBAR_COLUMN_0 = new VisualElement(); 36 | var TOOLBAR_COLUMN_1 = new VisualElement(); 37 | ROOT.Add( TOOLBAR ); 38 | TOOLBAR.Add( TOOLBAR_COLUMN_0 ); 39 | TOOLBAR.Add( TOOLBAR_COLUMN_1 ); 40 | ROOT.Add( GRID ); 41 | 42 | { 43 | var style = TOOLBAR.style; 44 | style.flexDirection = FlexDirection.Row; 45 | } 46 | { 47 | var style = TOOLBAR_COLUMN_0.style; 48 | style.width = new Length( 50f , LengthUnit.Percent ); 49 | style.flexDirection = FlexDirection.Column; 50 | } 51 | { 52 | var style = TOOLBAR_COLUMN_1.style; 53 | style.width = new Length( 50f , LengthUnit.Percent ); 54 | style.flexDirection = FlexDirection.Column; 55 | } 56 | 57 | var RESOLUTION = new IntegerField( "Resolution:" ); 58 | RESOLUTION.style.paddingLeft = RESOLUTION.style.paddingRight = 10; 59 | RESOLUTION.value = _resolution; 60 | RESOLUTION.RegisterValueChangedCallback( (e) => { 61 | _resolution = math.clamp( e.newValue , 1 , 256 ); 62 | if( RESOLUTION.value!=_resolution ) RESOLUTION.value = _resolution; 63 | GRID.Clear(); 64 | CreateGridLayout( GRID ); 65 | NewRandomMap(); 66 | SolvePath(); 67 | Repaint(); 68 | } ); 69 | TOOLBAR_COLUMN_0.Add( RESOLUTION ); 70 | 71 | var HEURISTIC_COST = new FloatField( $"H Multiplier:" ); 72 | HEURISTIC_COST.style.paddingLeft = HEURISTIC_COST.style.paddingRight = 10; 73 | HEURISTIC_COST.value = _hMultiplier; 74 | HEURISTIC_COST.RegisterValueChangedCallback( (e)=> { 75 | _hMultiplier = e.newValue; 76 | NewRandomMap(); 77 | SolvePath(); 78 | Repaint(); 79 | } ); 80 | TOOLBAR_COLUMN_0.Add( HEURISTIC_COST ); 81 | 82 | var HEURISTIC_SEARCH = new FloatField( $"Move Cost Sensitivity:" ); 83 | HEURISTIC_SEARCH.style.paddingLeft = HEURISTIC_SEARCH.style.paddingRight = 10; 84 | HEURISTIC_SEARCH.value = _moveCostSensitivity; 85 | HEURISTIC_SEARCH.RegisterValueChangedCallback( (e)=> { 86 | _moveCostSensitivity = e.newValue; 87 | NewRandomMap(); 88 | SolvePath(); 89 | Repaint(); 90 | } ); 91 | TOOLBAR_COLUMN_0.Add( HEURISTIC_SEARCH ); 92 | 93 | var SMOOTHSTEP = new MinMaxSlider( "Move Cost Range:" , _smoothstep.x , _smoothstep.y , 0 , 1 ); 94 | { 95 | var style = SMOOTHSTEP.style; 96 | style.marginBottom = style.marginLeft = style.marginRight = style.marginTop = 2; 97 | } 98 | SMOOTHSTEP.RegisterValueChangedCallback( (ctx) => { 99 | _smoothstep = ctx.newValue; 100 | NewRandomMap(); 101 | SolvePath(); 102 | Repaint(); 103 | } ); 104 | TOOLBAR_COLUMN_1.Add( SMOOTHSTEP ); 105 | 106 | var START_DEST_LINE = new VisualElement(); 107 | { 108 | // START_DEST_LINE.style.flexGrow = 1; 109 | START_DEST_LINE.style.flexDirection = FlexDirection.Row; 110 | 111 | var SPACE = new VisualElement(); 112 | SPACE.style.flexGrow = 1; 113 | 114 | var START = new Label("Start:"); 115 | var START_X = new Slider( 0 , 1 ); 116 | var START_Y = new Slider( 0 , 1 ); 117 | START_X.value = _start01.x; 118 | START_Y.value = _start01.y; 119 | START_X.style.flexGrow = 1; 120 | START_Y.style.flexGrow = 1; 121 | START_X.RegisterValueChangedCallback( (ctx) => { 122 | _start01.x = ctx.newValue; 123 | NewRandomMap(); 124 | SolvePath(); 125 | Repaint(); 126 | } ); 127 | START_Y.RegisterValueChangedCallback( (ctx) => { 128 | _start01.y = ctx.newValue; 129 | NewRandomMap(); 130 | SolvePath(); 131 | Repaint(); 132 | } ); 133 | START_DEST_LINE.Add( START ); 134 | START_DEST_LINE.Add( SPACE ); 135 | START_DEST_LINE.Add( START_X ); 136 | START_DEST_LINE.Add( START_Y ); 137 | START_DEST_LINE.Add( SPACE ); 138 | 139 | var END = new Label("End:"); 140 | var END_X = new Slider( 0 , 1 ); 141 | var END_Y = new Slider( 0 , 1 ); 142 | END_X.value = _dest01.x; 143 | END_Y.value = _dest01.y; 144 | END_X.style.flexGrow = 1; 145 | END_Y.style.flexGrow = 1; 146 | END_X.RegisterValueChangedCallback( (ctx) => { 147 | _dest01.x = ctx.newValue; 148 | NewRandomMap(); 149 | SolvePath(); 150 | Repaint(); 151 | } ); 152 | END_Y.RegisterValueChangedCallback( (ctx) => { 153 | _dest01.y = ctx.newValue; 154 | NewRandomMap(); 155 | SolvePath(); 156 | Repaint(); 157 | } ); 158 | START_DEST_LINE.Add( END ); 159 | START_DEST_LINE.Add( SPACE ); 160 | START_DEST_LINE.Add( END_X ); 161 | START_DEST_LINE.Add( END_Y ); 162 | START_DEST_LINE.Add( SPACE ); 163 | } 164 | TOOLBAR_COLUMN_1.Add( START_DEST_LINE ); 165 | 166 | var STEPLIMIT = new IntegerField("Step Budget:"); 167 | { 168 | STEPLIMIT.value = _stepBudget; 169 | STEPLIMIT.RegisterValueChangedCallback( (ctx) => { 170 | if( ctx.newValue>=0 ) 171 | { 172 | _stepBudget = ctx.newValue; 173 | } 174 | else 175 | { 176 | _stepBudget = 0; 177 | STEPLIMIT.SetValueWithoutNotify( 0 ); 178 | } 179 | NewRandomMap(); 180 | SolvePath(); 181 | Repaint(); 182 | } ); 183 | STEPLIMIT.RegisterCallback( (WheelEvent e) => { 184 | Vector2 mouseScrollDelta = e.mouseDelta; 185 | int scrollDir = (int) Mathf.Sign(mouseScrollDelta.y); 186 | _stepBudget = Mathf.Max( _stepBudget - scrollDir , 0 ); 187 | STEPLIMIT.SetValueWithoutNotify( _stepBudget ); 188 | NewRandomMap(); 189 | SolvePath(); 190 | Repaint(); 191 | } ); 192 | } 193 | TOOLBAR_COLUMN_1.Add( STEPLIMIT ); 194 | 195 | { 196 | var gridStyle = GRID.style; 197 | gridStyle.flexGrow = 1; 198 | gridStyle.flexDirection = FlexDirection.ColumnReverse; 199 | gridStyle.marginBottom = gridStyle.marginLeft = gridStyle.marginRight = gridStyle.marginTop = 2; 200 | gridStyle.backgroundColor = new Color{ a = 0.02f }; 201 | } 202 | GRID.RegisterCallback( (MouseDownEvent e)=>{ 203 | _offset = (float) EditorApplication.timeSinceStartup; 204 | NewRandomMap(); 205 | SolvePath(); 206 | Repaint(); 207 | } ); 208 | CreateGridLayout( GRID ); 209 | 210 | NewRandomMap(); 211 | SolvePath(); 212 | } 213 | 214 | [MenuItem("Test/NativeGrid/Pathfinding")] 215 | static void ShowWindow () 216 | { 217 | var window = GetWindow(); 218 | window.titleContent = new GUIContent("NativeGrid Pathfinding Test"); 219 | window.minSize = new Vector2{ x=512+4 , y=512+4+60 }; 220 | } 221 | 222 | void CreateGridLayout ( VisualElement GRID ) 223 | { 224 | _grid = new VisualElement[ _resolution*_resolution ]; 225 | for( int i=0, y=0 ; y<_resolution ; y++ ) 226 | { 227 | var ROW = new VisualElement(); 228 | var rowStyle = ROW.style; 229 | rowStyle.flexDirection = FlexDirection.Row; 230 | rowStyle.flexGrow = 1; 231 | 232 | for( int x=0 ; x<_resolution ; x++, i++ ) 233 | { 234 | var CELL = new VisualElement(); 235 | CELL.style.flexGrow = 1; 236 | 237 | if( labelsExist ) 238 | { 239 | var LABEL = new Label("00"); 240 | LABEL.visible = false; 241 | LABEL.StretchToParentSize(); 242 | LABEL.style.unityTextAlign = TextAnchor.MiddleCenter; 243 | CELL.Add( LABEL ); 244 | } 245 | 246 | ROW.Add( CELL ); 247 | _grid[i] = CELL; 248 | } 249 | 250 | GRID.Add( ROW ); 251 | } 252 | } 253 | 254 | void NewRandomMap () 255 | { 256 | float frac = 1f / (float)_resolution; 257 | for( int i=0, y=0 ; y<_resolution ; y++ ) 258 | for( int x=0 ; x<_resolution ; x++, i++ ) 259 | { 260 | float fx = (float)x * frac * 4f + _offset.x; 261 | float fy = (float)y * frac * 4f + _offset.y; 262 | float noise1 = Mathf.PerlinNoise( fx , fy ); 263 | float noise2 = math.pow( Mathf.PerlinNoise(fx*2.3f,fy*2.3f) , 3f ); 264 | float noise3 = math.pow( Mathf.PerlinNoise(fx*14f,fy*14f) , 6f ) * (1f-noise1) * (1f-noise2); 265 | float noiseSum = math.pow( noise1 + noise2*0.3f + noise3*0.08f , 3.6f ); 266 | float smoothstep = math.smoothstep( _smoothstep.x , _smoothstep.y , noiseSum ); 267 | _grid[i].style.backgroundColor = new Color{ r=smoothstep , g=smoothstep , b=smoothstep , a=1f }; 268 | } 269 | } 270 | 271 | void SolvePath () 272 | { 273 | // prepare data: 274 | NativeArray moveCost; 275 | { 276 | int len = _resolution*_resolution; 277 | moveCost = new NativeArray( len , Allocator.TempJob , NativeArrayOptions.UninitializedMemory ); 278 | byte[] arr = new byte[len];// NativeArray enumeration is slow outside Burst 279 | for( int i=len-1 ; i!=-1 ; i-- ) 280 | arr[i] = (byte)( _grid[i].style.backgroundColor.value.r * 255 ); 281 | moveCost.CopyFrom( arr ); 282 | } 283 | 284 | // calculate: 285 | NativeList path; 286 | half[] fData; 287 | half[] gData; 288 | int2[] solution; 289 | int2[] visited; 290 | { 291 | path = new NativeList( _resolution , Allocator.TempJob ); 292 | 293 | // run job: 294 | var watch = System.Diagnostics.Stopwatch.StartNew(); 295 | var job = new NativeGrid.AStarJob( 296 | start: _startI2 , 297 | destination: _destI2 , 298 | moveCost: moveCost , 299 | moveCostWidth: _resolution , 300 | results: path , 301 | hMultiplier: _hMultiplier , 302 | moveCostSensitivity: _moveCostSensitivity , 303 | stepBudget: _stepBudget 304 | ); 305 | job.Run(); 306 | watch.Stop(); 307 | bool success = job.Results.Length!=0; 308 | Debug.Log($"{nameof(NativeGrid.AStarJob)} took {(double)watch.ElapsedTicks/(double)System.TimeSpan.TicksPerMillisecond:G8} ms {(success?$"and succeeded in finding a path of {job.Results.Length} steps":"but no path was found")}."); 309 | 310 | // copy debug data: 311 | fData = job.F.ToArray(); 312 | gData = job.G.ToArray(); 313 | solution = job.Solution.ToArray(); 314 | using( var nativeArray = job.Visited.ToNativeArray(Allocator.Temp) ) visited = nativeArray.ToArray(); 315 | 316 | // dispose unmanaged arrays: 317 | job.Dispose(); 318 | } 319 | 320 | // visualize: 321 | { 322 | // start cell 323 | int startI = NativeGrid.CoordToIndex( _startI2 , _resolution ); 324 | var cellStyle = _grid[startI].style; 325 | Color col = cellStyle.backgroundColor.value * 0.75f; 326 | col.r = 1f; 327 | cellStyle.backgroundColor = col; 328 | } 329 | foreach( var coord in path )// path 330 | { 331 | int i = NativeGrid.CoordToIndex( coord , _resolution ); 332 | var cellStyle = _grid[i].style; 333 | Color col = cellStyle.backgroundColor.value * 0.75f; 334 | col.r = 1f; 335 | cellStyle.backgroundColor = col; 336 | } 337 | foreach( var coord in visited )// visited 338 | { 339 | int i = NativeGrid.CoordToIndex( coord , _resolution ); 340 | var CELL = _grid[i]; 341 | 342 | var cellStyle = CELL.style; 343 | Color col = cellStyle.backgroundColor.value; 344 | col.b = 1f; 345 | cellStyle.backgroundColor = col; 346 | } 347 | if( labelsExist ) 348 | for( int i=fData.Length-1 ; i!=-1 ; i-- )// labels 349 | { 350 | var CELL = _grid[i]; 351 | int2 coord = NativeGrid.IndexToCoord( i , _resolution ); 352 | Label LABEL = CELL[0] as Label; 353 | 354 | var f = fData[i]; 355 | var g = gData[i]; 356 | var h = NativeGrid.EuclideanHeuristic( coord , _destI2 ); 357 | int2 origin = solution[i]; 358 | if( f!=float.MaxValue ) 359 | { 360 | LABEL.text = $"[{coord.x},{coord.y}]\nF:{f:G8}\nG:{g:G8}\nH:{h:G8}\nstep-1: [{origin.x},{origin.y}]"; 361 | LABEL.visible = true; 362 | } 363 | else LABEL.visible = false; 364 | } 365 | 366 | // dispose data: 367 | moveCost.Dispose(); 368 | path.Dispose(); 369 | } 370 | 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /Tests/Editor/Test NativeGrid Pathfinding.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 05b303d9d5db24348ab9371d8dca139e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Test NativeGrid.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using NUnit.Framework; 4 | 5 | using Unity.Mathematics; 6 | using Unity.Collections; 7 | using Unity.Jobs; 8 | 9 | namespace NativeGridNamespace.Tests 10 | { 11 | static class NATIVE_GRID 12 | { 13 | static class INDICES 14 | { 15 | 16 | [Test] public static void IndexToCoord () 17 | { 18 | RectInt R = new RectInt{ width=6 , height=6 }; 19 | Assert.AreEqual( new int2{ x=1 , y=2 } , NativeGrid.IndexToCoord(13,R.width) ); 20 | } 21 | 22 | [Test] public static void CoordToIndex () 23 | { 24 | RectInt R = new RectInt{ width=6 , height=6 }; 25 | Assert.AreEqual( 13 , NativeGrid.CoordToIndex(1,2,R.width) ); 26 | } 27 | 28 | [Test] public static void IndexTranslate () 29 | { 30 | RectInt R = new RectInt{ width=6 , height=6 }; 31 | RectInt r = new RectInt{ x=1 , y=1 , width=3 , height=3 }; 32 | Assert.AreEqual( new int2(1,2) , NativeGrid.IndexTranslate(r,new int2(0,1)) ); 33 | Assert.AreEqual( 20 , NativeGrid.IndexTranslate(r,1,2,R.width) ); 34 | } 35 | 36 | static class MORE_INDEX_CONVERSION_TESTS 37 | { 38 | [Test] public static void MidpointRoundingAwayFromZero () 39 | { 40 | for( float range=2.1f, value=-range ; value PointToCoord_Test( new float2(-1498.664f,-176.8691f) , new float2(-1499f,-177f) ); 45 | [Test] public static void PointToCoord_ReproCase2 () => PointToCoord_Test( new float2(-1486.99f,-167.4532f) , new float2(-1487f,-167f) ); 46 | [Test] public static void PointToCoord_ReproCase3 () => PointToCoord_Test( new float2(-1052.006f,-125.7217f) , new float2(-1053f,-125f) ); 47 | [Test] public static void PointToCoord_ReproCase4 () => PointToCoord_Test( new float2(-362.202f,78.26967f) , new float2(-363f,79f) ); 48 | [Test] public static void PointToCoord_ReproCase5 () => PointToCoord_Test( new float2(640.7607f,-180.0993f) , new float2(641f,-181f) ); 49 | [Test] public static void PointToCoord_ReproCase6 () => PointToCoord_Test( new float2(992.0559f,-373.1479f) , new float2(993f,-373f) ); 50 | [Test] public static void PointToCoord_ReproCase7 () => PointToCoord_Test( new float2(1267.036f,-476.1908f) , new float2(1267f,-477f) ); 51 | static void PointToCoord_Test ( float2 a , float2 b ) 52 | { 53 | float2 worldSize = new float2( 3000f , 3000f ); 54 | float2 gridOrigin = new float2( -1500f , -1500f ); 55 | const int width = 1500, height = 1500; 56 | INT2 A = NativeGrid.PointToCoord( a-gridOrigin, worldSize , width , height ); 57 | INT2 B = NativeGrid.PointToCoord( b-gridOrigin , worldSize , width , height ); 58 | // Debug.Log( $"a:{GetPositionInsideCell_GetDebugString(a,width,height,worldSize)}\nb:{GetPositionInsideCell_GetDebugString(b,width,height,worldSize)}" ); 59 | Assert.AreEqual( A , B ); 60 | } 61 | 62 | [Test] public static void PointToCoord_2x2_0f_N0f1 () => PointToCoord_Test_2x2( 0f , -0.1f ); 63 | [Test] public static void PointToCoord_2x2_0f_0f1 () => PointToCoord_Test_2x2( 0f , 0.1f ); 64 | [Test] public static void PointToCoord_2x2_0f_0f2 () => PointToCoord_Test_2x2( 0f , 0.2f ); 65 | [Test] public static void PointToCoord_2x2_0f_0f499 () => PointToCoord_Test_2x2( 0f , 0.499f ); 66 | [Test] public static void PointToCoord_2x2_0f5_0f3 () => PointToCoord_Test_2x2( 0f , 0.3f ); 67 | [Test] public static void PointToCoord_2x2_0f5_0f4 () => PointToCoord_Test_2x2( 0f , 0.4f ); 68 | [Test] public static void PointToCoord_2x2_0f5_0f5 () => PointToCoord_Test_2x2( 0f , 0.5f ); 69 | [Test] public static void PointToCoord_2x2_0f5_0f6 () => PointToCoord_Test_2x2( 0f , 0.6f ); 70 | [Test] public static void PointToCoord_2x2_0f_0f9999 () => PointToCoord_Test_2x2( 0f , 0.9999f ); 71 | [Test] public static void PointToCoord_2x2_0f_1fMinusEpsilon () => PointToCoord_Test_2x2( 0f , 1f-float.Epsilon , false );// Jeśli zacznie działać to znaczy że się precyzja zmieniła 72 | [Test] public static void PointToCoord_2x2_0f_1fMinus1E08 () => PointToCoord_Test_2x2( 0f , 1f - 1E-8f , false );// Jeśli zacznie działać to znaczy że się precyzja zmieniła 73 | [Test] public static void PointToCoord_2x2_0f_1fMinus1E07 () => PointToCoord_Test_2x2( 0f , 1f - 1E-7f ); 74 | [Test] public static void PointToCoord_2x2_0f_1fMinus1E06 () => PointToCoord_Test_2x2( 0f , 1f - 1E-6f ); 75 | [Test] public static void PointToCoord_2x2_1f_1f () => PointToCoord_Test_2x2( 1f , 1f ); 76 | [Test] public static void PointToCoord_2x2_1f_1fPlusEpsilon () => PointToCoord_Test_2x2( 1f , 1f+float.Epsilon ); 77 | [Test] public static void PointToCoord_2x2_1f_1f00001 () => PointToCoord_Test_2x2( 1f , 1.00001f ); 78 | public static void PointToCoord_Test_2x2 ( float2 a , float2 b , bool equalityTest = true ) 79 | { 80 | float2 worldSize = new float2( 2f , 2f ); const int width = 2, height = 2; 81 | INT2 A = NativeGrid.PointToCoord( a , worldSize , width , height ); 82 | INT2 B = NativeGrid.PointToCoord( b , worldSize , width , height ); 83 | // Debug.Log( $"a:{GetPositionInsideCell_GetDebugString(a,width,height,worldSize)}\nb:{GetPositionInsideCell_GetDebugString(b,width,height,worldSize)}" ); 84 | if( equalityTest ) Assert.AreEqual( A , B ); 85 | else Assert.AreNotEqual( A , B ); 86 | } 87 | 88 | 89 | [Test] public static void PointToCoord_1x1_0f_N0f00001 () => PointToCoord_Test_1x1( 0f , -0.00001f ); 90 | [Test] public static void PointToCoord_1x1_0f_0f00001 () => PointToCoord_Test_1x1( 0f , 0.00001f ); 91 | [Test] public static void PointToCoord_1x1_0f_0f1 () => PointToCoord_Test_1x1( 0f , 0.1f ); 92 | [Test] public static void PointToCoord_1x1_0f_0f49999 () => PointToCoord_Test_1x1( 0f , 0.49999f ); 93 | [Test] public static void PointToCoord_1x1_0f_0f5MinusEpsilon () => PointToCoord_Test_1x1( 0f , 0.5f-float.Epsilon ); 94 | [Test] public static void PointToCoord_1x1_1f_0f5 () => PointToCoord_Test_1x1( 1f , 0.5f ); 95 | [Test] public static void PointToCoord_1x1_0f_0f5PlusEpsilon () => PointToCoord_Test_1x1( 1f , 0.5f+float.Epsilon ); 96 | [Test] public static void PointToCoord_1x1_1f_0f9 () => PointToCoord_Test_1x1( 1f , 0.9f ); 97 | [Test] public static void PointToCoord_1x1_1f_1f1 () => PointToCoord_Test_1x1( 1f , 1.1f ); 98 | public static void PointToCoord_Test_1x1 ( float2 a , float2 b ) 99 | { 100 | float2 worldSize = new float2{ x=2f , y=1f }; const int width = 1, height = 1; 101 | INT2 A = NativeGrid.PointToCoord( a , worldSize , width , height ); 102 | INT2 B = NativeGrid.PointToCoord( b , worldSize , width , height ); 103 | // Debug.Log( $"a:{GetPositionInsideCell_GetDebugString(a,width,height,worldSize)}\nb:{GetPositionInsideCell_GetDebugString(b,width,height,worldSize)}" ); 104 | Assert.AreEqual( A , B ); 105 | } 106 | 107 | static string GetPositionInsideCell_GetDebugString ( float2 p , int width , int height , float2 worldSize ) 108 | { 109 | NativeGrid.GetPositionInsideCell( p.x , width , worldSize.x , out int xlo , out int xhi , out float xf ); 110 | NativeGrid.GetPositionInsideCell( p.y , height , worldSize.y , out int ylo , out int yhi , out float yf ); 111 | return $"\n x: {xlo}... {xf:R} ...{xhi}\n y: {ylo}... {yf:R} ...{yhi}"; 112 | } 113 | 114 | 115 | [Test] public static void IsPointBetweenCells___3000f_1500___1f_IS_0 () => Assert.AreEqual( 0 , NativeGrid.IsPointBetweenCells(1f,1500,3000f) ); 116 | // [Test] public static void IsPointBetweenCells___3000f_1500___2f_IS_1 () => Assert.AreEqual( 1 , NativeGrid.IsPointBetweenCells(2f,1500,3000f) ); 117 | 118 | [Test] public static void IsPointBetweenCells___2f_2___1fMinusEpsilon_IS_0 () => Assert.AreEqual( 0 , NativeGrid.IsPointBetweenCells(1f-float.Epsilon,2,2f) ); 119 | [Test] public static void IsPointBetweenCells___2f_2___1fPlusEpsilon_IS_0 () => Assert.AreEqual( 0 , NativeGrid.IsPointBetweenCells(1f+float.Epsilon,2,2f) ); 120 | // [Test] public static void IsPointBetweenCells___2f_2___1f_IS_1 () => Assert.AreEqual( 1 , NativeGrid.IsPointBetweenCells(1f,2,2f) ); 121 | 122 | } 123 | 124 | static class EQUIVALENT_METHODS_COMPARED 125 | { 126 | [Test] public static void IndexToCoord_VS_PointToCoord_CASE01 () 127 | { 128 | IndexToCoord_VS_PointToCoord( 129 | position: new float2{ x = 0.333f , y = 0.666f } , 130 | gridWorldSize: new float2{ x = 1f , y = 1f } , 131 | gridWidth: 100 , 132 | gridHeight: 100 133 | ); 134 | } 135 | [Test] public static void IndexToCoord_VS_PointToCoord_CASE02 () 136 | { 137 | IndexToCoord_VS_PointToCoord( 138 | position: new float2{ x = 1557.4f , y = 1521.5f } , 139 | gridWorldSize: new float2{ x = 3000f , y = 3000f } , 140 | gridWidth: 1500 , 141 | gridHeight: 1500 142 | ); 143 | } 144 | [Test] public static void IndexToCoord_VS_PointToCoord_CASE03 () 145 | { 146 | IndexToCoord_VS_PointToCoord( 147 | position: new float2{ x = -1499f , y = -177f } , 148 | gridWorldSize: new float2{ x = 3000f , y = 3000f } , 149 | gridWidth: 1500 , 150 | gridHeight: 1500 151 | ); 152 | } 153 | static void IndexToCoord_VS_PointToCoord ( float2 position , float2 gridWorldSize , int gridWidth , int gridHeight ) 154 | { 155 | int Ai = NativeGrid.PointToIndex( position , gridWorldSize , gridWidth , gridHeight ); 156 | int2 Ai2 = NativeGrid.IndexToCoord( Ai , gridWidth ); 157 | int2 Bi2 = NativeGrid.PointToCoord( position , gridWorldSize , gridWidth , gridHeight ); 158 | Assert.AreEqual( Ai2 , Bi2 , $"\tNativeGrid.IndexToCoord(NativeGrid.PointToIndex(params)) returned: {Ai2}\n\tNativeGrid.PointToCoord returned: {Bi2}" ); 159 | } 160 | } 161 | 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /Tests/Editor/Test NativeGrid.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42bed2fc7d990c140bd87f425721ddcf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Test NativeMinHeap.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using NUnit.Framework; 3 | 4 | using Unity.Mathematics; 5 | using Unity.Collections; 6 | using Unity.Jobs; 7 | 8 | namespace NativeGridNamespace.Tests 9 | { 10 | static class NATIVE_MIN_HEAP 11 | { 12 | static class INT 13 | { 14 | [Test] public static void Peek_01 () 15 | { 16 | var minHeap = new NativeMinHeap( 1 , Allocator.Temp , default(IntComparer) , new NativeArray(0,Allocator.Temp) ); 17 | minHeap.Push(3); 18 | minHeap.Push(10); 19 | minHeap.Push(7); 20 | minHeap.Push(14); 21 | minHeap.Push(1); 22 | minHeap.Push(5); 23 | minHeap.Push(2); 24 | minHeap.Push(22); 25 | 26 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 27 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 28 | 29 | Assert.AreEqual( minHeap.Pop() , 1 ); 30 | } 31 | 32 | [Test] public static void Peek_02 () 33 | { 34 | var minHeap = new NativeMinHeap( 1 , Allocator.Temp , default(IntComparer) , new NativeArray(0,Allocator.Temp) ); 35 | minHeap.Push(33); 36 | minHeap.Push(22); 37 | minHeap.Push(11); 38 | minHeap.Push(14); 39 | 40 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 41 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 42 | 43 | Assert.AreEqual( minHeap.Pop() , 11 ); 44 | } 45 | 46 | [Test] public static void Peek_03 () 47 | { 48 | var minHeap = new NativeMinHeap( 1 , Allocator.Temp , default(IntComparer) , new NativeArray(0,Allocator.Temp) ); 49 | minHeap.Push(3); 50 | minHeap.Push(1); 51 | minHeap.Push(-1); 52 | minHeap.Push(2); 53 | 54 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 55 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 56 | 57 | Assert.AreEqual( minHeap.Pop() , -1 ); 58 | } 59 | 60 | public struct IntComparer : INativeMinHeapComparer 61 | { 62 | public int Compare ( int lhs , int rhs , NativeSlice IGNORED ) => lhs.CompareTo(rhs); 63 | } 64 | 65 | } 66 | 67 | static class ASTAR_JOB_COMPARER 68 | { 69 | 70 | [Test] public static void Peek_01 () 71 | { 72 | var weights = new NativeArray( 4 , Allocator.Temp ); 73 | weights[0] = (half) 10; 74 | weights[1] = (half) 20; 75 | weights[2] = (half) 30; 76 | weights[3] = (half) 5; 77 | var comparer = new NativeGrid.AStarJob.Comparer( weights.Length/2 ); 78 | var minHeap = new NativeMinHeap( weights.Length , Allocator.Temp , comparer, weights ); 79 | minHeap.Push( new int2(0,0) ); 80 | minHeap.Push( new int2(0,1) ); 81 | minHeap.Push( new int2(1,0) ); 82 | minHeap.Push( new int2(1,1) ); 83 | 84 | Debug.Log( $"weights:\t{weights.ToString()}" ); 85 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 86 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 87 | 88 | Assert.AreEqual( minHeap.Pop() , new int2(1,1) ); 89 | } 90 | 91 | [Test] public static void Peek_02 () 92 | { 93 | var weights = new NativeArray( 4 , Allocator.Temp ); 94 | weights[0] = (half) 11; 95 | weights[1] = (half) 111; 96 | weights[2] = (half) 222; 97 | weights[3] = (half) 333; 98 | var comparer = new NativeGrid.AStarJob.Comparer( weights.Length/2 ); 99 | var minHeap = new NativeMinHeap( weights.Length , Allocator.Temp , comparer, weights ); 100 | minHeap.Push( new int2(0,0) ); 101 | minHeap.Push( new int2(0,1) ); 102 | minHeap.Push( new int2(1,0) ); 103 | minHeap.Push( new int2(1,1) ); 104 | 105 | Debug.Log( $"weights:\t{weights.ToString()}" ); 106 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 107 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 108 | 109 | Assert.AreEqual( minHeap.Pop() , new int2(0,0) ); 110 | } 111 | 112 | [Test] public static void Peek_03 () 113 | { 114 | var weights = new NativeArray( 4 , Allocator.Temp ); 115 | weights[0] = (half) 0.5; 116 | weights[1] = (half) 0.1; 117 | weights[2] = (half) 0.2; 118 | weights[3] = (half) 0.3; 119 | var comparer = new NativeGrid.AStarJob.Comparer( weights.Length/2 ); 120 | var minHeap = new NativeMinHeap( weights.Length , Allocator.Temp , comparer, weights ); 121 | minHeap.Push( new int2(0,0) ); 122 | minHeap.Push( new int2(0,1) ); 123 | minHeap.Push( new int2(1,0) ); 124 | minHeap.Push( new int2(1,1) ); 125 | 126 | Debug.Log( $"weights:\t{weights.ToString()}" ); 127 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 128 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 129 | 130 | Assert.AreEqual( minHeap.Pop() , NativeGrid.IndexToCoord(1,weights.Length/2) ); 131 | } 132 | 133 | [Test] public static void Peek_04 () 134 | { 135 | var weights = new NativeArray( 4 , Allocator.Temp ); 136 | weights[0] = (half) 50; 137 | weights[1] = (half) 1; 138 | weights[2] = (half) (-0.1); 139 | weights[3] = (half) 300; 140 | var comparer = new NativeGrid.AStarJob.Comparer( weights.Length/2 ); 141 | var minHeap = new NativeMinHeap( weights.Length , Allocator.Temp , comparer, weights ); 142 | minHeap.Push( new int2(0,0) ); 143 | minHeap.Push( new int2(0,1) ); 144 | minHeap.Push( new int2(1,0) ); 145 | minHeap.Push( new int2(1,1) ); 146 | 147 | Debug.Log( $"weights:\t{weights.ToString()}" ); 148 | Debug.Log( $"min-heap:\t{minHeap.ToString()}" ); 149 | Debug.Log( $"Peek():\t{minHeap.Peek()}" ); 150 | 151 | Assert.AreEqual( minHeap.Pop() , NativeGrid.IndexToCoord(2,weights.Length/2) ); 152 | } 153 | 154 | } 155 | 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Tests/Editor/Test NativeMinHeap.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 098870603b4e0ee46be52d7e2049430f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Test enumerators.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using NUnit.Framework; 4 | using Unity.Mathematics; 5 | using Unity.Collections; 6 | using Unity.Jobs; 7 | 8 | namespace NativeGridNamespace.Tests 9 | { 10 | static class Enumerators 11 | { 12 | static ( int2 coord , int gridWidth , int gridheight , int2[] expected )[] _tests = new ( int2 , int , int , int2[] )[]{ 13 | ( new int2(1,1) , 0 , 0 , new int2[0] ) , 14 | ( new int2(-1,1) , 0 , 0 , new int2[0] ) , 15 | ( new int2(1,-1) , 0 , 0 , new int2[0] ) , 16 | 17 | ( new int2(5,5) , 10 , 10 , new int2[]{ 18 | new int2(4,4) , new int2(5,4) , new int2(6,4) , 19 | new int2(4,5) , new int2(6,5) , 20 | new int2(4,6) , new int2(5,6) , new int2(6,6) 21 | } ) , 22 | 23 | ( new int2(5,5) , 6 , 6 , new int2[]{ 24 | new int2(4,4) , new int2(5,4) , 25 | new int2(4,5) , 26 | } ) , 27 | 28 | ( new int2(0,0) , 10 , 10 , new int2[]{ 29 | new int2(1,0) , 30 | new int2(0,1) , new int2(1,1) 31 | } ) , 32 | 33 | ( new int2(3,0) , 5 , 5 , new int2[]{ 34 | new int2(2,0) , new int2(4,0) , 35 | new int2(2,1) , new int2(3,1) , new int2(4,1) 36 | } ) , 37 | 38 | ( new int2(333,333) , 333 , 333 , new int2[]{ 39 | new int2(332,332) 40 | } ) , 41 | 42 | ( new int2(-1,-1) , 333 , 33 , new int2[]{ 43 | new int2(0,0) 44 | } ) , 45 | 46 | ( new int2(0,-1) , 333 , 33 , new int2[]{ 47 | new int2(0,0) , new int2(1,0) 48 | } ) , 49 | }; 50 | [Test] public static void NeighbourEnumerator__multiple_tests () 51 | { 52 | Debug.Log("test start"); 53 | foreach( var test in _tests ) 54 | { 55 | Debug.Log( $"case: coord: ( {test.coord.x} , {test.coord.y} ) , gridWidth:{test.gridWidth} , gridHeight:{test.gridheight}" ); 56 | 57 | var enumerator = new NativeGrid.NeighbourEnumerator( coord:test.coord , gridWidth:test.gridWidth , gridHeight:test.gridheight ); 58 | var results = new List( capacity:8 ); 59 | while( enumerator.MoveNext(out int2 coord) ) 60 | results.Add(coord); 61 | 62 | { 63 | var sb = new System.Text.StringBuilder("{"); 64 | foreach( int2 coord in results ) 65 | { 66 | sb.AppendFormat(" ({0},{1})",coord.x,coord.y); 67 | sb.Append(" ,"); 68 | } 69 | if( sb[sb.Length-1]==',' ) sb.Remove(sb.Length-1,1); 70 | sb.Append('}'); 71 | Debug.Log($" results: {sb}"); 72 | } 73 | 74 | Debug.Log($" comparing number of results: {results.Count}, expected:{test.expected.Length} ..."); 75 | Assert.AreEqual( expected:test.expected.Length , actual:results.Count ); 76 | Debug.Log(" passed."); 77 | 78 | Debug.Log($" comparing indices..."); 79 | for( int i=0 ; i