/// <summary> Traces path using some kind of A* algorithm </summary> /// <param name="start"> Start index 2d </param> /// <param name="destination"> Destination index 2d </param> /// <param name="moveCost"> Move cost data 2d array </param> /// <param name="moveCost_width"> 2d array's width </param> /// <param name="heuristic_cost"> Cost heuristic multiplier. Figure out yourself what value works best for your specific moveCost data </param> /// <param name="heuristic_search"> Search heuristic multiplier. Figure out yourself what value works best for your specific moveCost data </param> /// <param name="output_path"> Resulting path goes here </param> public unsafe AStarJob ( INT2 start, INT2 destination, NativeArray <float> moveCost, int moveCost_width, float heuristic_cost, float heuristic_search, NativeList <int2> output_path ) { this.start = start; this.destination = destination; this.moveCost = moveCost; this.moveCost_width = moveCost_width; this.Results = output_path; this.heuristic_cost = heuristic_cost; this.heuristic_search = heuristic_search; int length = moveCost.Length; int start1d = Index2dTo1d(start, moveCost_width); _F_ = new NativeArray <float>(length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); solution = new NativeArray <int2>(length, Allocator.TempJob); frontier = new NativeMinHeap <int2, AStarJobComparer>( new AStarJobComparer(_F_, moveCost_width, destination, heuristic_search), Allocator.TempJob, length ); visited = new NativeHashMap <int2, byte>(length, Allocator.TempJob); //TODO: use actual hashSet once available neighbours = new NativeList <int2>(8, Allocator.TempJob); }
public string ToFixedSizeString() { StringBuilder sb = new StringBuilder(); sb.Append(ID.ToString().PadLeft(10, '0')); sb.Append('^'); sb.Append(INT1.ToString().PadLeft(10, '0')); sb.Append('^'); sb.Append(INT2.ToString().PadLeft(10, '0')); sb.Append('^'); sb.Append(INT3.ToString().PadLeft(10, '0')); sb.Append('^'); sb.Append(DT1.PadLeft(25, '#')); sb.Append('^'); sb.Append(DT2.PadLeft(25, '#')); sb.Append('^'); sb.Append(DT3.PadLeft(25, '#')); sb.Append('^'); sb.Append(VAR1.PadLeft(100, '#')); sb.Append('^'); sb.Append(VAR2.PadLeft(100, '#')); sb.Append('^'); sb.Append(VAR3.PadLeft(100, '#')); return(sb.ToString()); }
public AStarJobComparer(NativeArray <float> _G_, int weightsWidth, INT2 destination, float heuristic_search) { this._F_Ptr = NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(_G_); this._width = weightsWidth; this._dest = destination; this.heuristic_search = heuristic_search; }
/// <summary> Gets the surrounding field values </summary> /// <returns> /// 8-bit clockwise-enumerated bit values /// 7 0 1 [x-1,y+1] [x,y+1] [x+1,y+1] /// 6 ^ 2 == [x-1,y] [x,y] [x+1,y] /// 5 4 3 [x-1,y-1] [x,y-1] [x+1,y-1] /// 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 /// </returns> public byte GetMarchingSquares(INT2 index2d, System.Predicate <STRUCT> predicate) { int x = index2d.x; int y = index2d.y; const byte zero = 0b_0000_0000; byte result = zero; //out of bounds test: bool xPlus = x + 1 < Width; bool yPlus = y + 1 < Height; bool xMinus = x - 1 >= 0; bool yMinus = y - 1 >= 0; //top, down: result |= yPlus && predicate(this[x, y + 1]) ? (byte)0b_0000_0001 : zero; result |= yMinus && predicate(this[x, y - 1]) ? (byte)0b_0001_0000 : zero; //right side: result |= xPlus && yPlus && predicate(this[x + 1, y + 1]) ? (byte)0b_0000_0010 : zero; result |= xPlus && predicate(this[x + 1, y]) ? (byte)0b_0000_0100 : zero; result |= xPlus && yMinus && predicate(this[x + 1, y - 1]) ? (byte)0b_0000_1000 : zero; //left side: result |= xMinus && yPlus && predicate(this[x - 1, y + 1]) ? (byte)0b_0010_0000 : zero; result |= xMinus && predicate(this[x - 1, y]) ? (byte)0b_0000_0100 : zero; result |= xMinus && yMinus && predicate(this[x - 1, y - 1]) ? (byte)0b_1000_0000 : zero; return(result); }
/// <summary> Translate regional coordinate to outer array index 1d </summary> /// <param name="R">Outer RectInt</param> /// <param name="r">Inner, smaller RectInt</param> /// <param name="rx">Inner x coordinate</param> /// <param name="ry">Inner y coordinate</param> /// <param name="R_width">Outer RectInt.width</param> public static int2 IndexTranslate(RectInt r, INT2 rxy) { Assert_IndexTranslate(r, rxy.x, rxy.y); return(new int2 { x = r.x, y = r.y } +(int2)rxy); }
public void TrackVisibleChunks(GameContext gameContext) { INT3 aPos = playerInfo.positionI.AlignedDown(16); for (int x = -128; x < 144; x += 16) { for (int z = -128; z < 144; z += 16) { INT2 testPoint = new INT2(x + aPos.x, z + aPos.z); if (gameContext.regionInfo.TryGetValue(testPoint, out var regionInfo)) { for (int y = regionInfo.m_minHeight; y < regionInfo.m_maxHeight; y += 16) { var chunkPosition = new INT3(aPos.x + x, y, aPos.z + z); //var chunk1 = gameCore.GetExistChunk(chunkPosition.x, chunkPosition.y, chunkPosition.z); //if (playerInfo.visibleChunks.TryGetValue(chunkPosition, out var timeStamp1)) //{ // if (chunk1 != null && chunk1.stamp > timeStamp1) // { // playerInfo.visibleChunks[chunkPosition] = chunk1.stamp; // NetBlock netBlock = netContext.GetNetBlock(1802856547/*'kuhc'*/, Chunk.c_minimumSize); // GUtility.ToNetBlock(chunk1, netBlock); // wrapLayout.SendNetBlock(netBlock); // } //} //else if (chunk1 != null) //{ // playerInfo.visibleChunks[chunkPosition] = chunk1.stamp; // NetBlock netBlock = netContext.GetNetBlock(1802856547/*'kuhc'*/, Chunk.c_minimumSize); // GUtility.ToNetBlock(chunk1, netBlock); // wrapLayout.SendNetBlock(netBlock); //} } playerInfo.visibleRegions[testPoint] = regionInfo.m_recentUpdateTimeStamp; } } } List <INT3> chunkCullResult = new List <INT3>(); foreach (var pair in playerInfo.visibleChunks) { INT3 a = pair.Key - playerInfo.positionI + new INT3(8, 8, 8); if (Math.Abs(a.x) + Math.Abs(a.z) > 512) { chunkCullResult.Add(pair.Key); } } for (int j = 0; j < chunkCullResult.Count; j++) { playerInfo.visibleChunks.Remove(chunkCullResult[j]); } }
public static void PointToIndex2d_Test_1x1(float2 a, float2 b) { float2 worldSize = new float2 { x = 2f, y = 1f }; const int width = 1, height = 1; INT2 A = NativeGrid.PointToIndex2d(a, worldSize, width, height); INT2 B = NativeGrid.PointToIndex2d(b, worldSize, width, height); // Debug.Log( $"a:{GetPositionInsideCell_GetDebugString(a,width,height,worldSize)}\nb:{GetPositionInsideCell_GetDebugString(b,width,height,worldSize)}" ); Assert.AreEqual(A, B); }
static void PointToIndex2d_Test(float2 a, float2 b) { float2 worldSize = new float2(3000f, 3000f); float2 gridOrigin = new float2(-1500f, -1500f); const int width = 1500, height = 1500; INT2 A = NativeGrid.PointToIndex2d(a - gridOrigin, worldSize, width, height); INT2 B = NativeGrid.PointToIndex2d(b - gridOrigin, worldSize, width, height); // Debug.Log( $"a:{GetPositionInsideCell_GetDebugString(a,width,height,worldSize)}\nb:{GetPositionInsideCell_GetDebugString(b,width,height,worldSize)}" ); Assert.AreEqual(A, B); }
public void FillRegion(int x, int z, GameContext gameContext) { INT2 position = new INT2(x, z); for (int i = -2; i < 4; i++) { GetChunk(x, i * 16, z, gameContext); } var regionInfo = GetRegionInfo(x, z, gameContext); regionInfo.m_generatorFilled = true; gameContext.regionInfo[position] = regionInfo; }
void ExpandMap() { foreach (var playerInfo in gameContext.playerInfos.Values) { INT3 p = playerInfo.positionI; INT2 p1 = new INT2(p.x, p.z).AlignedDown(64); for (int x = -192; x < 256; x += 64) { for (int y = -192; y < 256; y += 64) { cover64.Add(new INT2(p1.x + x, p1.y + y)); } } } foreach (var p64 in cover64) { int filledCount = 0; if (gameContext.regionInfo64.TryGetValue(p64, out var regionInfo64)) { if (regionInfo64.m_generatorFilled) { continue; } } else { regionInfo64 = new RegionInfo(); gameContext.regionInfo64[p64] = regionInfo64; } for (int x = 0; x < 64; x += 16) { for (int y = 0; y < 64; y += 16) { INT2 position = new INT2(p64.x + x, p64.y + y); if (!gameContext.regionInfo.TryGetValue(position, out var regionInfo) || !regionInfo.m_generatorFilled) { chunkGenerator.FillRegion(position.x, position.y, gameContext); } else { filledCount++; } } } if (filledCount == 16) { regionInfo64.m_generatorFilled = true; } } cover64.Clear(); }
public static void PointToIndex2d_Test_2x2(float2 a, float2 b, bool equalityTest = true) { float2 worldSize = new float2(2f, 2f); const int width = 2, height = 2; INT2 A = NativeGrid.PointToIndex2d(a, worldSize, width, height); INT2 B = NativeGrid.PointToIndex2d(b, worldSize, width, height); // Debug.Log( $"a:{GetPositionInsideCell_GetDebugString(a,width,height,worldSize)}\nb:{GetPositionInsideCell_GetDebugString(b,width,height,worldSize)}" ); if (equalityTest) { Assert.AreEqual(A, B); } else { Assert.AreNotEqual(A, B); } }
public static void GetPositionInsideCell ( FLOAT2 point, INT2 numCells, FLOAT2 worldSize, out int2 lowerCell, out int2 upperCell, out float2 normalizedPositionBetweenThoseTwoPoints ) { GetPositionInsideCell(point.x, numCells.x, worldSize.x, out int xlo, out int xhi, out float xf); GetPositionInsideCell(point.y, numCells.y, worldSize.y, out int ylo, out int yhi, out float yf); lowerCell = new int2 { x = xlo, y = ylo }; upperCell = new int2 { x = xhi, y = yhi }; normalizedPositionBetweenThoseTwoPoints = new float2 { x = xf, y = yf }; }
/// <summary> Finds sequence of indices (a path) for given AStar solution </summary> /// <returns> Was destination reached </returns> public static bool BacktrackToPath ( NativeArray <int2> solution, INT solutionWidth, INT2 destination, NativeList <int2> path ) { path.Clear(); if (path.Capacity < solutionWidth * 2) { path.Capacity = solutionWidth * 2; //preallocate for reasonably common/bad scenario } int solutionLength = solution.Length; int2 pos = destination; int pos1d = Index2dTo1d(pos, solutionWidth); int step = 0; while ( math.any(pos != solution[pos1d]) && step < solutionLength ) { path.Add(pos); pos = solution[pos1d]; pos1d = Index2dTo1d(pos, solutionWidth); step++; } bool wasDestinationReached = math.all(pos == solution[pos1d]); // TODO: can this step be avoided? ReverseArray <int2>(path); return(wasDestinationReached); }
public static int2 ClampIndex2d(INT2 i2, INT width, INT height) => NativeGrid.ClampIndex2d(i2, width, height);
/// <summary> Bresenham's line drawing algorithm (https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm). </summary> public static void TraceLine(NativeList <int2> results, INT2 A, INT2 B) { results.Clear(); { int2 dir = math.abs(A - B); int capacity = math.max(dir.x, dir.y); if (results.Capacity < capacity) { results.Capacity = capacity; } } int2 pos = A; int d, dx, dy, ai, bi, xi, yi; if (A.x < B.x) { xi = 1; dx = B.x - A.x; } else { xi = -1; dx = A.x - B.x; } if (A.y < B.y) { yi = 1; dy = B.y - A.y; } else { yi = -1; dy = A.y - B.y; } results.Add(pos); if (dx > dy) { ai = (dy - dx) * 2; bi = dy * 2; d = bi - dx; while (pos.x != B.x) { if (d >= 0) { pos.x += xi; pos.y += yi; d += ai; } else { d += bi; pos.x += xi; } results.Add(pos); } } else { ai = (dx - dy) * 2; bi = dx * 2; d = bi - dy; while (pos.y != B.y) { if (d >= 0) { pos.x += xi; pos.y += yi; d += ai; } else { d += bi; pos.y += yi; } results.Add(pos); } } }
public static void EnumerateNeighbours(NativeArray <int2> results, int width, int height, INT2 origin2) { #if DEBUG ASSERT_TRUE(results.Length == 8, "Array length must be 8"); #endif int2 origin2d = origin2; int2 o2 = int2.zero; int i = 0; //move 1 step up: o2 += new int2 { y = 1 }; results[i++] = ClampIndex2d(origin2d + o2, width, height); //move 1 step right: o2.x++; results[i++] = ClampIndex2d(origin2d + o2, width, height); //move 2 steps down: o2.y--; results[i++] = ClampIndex2d(origin2d + o2, width, height); o2.y--; results[i++] = ClampIndex2d(origin2d + o2, width, height); //move 2 steps left: o2.x--; results[i++] = ClampIndex2d(origin2d + o2, width, height); o2.x--; results[i++] = ClampIndex2d(origin2d + o2, width, height); //move 2 steps up: o2.y++; results[i++] = ClampIndex2d(origin2d + o2, width, height); o2.y++; results[i++] = ClampIndex2d(origin2d + o2, width, height); }
/// <summary> Enumerates neighbours in growing spiral pattern (clockwise). </summary> /// <note> Offsets will repeat for border cells due to clamping (make sure this breaks nothing). </note> /// <note> Range > 1 will produce spiral pattern </note> /// <returns> Enumeration of index 2d coordinates to form this spiral. </returns> public static void EnumerateNeighbours(NativeList <int2> results, int width, int height, INT2 origin2, int range = 1) { // .. 24 25 26 27 28 29 // 46 23 8 9 10 11 30 // 45 22 7 0 1 12 31 // 44 21 6 ^ 2 13 32 // 43 20 5 4 3 14 33 // 42 19 18 17 16 15 34 // 41 40 39 38 37 36 35 results.Clear(); { int capacity = (range * 2 + 1) * (range * 2 + 1) - 1; if (results.Capacity < capacity) { results.Capacity = capacity; } } int2 origin2d = origin2; int2 o2 = int2.zero; for (int ir = 1; ir <= range; ir++) { //move 1 step up: o2 += new int2 { y = 1 }; results.Add(ClampIndex2d(origin2d + o2, width, height)); //move right: for (int imr = 0; imr < (ir * 2) - 1; imr++) { o2.x++; results.Add(ClampIndex2d(origin2d + o2, width, height)); } //move down: for (int imd = 0; imd < ir * 2; imd++) { o2.y--; results.Add(ClampIndex2d(origin2d + o2, width, height)); } //move left: for (int iml = 0; iml < ir * 2; iml++) { o2.x--; results.Add(ClampIndex2d(origin2d + o2, width, height)); } //move up: for (int imu = 0; imu < ir * 2; imu++) { o2.y++; results.Add(ClampIndex2d(origin2d + o2, width, height)); } } }
public static bool IsIndex2dValid(INT2 i2, INT w, INT h) => NativeGrid.IsIndex2dValid(i2, w, h);
public static int2 IndexTranslate(RectInt r, INT2 rxy) => NativeGrid.IndexTranslate(r, rxy);
/// <summary> Finds sequence of indices (a path) for given AStar solution </summary> /// <remarks> Uses segmented array for output </remarks> /// <returns> Was destination reached </returns> public static bool BacktrackToPath ( NativeArray <int2> solvedGrid, INT solvedGridWidth, INT2 destination, NativeArray <int2> segmentedIndices, // array segmented to store multiple paths INT segmentStart, // position for first path index2d INT segmentEnd, // position for last path index2d out int pathLength ) { #if UNITY_ASSERTIONS ASSERT_TRUE(destination.x >= 0 && destination.y >= 0, $"destination: {destination} >= 0"); ASSERT_TRUE(destination.x < solvedGridWidth && destination.y < solvedGridWidth, $"destination: {destination} < {solvedGridWidth} solutionWidth"); #endif int2 pos = destination; int pos1d = Index2dTo1d(pos, solvedGridWidth); int availableSpace = segmentEnd - segmentStart; int step = 0; #if UNITY_ASSERTIONS localAssertions(); #endif while ( math.any(pos != solvedGrid[pos1d]) && step < availableSpace ) { int segmentedArrayIndex = segmentStart + step; #if UNITY_ASSERTIONS if (segmentedArrayIndex < segmentStart || segmentedArrayIndex > segmentEnd) { // throw new System.Exception Debug.LogError($"segmentedArrayIndex {segmentedArrayIndex} is outside it's range of {{{segmentStart}...{segmentEnd}}}"); } #endif segmentedIndices[segmentedArrayIndex] = pos; pos = solvedGrid[pos1d]; pos1d = Index2dTo1d(pos, solvedGridWidth); #if UNITY_ASSERTIONS localAssertions(); #endif step++; } pathLength = step; bool wasDestinationReached = math.all(pos == solvedGrid[pos1d]); #if UNITY_ASSERTIONS for (int n = 0; n < pathLength; n++) { int index = segmentStart + n; if (math.all(segmentedIndices[index] == int2.zero)) { // throw new System.Exception Debug.LogError($"segmentedIndices[{index}] is {segmentedIndices[index]}, segmentStart: {segmentStart}, segmentEnd: {segmentEnd}, pathLength: {pathLength}, step: {step}"); } } #endif // TODO: can this step be avoided? // reverse path order: { int first = segmentStart; int last = segmentStart + math.max(pathLength - 1, 0); #if UNITY_ASSERTIONS if (last > segmentEnd) { // throw new System.Exception Debug.LogError($"last {last} > {segmentEnd} segmentEnd"); } #endif ReverseArraySegment(segmentedIndices, first, last); } #if UNITY_ASSERTIONS void localAssertions() { FixedString128 debugInfo = $"pos: {pos}, pos1d:{pos1d}, solution.Length:{solvedGrid.Length}, solutionWidth:{solvedGridWidth} squared: {solvedGridWidth}"; ASSERT_TRUE(pos1d >= 0, debugInfo); ASSERT_TRUE(pos1d < solvedGrid.Length, debugInfo); } #endif return(wasDestinationReached); }
public static float EuclideanHeuristicNormalized(INT2 a, INT2 b, float maxLength) => math.length(a - b) / maxLength;
public static float EuclideanHeuristic(INT2 a, INT2 b) => math.length(a - b);
public static int2 ClampIndex2d(INT2 i2, INT width, INT height) => ClampIndex2d(i2.x, i2.y, width, height);
public static float2 Index2dToPoint(INT2 i2, FLOAT stepX, FLOAT stepY) => Index2dToPoint(i2.x, i2.y, stepX, stepY);
public static int Index2dTo1d(INT2 i2, INT width) => Index2dTo1d(i2.x, i2.y, width);
public static float2 Index2dToPoint(INT2 i2, FLOAT2 step) => NativeGrid.Index2dToPoint(i2, step);
public static bool IsIndex2dValid(INT2 i2, INT w, INT h) => IsIndex2dValid(i2.x, i2.y, w, h);
public static void GetPositionInsideCell(FLOAT2 point, INT2 numCells, FLOAT2 worldSize, out int2 lowerCell, out int2 upperCell, out float2 normalizedPositionBetweenThoseTwoPoints) => NativeGrid.GetPositionInsideCell(point, numCells, worldSize, out lowerCell, out upperCell, out normalizedPositionBetweenThoseTwoPoints);
public static float2 Index2dToPoint(INT2 i2, FLOAT2 step) => Index2dToPoint(i2.x, i2.y, step.x, step.y);
public static float2 Index2dToPoint(INT2 i2, FLOAT stepX, FLOAT stepY) => NativeGrid.Index2dToPoint(i2, stepX, stepY);