public void CombineTerrainTileMeshes(List <TerrainTileMesh> tiles)
        {
            vertices.Clear();
            normals.Clear();
            uvs.Clear();
            triangles.Clear();

            int triangleOffset = 0;

            for (int i = 0; i < tiles.Count; i++)
            {
                TerrainTileMesh tile              = tiles[i];
                int             tileVertexCount   = tile.vertices.Count;
                int             tileTriangleCount = tile.triangles.Count;

                for (int j = 0; j < tileVertexCount; j++)
                {
                    vertices.Add(tile.vertices[j]);
                    normals.Add(tile.normals[j]);
                    uvs.Add(tile.uvs[j]);
                }

                for (int t = 0; t < tileTriangleCount; t++)
                {
                    triangles.Add(tile.triangles[t] + triangleOffset);
                }

                triangleOffset += tileVertexCount;
            }
        }
        // Start is called before the first frame update
        void Start()
        {
            // Initialise variables
            tileSize          = size / numTiles;
            prevPlayerTilePos = Vector2Int.one * int.MaxValue;
            map = new Map(mapSeed, size, numTiles, tileResolution, heightmapProvider);

            // Create mesh renderers and filters
            int numLODs = lodDistances.Length;

            lodGameObjects          = new List <GameObject> [numLODs];
            tileMeshes              = new TerrainTileMesh[numTiles, numTiles];
            tileLODs                = new int[numTiles, numTiles];
            tileEdgeTransitionFlags = new int[numTiles, numTiles];
            lodMeshes               = new List <CombinedTerrainTileMesh> [numLODs];
            meshRenderers           = new List <MeshRenderer> [numLODs];
            meshFilters             = new List <MeshFilter> [numLODs];

            // Create LOD meshes
            for (int i = 0; i < numLODs; i++)
            {
                lodGameObjects[i] = new List <GameObject>(8);
                lodMeshes[i]      = new List <CombinedTerrainTileMesh>(8);
                meshRenderers[i]  = new List <MeshRenderer>(8);
                meshFilters[i]    = new List <MeshFilter>(8);

                for (int m = 0; m < 8; m++)
                {
                    lodGameObjects[i].Add(new GameObject());
                    lodGameObjects[i][m].transform.parent = transform;
                    lodGameObjects[i][m].name             = "LOD " + i + " mesh " + m;
                    lodGameObjects[i][m].layer            = LayerMask.NameToLayer(terrainLayer);

                    CombinedMeshType meshType = CombinedMeshType.Corner;

                    if (i == 0)
                    {
                        meshType = CombinedMeshType.Center;
                    }
                    else if (m > 3)
                    {
                        meshType = CombinedMeshType.Edge;
                    }

                    int prevLODDistance = (i == 0) ? 0 : lodDistances[i - 1];

                    lodMeshes[i].Add(new CombinedTerrainTileMesh(meshType, tileResolution / MathUtilities.Pow(2, i), lodDistances[i], Mathf.Max(0, lodDistances[i] - prevLODDistance)));

                    meshRenderers[i].Add(lodGameObjects[i][m].AddComponent(typeof(MeshRenderer)) as MeshRenderer);
                    meshRenderers[i][m].sharedMaterial = terrainMaterial;

                    meshFilters[i].Add(lodGameObjects[i][m].AddComponent(typeof(MeshFilter)) as MeshFilter);
                    meshFilters[i][m].mesh = lodMeshes[i][m].mesh;
                }
            }

            // Create tile objects
            for (int x = 0; x < numTiles; x++)
            {
                for (int z = 0; z < numTiles; z++)
                {
                    tileLODs[x, z]   = -1;
                    tileMeshes[x, z] = new TerrainTileMesh(tileResolution);
                }
            }

            // Initialise heightmap provider
            heightmapProvider.Init(mapSeed, size);

            // Start mesh updater thread
            ThreadStart MeshUpdaterThreadStart = MeshUpdateThread;

            meshUpdaterThread      = new Thread(MeshUpdaterThreadStart);
            meshUpdateThreadStatus = (int)MeshUpdateThreadStatus.Idle;
            meshUpdaterThread.Start();
        }
        public static void GenerateTile(TerrainTileMesh tileMesh, Vector2Int tilePosition, float tileSize, int lod0Resolution, int lod, float[,] heightMap, int edgeTransitionFlags)
        {
            tileMesh.Clear();

            if (lod == -1)
            {
                return;
            }

            int heightmapStep = MathUtilities.Pow(2, lod);

            int planeResolution = lod0Resolution / heightmapStep;

            float positionStep = tileSize / planeResolution;
            float uvStep       = 1.0f / planeResolution;

            Vector3 tileOrigin = new Vector3(tilePosition.x * tileSize, 0.0f, tilePosition.y * tileSize);

            Vector3 vertex = new Vector3();
            Vector3 normal = new Vector3(0.0f, 1.0f, 0.0f);
            Vector2 uv     = Vector3.zero;

            float leftHeight, rightHeight, topHeight, bottomHeight;

            // Create vertices
            for (int z = 0, heightmapZ = 1; z <= planeResolution; z++, heightmapZ += heightmapStep)
            {
                for (int x = 0, heightmapX = 1; x <= planeResolution; x++, heightmapX += heightmapStep)
                {
                    vertex = tileOrigin;

                    vertex.x += x * positionStep;
                    vertex.y += heightMap[heightmapX, heightmapZ];
                    vertex.z += z * positionStep;

                    /*leftHeight = heightMap[heightmapX - heightmapStep, heightmapZ];
                     * rightHeight = heightMap[heightmapX + heightmapStep, heightmapZ];
                     * topHeight = heightMap[heightmapX, heightmapZ + heightmapStep];
                     * bottomHeight = heightMap[heightmapX, heightmapZ - heightmapStep];*/

                    leftHeight   = heightMap[heightmapX - 1, heightmapZ];
                    rightHeight  = heightMap[heightmapX + 1, heightmapZ];
                    topHeight    = heightMap[heightmapX, heightmapZ + 1];
                    bottomHeight = heightMap[heightmapX, heightmapZ - 1];

                    Vector3 tangent   = new Vector3(2.0f, rightHeight - leftHeight, 0.0f);
                    Vector3 bitangent = new Vector3(0.0f, bottomHeight - topHeight, 2.0f);
                    normal = Vector3.Cross(tangent, bitangent).normalized;

                    //normal = (new Vector3(2.0f * (rightHeight - leftHeight), 2.0f * (topHeight - bottomHeight), -4.0f).normalized);

                    uv.x = x * uvStep;
                    uv.y = z * uvStep;

                    tileMesh.vertices.Add(vertex);
                    tileMesh.normals.Add(normal);
                    tileMesh.uvs.Add(uv);
                }
            }

            // Update seams
            if ((edgeTransitionFlags & (int)TileEdgeTransitionFlags.Top) == (int)TileEdgeTransitionFlags.Top)
            {
                for (int x = 1, vert = ((planeResolution + 1) * planeResolution) + 1; x < planeResolution; x += 2, vert += 2)
                {
                    tileMesh.vertices[vert] = new Vector3(tileMesh.vertices[vert].x, Mathf.Lerp(tileMesh.vertices[vert - 1].y, tileMesh.vertices[vert + 1].y, 0.5f), tileMesh.vertices[vert].z);
                }
            }

            if ((edgeTransitionFlags & (int)TileEdgeTransitionFlags.Bottom) == (int)TileEdgeTransitionFlags.Bottom)
            {
                for (int x = 1, vert = 1; x < planeResolution; x += 2, vert += 2)
                {
                    tileMesh.vertices[vert] = new Vector3(tileMesh.vertices[vert].x, Mathf.Lerp(tileMesh.vertices[vert - 1].y, tileMesh.vertices[vert + 1].y, 0.5f), tileMesh.vertices[vert].z);
                }
            }

            if ((edgeTransitionFlags & (int)TileEdgeTransitionFlags.Left) == (int)TileEdgeTransitionFlags.Left)
            {
                for (int z = 1, vert = planeResolution + 1; z < planeResolution; z += 2, vert += (planeResolution + 1) * 2)
                {
                    tileMesh.vertices[vert] = new Vector3(tileMesh.vertices[vert].x, Mathf.Lerp(tileMesh.vertices[vert - (planeResolution + 1)].y, tileMesh.vertices[vert + (planeResolution + 1)].y, 0.5f), tileMesh.vertices[vert].z);
                }
            }

            if ((edgeTransitionFlags & (int)TileEdgeTransitionFlags.Right) == (int)TileEdgeTransitionFlags.Right)
            {
                for (int z = 1, vert = (planeResolution * 2) + 1; z < planeResolution; z += 2, vert += (planeResolution + 1) * 2)
                {
                    tileMesh.vertices[vert] = new Vector3(tileMesh.vertices[vert].x, Mathf.Lerp(tileMesh.vertices[vert - (planeResolution + 1)].y, tileMesh.vertices[vert + (planeResolution + 1)].y, 0.5f), tileMesh.vertices[vert].z);
                }
            }

            // Calculate indices
            for (int ti = 0, vi = 0, y = 0; y < planeResolution; y++, vi++)
            {
                for (int x = 0; x < planeResolution; x++, ti += 6, vi++)
                {
                    tileMesh.triangles.Add(vi);
                    tileMesh.triangles.Add(vi + planeResolution + 1);
                    tileMesh.triangles.Add(vi + 1);
                    tileMesh.triangles.Add(vi + 1);
                    tileMesh.triangles.Add(vi + planeResolution + 1);
                    tileMesh.triangles.Add(vi + planeResolution + 2);
                }
            }
        }