diff options
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs new file mode 100644 index 0000000..b1994fe --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/TileMeshes.cs @@ -0,0 +1,193 @@ +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + +namespace Pathfinding.Graphs.Navmesh { + /// <summary> + /// Represents a rectangular group of tiles of a recast graph. + /// + /// This is a portable representation in that it can be serialized to and from a byte array. + /// + /// <code> + /// // Scans the first 6x6 chunk of tiles of the recast graph (the IntRect uses inclusive coordinates) + /// var graph = AstarPath.active.data.recastGraph; + /// var buildSettings = RecastBuilder.BuildTileMeshes(graph, new TileLayout(graph), new IntRect(0, 0, 5, 5)); + /// var disposeArena = new Pathfinding.Jobs.DisposeArena(); + /// var promise = buildSettings.Schedule(disposeArena); + /// + /// AstarPath.active.AddWorkItem(() => { + /// // Block until the asynchronous job completes + /// var result = promise.Complete(); + /// TileMeshes tiles = result.tileMeshes.ToManaged(); + /// // Take the scanned tiles and place them in the graph, + /// // but not at their original location, but 2 tiles away, rotated 90 degrees. + /// tiles.tileRect = tiles.tileRect.Offset(new Int2(2, 0)); + /// tiles.Rotate(1); + /// graph.ReplaceTiles(tiles); + /// + /// // Dispose unmanaged data + /// disposeArena.DisposeAll(); + /// result.Dispose(); + /// }); + /// </code> + /// + /// See: <see cref="NavmeshPrefab"/> uses this representation internally for storage. + /// See: <see cref="RecastGraph.ReplaceTiles"/> + /// See: <see cref="RecastBuilder.BuildTileMeshes"/> + /// </summary> + public struct TileMeshes { + /// <summary>Tiles laid out row by row</summary> + public TileMesh[] tileMeshes; + /// <summary>Which tiles in the graph this group of tiles represents</summary> + public IntRect tileRect; + /// <summary>World-space size of each tile</summary> + public Vector2 tileWorldSize; + + /// <summary>Rotate this group of tiles by 90*N degrees clockwise about the group's center</summary> + public void Rotate (int rotation) { + rotation = -rotation; + // Get the positive remainder modulo 4. I.e. a number between 0 and 3. + rotation = ((rotation % 4) + 4) % 4; + if (rotation == 0) return; + var rot90 = new int2x2(0, -1, 1, 0); + var rotN = int2x2.identity; + for (int i = 0; i < rotation; i++) rotN = math.mul(rotN, rot90); + + var tileSize = (Int3) new Vector3(tileWorldSize.x, 0, tileWorldSize.y); + var offset = -math.min(int2.zero, math.mul(rotN, new int2(tileSize.x, tileSize.z))); + var size = new int2(tileRect.Width, tileRect.Height); + var offsetTileCoordinate = -math.min(int2.zero, math.mul(rotN, size - 1)); + var newTileMeshes = new TileMesh[tileMeshes.Length]; + var newSize = (rotation % 2) == 0 ? size : new int2(size.y, size.x); + + for (int z = 0; z < size.y; z++) { + for (int x = 0; x < size.x; x++) { + var vertices = tileMeshes[x + z*size.x].verticesInTileSpace; + for (int i = 0; i < vertices.Length; i++) { + var v = vertices[i]; + var rotated = math.mul(rotN, new int2(v.x, v.z)) + offset; + vertices[i] = new Int3(rotated.x, v.y, rotated.y); + } + + var tileCoord = math.mul(rotN, new int2(x, z)) + offsetTileCoordinate; + newTileMeshes[tileCoord.x + tileCoord.y*newSize.x] = tileMeshes[x + z*size.x]; + } + } + + tileMeshes = newTileMeshes; + tileWorldSize = rotation % 2 == 0 ? tileWorldSize : new Vector2(tileWorldSize.y, tileWorldSize.x); + tileRect = new IntRect(tileRect.xmin, tileRect.ymin, tileRect.xmin + newSize.x - 1, tileRect.ymin + newSize.y - 1); + } + + /// <summary> + /// Serialize this struct to a portable byte array. + /// The data is compressed using the deflate algorithm to reduce size. + /// See: <see cref="Deserialize"/> + /// </summary> + public byte[] Serialize () { + var buffer = new System.IO.MemoryStream(); + var writer = new System.IO.BinaryWriter(new System.IO.Compression.DeflateStream(buffer, System.IO.Compression.CompressionMode.Compress)); + // Version + writer.Write(0); + writer.Write(tileRect.Width); + writer.Write(tileRect.Height); + writer.Write(this.tileWorldSize.x); + writer.Write(this.tileWorldSize.y); + for (int z = 0; z < tileRect.Height; z++) { + for (int x = 0; x < tileRect.Width; x++) { + var tile = tileMeshes[(z*tileRect.Width) + x]; + UnityEngine.Assertions.Assert.IsTrue(tile.tags.Length*3 == tile.triangles.Length); + writer.Write(tile.triangles.Length); + writer.Write(tile.verticesInTileSpace.Length); + for (int i = 0; i < tile.verticesInTileSpace.Length; i++) { + var v = tile.verticesInTileSpace[i]; + writer.Write(v.x); + writer.Write(v.y); + writer.Write(v.z); + } + for (int i = 0; i < tile.triangles.Length; i++) writer.Write(tile.triangles[i]); + for (int i = 0; i < tile.tags.Length; i++) writer.Write(tile.tags[i]); + } + } + writer.Close(); + return buffer.ToArray(); + } + + /// <summary> + /// Deserialize an instance from a byte array. + /// See: <see cref="Serialize"/> + /// </summary> + public static TileMeshes Deserialize (byte[] bytes) { + var reader = new System.IO.BinaryReader(new System.IO.Compression.DeflateStream(new System.IO.MemoryStream(bytes), System.IO.Compression.CompressionMode.Decompress)); + var version = reader.ReadInt32(); + if (version != 0) throw new System.Exception("Invalid data. Unexpected version number."); + var w = reader.ReadInt32(); + var h = reader.ReadInt32(); + var tileSize = new Vector2(reader.ReadSingle(), reader.ReadSingle()); + if (w < 0 || h < 0) throw new System.Exception("Invalid bounds"); + + var tileRect = new IntRect(0, 0, w - 1, h - 1); + + var tileMeshes = new TileMesh[w*h]; + for (int z = 0; z < h; z++) { + for (int x = 0; x < w; x++) { + int[] tris = new int[reader.ReadInt32()]; + Int3[] vertsInTileSpace = new Int3[reader.ReadInt32()]; + uint[] tags = new uint[tris.Length/3]; + + for (int i = 0; i < vertsInTileSpace.Length; i++) vertsInTileSpace[i] = new Int3(reader.ReadInt32(), reader.ReadInt32(), reader.ReadInt32()); + for (int i = 0; i < tris.Length; i++) { + tris[i] = reader.ReadInt32(); + UnityEngine.Assertions.Assert.IsTrue(tris[i] >= 0 && tris[i] < vertsInTileSpace.Length); + } + for (int i = 0; i < tags.Length; i++) tags[i] = reader.ReadUInt32(); + + tileMeshes[x + z*w] = new TileMesh { + triangles = tris, + verticesInTileSpace = vertsInTileSpace, + tags = tags, + }; + } + } + return new TileMeshes { + tileMeshes = tileMeshes, + tileRect = tileRect, + tileWorldSize = tileSize, + }; + } + } + + /// <summary>Unsafe representation of a <see cref="TileMeshes"/> struct</summary> + public struct TileMeshesUnsafe { + public NativeArray<TileMesh.TileMeshUnsafe> tileMeshes; + public IntRect tileRect; + public Vector2 tileWorldSize; + + public TileMeshesUnsafe(NativeArray<TileMesh.TileMeshUnsafe> tileMeshes, IntRect tileRect, Vector2 tileWorldSize) { + this.tileMeshes = tileMeshes; + this.tileRect = tileRect; + this.tileWorldSize = tileWorldSize; + } + + /// <summary>Copies the native data to managed data arrays which are easier to work with</summary> + public TileMeshes ToManaged () { + var output = new TileMesh[tileMeshes.Length]; + for (int i = 0; i < output.Length; i++) { + output[i] = tileMeshes[i].ToManaged(); + } + return new TileMeshes { + tileMeshes = output, + tileRect = this.tileRect, + tileWorldSize = this.tileWorldSize, + }; + } + + public void Dispose () { + // Allows calling Dispose on zero-initialized instances + if (!tileMeshes.IsCreated) return; + + for (int i = 0; i < tileMeshes.Length; i++) tileMeshes[i].Dispose(); + tileMeshes.Dispose(); + } + } +} |