/// <summary>
        /// Track an exterior object in streaming world.
        /// Tracked object will be automatically destroyed when outside of range.
        /// </summary>
        /// <param name="gameObject">Game object to track.</param>
        /// <param name="mapPixelX">Map pixel X to store gameobject. -1 for player location.</param>
        /// <param name="mapPixelY">Map pixel Y to store gameobject. -1 for player location.</param>
        /// <param name="setParent">True to set parent as StreamingTarget.</param>
        public void TrackLooseObject(GameObject gameObject, int mapPixelX = -1, int mapPixelY = -1, bool setParent = false)
        {
            // Create loose object description
            LooseObjectDesc desc = new LooseObjectDesc();
            desc.gameObject = gameObject;
            if (mapPixelX == -1)
                desc.mapPixelX = MapPixelX;
            if (mapPixelY == -1)
                desc.mapPixelY = MapPixelY;
            looseObjectsList.Add(desc);

            // Change object parent
            if (setParent)
                gameObject.transform.parent = StreamingTarget.transform;
        }
        private IEnumerator UpdateLocation(int index, bool allowYield)
        {
            //int key = TerrainHelper.MakeTerrainKey(terrainArray[index].mapPixelX, terrainArray[index].mapPixelY);
            //int playerKey = TerrainHelper.MakeTerrainKey(MapPixelX, MapPixelY);
            //bool isPlayerTerrain = (key == playerKey);

            if (terrainArray[index].active && terrainArray[index].hasLocation && terrainArray[index].updateLocation)
            {
                // Disable update flag
                terrainArray[index].updateLocation = false;

                // Create location object
                DFLocation location;
                GameObject locationObject = CreateLocationGameObject(index, out location);
                if (locationObject)
                {
                    // Add location to loose object list
                    LooseObjectDesc looseObject = new LooseObjectDesc();
                    looseObject.gameObject = locationObject;
                    looseObject.mapPixelX = terrainArray[index].mapPixelX;
                    looseObject.mapPixelY = terrainArray[index].mapPixelY;
                    looseObjectsList.Add(looseObject);

                    // Create billboard batch game objects for this location
                    // Streaming world always batches for performance, regardless of options
                    int natureArchive = ClimateSwaps.GetNatureArchive(LocalPlayerGPS.ClimateSettings.NatureSet, dfUnity.WorldTime.Now.SeasonValue);
                    TextureAtlasBuilder miscBillboardAtlas = dfUnity.MaterialReader.MiscBillboardAtlas;
                    DaggerfallBillboardBatch natureBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(natureArchive, locationObject.transform);
                    DaggerfallBillboardBatch lightsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.LightsTextureArchive, locationObject.transform);
                    DaggerfallBillboardBatch animalsBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(TextureReader.AnimalsTextureArchive, locationObject.transform);
                    DaggerfallBillboardBatch miscBillboardBatch = GameObjectHelper.CreateBillboardBatchGameObject(miscBillboardAtlas.AtlasMaterial, locationObject.transform);

                    // Set hide flags
                    natureBillboardBatch.hideFlags = defaultHideFlags;
                    lightsBillboardBatch.hideFlags = defaultHideFlags;
                    animalsBillboardBatch.hideFlags = defaultHideFlags;
                    miscBillboardBatch.hideFlags = defaultHideFlags;

                    // RMB blocks are laid out in centre of terrain to align with ground
                    //int width = location.Exterior.ExteriorData.Width;
                    //int height = location.Exterior.ExteriorData.Height;
                    //float offsetX = ((8 * RMBLayout.RMBSide) - (width * RMBLayout.RMBSide)) / 2;
                    //float offsetZ = ((8 * RMBLayout.RMBSide) - (height * RMBLayout.RMBSide)) / 2;
                    //Vector3 origin = new Vector3(offsetX, 2.0f * MeshReader.GlobalScale, offsetZ);

                    // Position RMB blocks inside terrain area
                    int width = location.Exterior.ExteriorData.Width;
                    int height = location.Exterior.ExteriorData.Height;
                    DFPosition tilePos = TerrainHelper.GetLocationTerrainTileOrigin(location);
                    Vector3 origin = new Vector3(tilePos.X * RMBLayout.RMBTileSide, 2.0f * MeshReader.GlobalScale, tilePos.Y * RMBLayout.RMBTileSide);

                    // Get location data
                    DaggerfallLocation dfLocation = locationObject.GetComponent<DaggerfallLocation>();

                    // Perform layout and yield after each block is placed
                    for (int y = 0; y < height; y++)
                    {
                        for (int x = 0; x < width; x++)
                        {
                            // Set block origin for billboard batches
                            // This causes next additions to be offset by this position
                            Vector3 blockOrigin = origin + new Vector3((x * RMBLayout.RMBSide), 0, (y * RMBLayout.RMBSide));
                            natureBillboardBatch.BlockOrigin = blockOrigin;
                            lightsBillboardBatch.BlockOrigin = blockOrigin;
                            animalsBillboardBatch.BlockOrigin = blockOrigin;
                            miscBillboardBatch.BlockOrigin = blockOrigin;

                            // Add block and yield
                            string blockName = dfUnity.ContentReader.BlockFileReader.CheckName(dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, x, y));
                            GameObject go = GameObjectHelper.CreateRMBBlockGameObject(
                                blockName,
                                false,
                                dfUnity.Option_CityBlockPrefab,
                                natureBillboardBatch,
                                lightsBillboardBatch,
                                animalsBillboardBatch,
                                miscBillboardAtlas,
                                miscBillboardBatch);
                            go.hideFlags = defaultHideFlags;
                            go.transform.parent = locationObject.transform;
                            go.transform.localPosition = blockOrigin;
                            dfLocation.ApplyClimateSettings();
                            if (allowYield) yield return new WaitForEndOfFrame();
                        }
                    }

                    // Apply billboard batches
                    natureBillboardBatch.Apply();
                    lightsBillboardBatch.Apply();
                    animalsBillboardBatch.Apply();
                    miscBillboardBatch.Apply();
                }
            }
            else if (terrainArray[index].active)
            {
                //if (playerKey == key && repositionMethod != RepositionMethods.None)
                //{
                //    //PositionPlayerToTerrain(MapPixelX, MapPixelY, Vector3.zero);
                //    //repositionPlayer = false;
                //}
            }
        }