├── 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