/// <summary>
    /// Copy tile data from v1.x tile system to v2.x tile system.
    /// </summary>
    /// <param name="v1">Old tile system.</param>
    /// <param name="v2">New tile system.</param>
    private static void CopyTileData(MonoBehaviour v1, TileSystem v2)
    {
        RtsUpgradedBrushMap map = RtsBrushUpgradeUtility.BrushMappings;

        FieldInfo fiFlagsV1 = (_tyTileData != null)
                        ? _tyTileData.GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance)
                        : null;
        FieldInfo fiFlagsV2 = typeof(TileData).GetField("_flags", BindingFlags.NonPublic | BindingFlags.Instance);

        TileData newTile = new TileData();

        int rows    = (int)_fiTileSystem_rows.GetValue(v1);
        int columns = (int)_fiTileSystem_columns.GetValue(v1);

        for (int row = 0; row < rows; ++row)
        {
            for (int column = 0; column < columns; ++column)
            {
                object oldTile = _miTileSystem_GetTile.Invoke(v1, new object[] { row, column });
                if (oldTile == null)
                {
                    continue;
                }

                if (fiFlagsV1 != null)
                {
                    fiFlagsV2.SetValue(newTile, fiFlagsV1.GetValue(oldTile));
                }

                // Prepare new tile from old tile.
                newTile.brush           = map.Lookup((Object)_fiTileData_brush.GetValue(oldTile));
                newTile.gameObject      = (GameObject)_fiTileData_gameObject.GetValue(oldTile);
                newTile.orientationMask = (byte)_fiTileData_orientationMask.GetValue(oldTile);
                newTile.variationIndex  = (byte)(int)_fiTileData_variationIndex.GetValue(oldTile);
                newTile.Empty           = false;

                v2.SetTileFrom(row, column, newTile);
                Chunk chunk = v2.GetChunkFromTileIndex(row, column);

                ForceRepaintForAtlasTiles(newTile, chunk);

                if (newTile.gameObject != null)
                {
                    // Transfer ownership of attached game object.
                    newTile.gameObject.transform.parent = chunk.transform;
                }
            }
        }

        // Some tiles might need to be refreshed!
        v2.RefreshAllTiles(
            RefreshFlags.PreservePaintedFlags
            | RefreshFlags.PreserveTransform
            | RefreshFlags.UpdateProcedural
            );
    }
    /// <summary>
    /// Upgrade tile system from v1.0.0-v1.0.8 to v2.0.0.
    /// </summary>
    /// <remarks>
    /// <para>Replicates upgrade process that was included in v1.0.9+ but converts straight
    /// to v2.0.0 instead of v1.0.9.</para>
    /// </remarks>
    /// <param name="v1">Old tile system.</param>
    public static void UpgradeTileSystemA(MonoBehaviour v1)
    {
        RtsUpgradedBrushMap map = RtsBrushUpgradeUtility.BrushMappings;

        EditorUtility.DisplayProgressBar("Upgrade Tile System", "Initializing new data structure...", 0.0f);
        try {
            PropertyInfo piTileData_hasGameObject = typeof(TileData).GetProperty("HasGameObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            Vector3 tileSize = (Vector3)_fiTileSystem_tileSize.GetValue(v1);
            int     rows     = (int)_fiTileSystem_rows.GetValue(v1);
            int     columns  = (int)_fiTileSystem_columns.GetValue(v1);

            // Create v2.x tile system.
            TileSystem v2 = v1.gameObject.AddComponent <TileSystem>();
            v2.CreateSystem(tileSize.x, tileSize.y, tileSize.z, rows, columns, 30, 30);
            CopyProperties(v1, v2);

            // Assume value that was consistent with original default settings
            v2.applyRuntimeStripping = true;
            v2.StrippingPreset       = StrippingPreset.NoStripping;
            v2.BeginBulkEdit();

            Component[] instanceComponents = v1.GetComponentsInChildren(_tyTileInstance, true);

            float task      = 0.0f;
            float taskCount = instanceComponents.Length;
            float taskRatio = 1.0f / taskCount;

            TileData tile = new TileData();

            // Retrieve all tile instance components
            foreach (MonoBehaviour instance in instanceComponents)
            {
                EditorUtility.DisplayProgressBar("Upgrade Tile System", "Processing tile data...", (task++) * taskRatio);

                int row    = (int)_fiTileInstance_row.GetValue(instance);
                int column = (int)_fiTileInstance_column.GetValue(instance);

                tile.Clear();

                // Create and assign tile data
                tile.brush           = map.Lookup((Object)_piTileInstance_brush.GetValue(instance, null));
                tile.orientationMask = (byte)OrientationUtility.MaskFromName((string)_fiTileInstance_orientationName.GetValue(instance));
                tile.variationIndex  = (byte)(int)_fiTileInstance_variationIndex.GetValue(instance);
                tile.Empty           = false;
                tile.gameObject      = instance.gameObject;
                piTileData_hasGameObject.SetValue(tile, true, null);

                v2.SetTileFrom(row, column, tile);

                Chunk chunk = v2.GetChunkFromTileIndex(row, column);
                ForceRepaintForAtlasTiles(v2.GetTile(row, column), chunk);
                if (instance == null)
                {
                    continue;
                }

                // Cleanup original tile instance?
                if (!StrippingUtility.StripEmptyGameObject(instance.transform))
                {
                    // Reparent game object to its shiny new chunk!
                    instance.gameObject.transform.parent = chunk.transform;
                }

                // Destroy unwanted tile instance component
                Object.DestroyImmediate(instance);
            }

            int count = v2.EndBulkEdit();
            RemoveTileSystem(v1);

            if (count > 0)
            {
                Debug.Log(string.Format("Upgrade of tile system '{0}' completed and {1} tile(s) were force refreshed.", v2.name, count));
            }
            else
            {
                Debug.Log(string.Format("Upgrade of tile system '{0}' completed.", v2.name));
            }
        }
        finally {
            EditorUtility.ClearProgressBar();
        }
    }
        /// <summary>
        /// Resize tile system.
        /// </summary>
        /// <remarks>
        /// <para>Progress bar is shown when this method is invoked in-editor, though this progress
        /// bar is not shown when working in play mode.</para>
        /// </remarks>
        /// <param name="system">Tile system.</param>
        /// <param name="newRows">New number of rows.</param>
        /// <param name="newColumns">New number of columns.</param>
        /// <param name="rowOffset">Number of rows of tiles to offset by.</param>
        /// <param name="columnOffset">Number of columns of tiles to offset by.</param>
        /// <param name="chunkWidth">New chunk width.</param>
        /// <param name="chunkHeight">New chunk height.</param>
        /// <param name="maintainTilePositionsInWorld">Indicates if tile positions should
        /// be maintained in world space.</param>
        /// <param name="eraseOutOfBounds">Indicates whether out-of-bound tiles should be
        /// erased.</param>
        public void Resize(TileSystem system, int newRows, int newColumns, int rowOffset, int columnOffset, int chunkWidth, int chunkHeight, bool maintainTilePositionsInWorld, bool eraseOutOfBounds)
        {
            bool restoreEnableProgressHandler = InternalUtility.EnableProgressHandler;

            InternalUtility.EnableProgressHandler = Application.isEditor && !Application.isPlaying;
            try {
                this.taskCount     = system.RowCount + newRows;
                this.taskProgress  = 0f;
                this.taskIncrement = 1f / this.taskCount;

                // Erase out-of-bound tiles.
                if (eraseOutOfBounds)
                {
                    InternalUtility.ProgressHandler("Rebuilding Tile System", "Erasing out-of-bound tiles.", 0f);
                    this.EraseOutOfBoundTiles(system, newRows, newColumns, rowOffset, columnOffset);
                }

                InternalUtility.ProgressHandler("Rebuilding Tile System", "Extracting tiles from chunks.", 0f);

                TileData[,] map = this.GenerateTileMap(system, newRows, newColumns, rowOffset, columnOffset);

                this.ReparentTileGameObjectsIntoWorldSpace(map);
                this.RemoveChunkObjects(system);

                // Update data structure of tile system.
                Vector3 cellSize = system.CellSize;
                system.InitializeSystem(cellSize.x, cellSize.y, cellSize.z, newRows, newColumns, chunkWidth, chunkHeight);

                // Reposition tile system so that tiles are re-parented correctly.
                Transform systemTransform       = system.transform;
                Vector3   previousLocalPosition = systemTransform.localPosition;
                systemTransform.position = system.WorldPositionFromTileIndex(-rowOffset, -columnOffset, false);

                system.BeginBulkEdit();

                // Reparent tile game objects.
                for (int row = 0; row < system.RowCount; ++row)
                {
                    this.taskProgress += this.taskIncrement;
                    InternalUtility.ProgressHandler("Rebuilding Tile System", "Creating new chunks.", this.taskProgress);

                    for (int column = 0; column < system.ColumnCount; ++column)
                    {
                        var tile = map[row, column];
                        if (tile == null || tile.Empty)
                        {
                            continue;
                        }

                        // Mark procedural tiles as dirty.
                        if (tile.Procedural)
                        {
                            tile.Dirty = true;
                        }

                        // Assign tile to tile system.
                        system.SetTile(row, column, tile);

                        var chunk = system.GetChunkFromTileIndex(row, column);
                        chunk.Dirty = true;

                        // Place tile game object into chunk.
                        if (tile.gameObject != null)
                        {
                            tile.gameObject.transform.SetParent(chunk.transform);
                        }
                    }
                }

                this.taskProgress += this.taskIncrement;
                InternalUtility.ProgressHandler("Rebuilding Tile System", "Updating tiles.", this.taskProgress);

                system.EndBulkEdit();

                if (!maintainTilePositionsInWorld)
                {
                    systemTransform.localPosition = previousLocalPosition;
                }
            }
            finally {
                InternalUtility.ClearProgress();
                InternalUtility.EnableProgressHandler = restoreEnableProgressHandler;
            }
        }