Exemple #1
0
 // Clear all sample tiles to same base index
 public static void ClearSampleTiles(ref MapPixelData mapPixel, byte record)
 {
     for (int i = 0; i < mapPixel.samples.Length; i++)
     {
         mapPixel.samples[i].record = record;
     }
 }
 // Clear all sample tiles to same base index
 public static void ClearSampleTiles(ref MapPixelData mapPixel, byte record)
 {
     for (int i = 0; i < mapPixel.samples.Length; i++)
     {
         mapPixel.samples[i].record = record;
     }
 }
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];

            // Populate heightmap
            float averageHeight = 0;
            float maxHeight     = float.MinValue;

            for (int y = 0; y < HeightmapDimension; y++)
            {
                for (int x = 0; x < HeightmapDimension; x++)
                {
                    // It is important to use a continuous noise function to avoid gaps between tiles
                    int   noisex = mapPixel.mapPixelX * (HeightmapDimension - 1) + x;
                    int   noisey = (MapsFile.MaxMapPixelY - mapPixel.mapPixelY) * (HeightmapDimension - 1) + y;
                    float height = TerrainHelper.GetNoise(noisex, noisey, 0.01f, 0.5f, 0.1f, 2) * Scale;
                    mapPixel.heightmapSamples[y, x] = height;

                    // Accumulate averages and max height
                    averageHeight += height;
                    if (height > maxHeight)
                    {
                        maxHeight = height;
                    }
                }
            }

            // Average and max heights are passed back for flattening location areas
            mapPixel.averageHeight = averageHeight /= (float)(HeightmapDimension * HeightmapDimension);
            mapPixel.maxHeight     = maxHeight;
        }
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            // Create samples arrays
            mapPixel.tilemapSamples = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
            mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];

            // Populate heightmap
            float averageHeight = 0;
            float maxHeight = float.MinValue;
            for (int y = 0; y < HeightmapDimension; y++)
            {
                for (int x = 0; x < HeightmapDimension; x++)
                {
                    // It is important to use a continuous noise function to avoid gaps between tiles
                    int noisex = mapPixel.mapPixelX * (HeightmapDimension - 1) + x;
                    int noisey = (MapsFile.MaxMapPixelY - mapPixel.mapPixelY) * (HeightmapDimension - 1) + y;
                    float height = TerrainHelper.GetNoise(noisex, noisey, 0.01f, 0.5f, 0.1f, 2) * Scale;
                    mapPixel.heightmapSamples[y, x] = height;

                    // Accumulate averages and max height
                    averageHeight += height;
                    if (height > maxHeight)
                        maxHeight = height;
                }
            }

            // Average and max heights are passed back for flattening location areas
            mapPixel.averageHeight = averageHeight /= (float)(HeightmapDimension * HeightmapDimension);
            mapPixel.maxHeight = maxHeight;
        }
        /// <summary>
        /// Update map pixel data based on current coordinates. (first of a two stage process)
        ///
        /// 1) BeginMapPixelDataUpdate - Schedules terrain data update using jobs system.
        /// 2) CompleteMapPixelDataUpdate - Completes terrain data update using jobs system.
        /// </summary>
        /// <param name="terrainTexturing">Instance of ITerrainTexturing implementation class to use.</param>
        /// <returns>JobHandle of the scheduled jobs</returns>
        public JobHandle BeginMapPixelDataUpdate(ITerrainTexturing terrainTexturing = null)
        {
            // Get basic terrain data.
            MapData = TerrainHelper.GetMapPixelData(dfUnity.ContentReader, MapPixelX, MapPixelY);

            // Create data array for heightmap.
            MapData.heightmapData = new NativeArray <float>(heightmapDim * heightmapDim, Allocator.TempJob);

            // Create data array for tilemap data.
            MapData.tilemapData = new NativeArray <byte>(tilemapDim * tilemapDim, Allocator.TempJob);

            // Create data array for shader tile map data.
            MapData.tileMap = new NativeArray <Color32>(tilemapDim * tilemapDim, Allocator.TempJob);

            // Create data array for average & max heights.
            MapData.avgMaxHeight = new NativeArray <float>(new float[] { 0, float.MinValue }, Allocator.TempJob);

            // Create list for recording native arrays that need disposal after jobs complete.
            MapData.nativeArrayList = new List <IDisposable>();

            // Generate heightmap samples. (returns when complete)
            JobHandle generateHeightmapSamplesJobHandle = dfUnity.TerrainSampler.ScheduleGenerateSamplesJob(ref MapData);

            // Handle location if one is present on terrain.
            JobHandle blendLocationTerrainJobHandle;

            if (MapData.hasLocation)
            {
                // Schedule job to calc average & max heights.
                JobHandle calcAvgMaxHeightJobHandle = TerrainHelper.ScheduleCalcAvgMaxHeightJob(ref MapData, generateHeightmapSamplesJobHandle);
                JobHandle.ScheduleBatchedJobs();

                // Set location tiles.
                TerrainHelper.SetLocationTiles(ref MapData);

                if (!dfUnity.TerrainSampler.IsLocationTerrainBlended())
                {
                    // Schedule job to blend and flatten location heights. (depends on SetLocationTiles being done first)
                    blendLocationTerrainJobHandle = TerrainHelper.ScheduleBlendLocationTerrainJob(ref MapData, calcAvgMaxHeightJobHandle);
                }
                else
                {
                    blendLocationTerrainJobHandle = calcAvgMaxHeightJobHandle;
                }
            }
            else
            {
                blendLocationTerrainJobHandle = generateHeightmapSamplesJobHandle;
            }

            // Assign tiles for terrain texturing.
            JobHandle assignTilesJobHandle = (terrainTexturing == null) ? blendLocationTerrainJobHandle :
                                             terrainTexturing.ScheduleAssignTilesJob(dfUnity.TerrainSampler, ref MapData, blendLocationTerrainJobHandle);

            // Update tile map for shader.
            JobHandle updateTileMapJobHandle = TerrainHelper.ScheduleUpdateTileMapDataJob(ref MapData, assignTilesJobHandle);

            JobHandle.ScheduleBatchedJobs();
            return(updateTileMapJobHandle);
        }
Exemple #6
0
        /// <summary>
        /// Updates map pixel data based on current coordinates.
        /// Must be called before other data update methods.
        /// </summary>
        public void UpdateMapPixelData(TerrainTexturing terrainTexturing = null)
        {
            if (!ReadyCheck())
            {
                return;
            }

            //System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
            //long startTime = stopwatch.ElapsedMilliseconds;

            // Get basic terrain data
            MapData = TerrainHelper.GetMapPixelData(dfUnity.ContentReader, MapPixelX, MapPixelY);
            dfUnity.TerrainSampler.GenerateSamples(ref MapData);

            // Handle terrain with location
            if (MapData.hasLocation)
            {
                TerrainHelper.SetLocationTiles(ref MapData);
                TerrainHelper.BlendLocationTerrain(ref MapData);
            }

            // Set textures
            if (terrainTexturing != null)
            {
                terrainTexturing.AssignTiles(dfUnity.TerrainSampler, ref MapData);
            }

            //long totalTime = stopwatch.ElapsedMilliseconds - startTime;
            //DaggerfallUnity.LogMessage(string.Format("Time to update map pixel data: {0}ms", totalTime), true);
        }
        public static float GetClampedHeight(ref MapPixelData mapPixel, int heightmapDimension, float u, float v)
        {
            int x = (int)Mathf.Clamp(heightmapDimension * u, 0, heightmapDimension - 1);
            int y = (int)Mathf.Clamp(heightmapDimension * v, 0, heightmapDimension - 1);

            return(mapPixel.heightmapSamples[y, x]);
        }
        // Flattens location terrain and blends with surrounding terrain
        public static void BlendLocationTerrain(ref MapPixelData mapPixel, float noiseStrength = 4f)
        {
            int heightmapDimension = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension;

            // Convert from rect in tilemap space to interior corners in 0-1 range
            float xMin = mapPixel.locationRect.xMin / MapsFile.WorldMapTileDim;
            float xMax = mapPixel.locationRect.xMax / MapsFile.WorldMapTileDim;
            float yMin = mapPixel.locationRect.yMin / MapsFile.WorldMapTileDim;
            float yMax = mapPixel.locationRect.yMax / MapsFile.WorldMapTileDim;

            // Scale values for converting blend space into 0-1 range
            float leftScale = 1 / xMin;
            float rightScale = 1 / (1 - xMax);
            float topScale = 1 / yMin;
            float bottomScale = 1 / (1 - yMax);

            // Flatten location area and blend with surrounding heights
            float strength = 0;
            float targetHeight = mapPixel.averageHeight;
            for (int y = 0; y < heightmapDimension; y++)
            {
                float v = (float)y / (float)(heightmapDimension - 1);
                bool insideY = (v >= yMin && v <= yMax);

                for (int x = 0; x < heightmapDimension; x++)
                {
                    float u = (float)x / (float)(heightmapDimension - 1);
                    bool insideX = (u >= xMin && u <= xMax);

                    float height = mapPixel.heightmapSamples[y, x];

                    if (insideX || insideY)
                    {
                        if (insideY && u <= xMin)
                            strength = u * leftScale;
                        else if (insideY && u >= xMax)
                            strength = (1 - u) * rightScale;
                        else if (insideX && v <= yMin)
                            strength = v * topScale;
                        else if (insideX && v >= yMax)
                            strength = (1 - v) * bottomScale;
                    }
                    else
                    {
                        float xs = 0, ys = 0;
                        if (u <= xMin) xs = u * leftScale; else if (u >= xMax) xs = (1 - u) * rightScale;
                        if (v <= yMin) ys = v * topScale; else if (v >= yMax) ys = (1 - v) * bottomScale;
                        strength = BilinearInterpolator(0, 0, 0, 1, xs, ys);
                    }

                    if (insideX && insideY)
                        height = targetHeight;
                    else
                        height = Mathf.Lerp(height, targetHeight, strength);

                    mapPixel.heightmapSamples[y, x] = height;
                }
            }
        }
 // Set all sample tiles to same base index
 public static void FillTilemapSamples(ref MapPixelData mapPixel, byte record)
 {
     for (int y = 0; y < MapsFile.WorldMapTileDim; y++)
     {
         for (int x = 0; x < MapsFile.WorldMapTileDim; x++)
         {
             mapPixel.tilemapSamples[x, y].record = record;
         }
     }
 }
Exemple #10
0
        public static JobHandle ScheduleCalcAvgMaxHeightJob(ref MapPixelData mapPixel, JobHandle dependencies)
        {
            CalcAvgMaxHeightJob calcAvgMaxHeightJob = new CalcAvgMaxHeightJob()
            {
                heightmapData = mapPixel.heightmapData,
                avgMaxHeight  = mapPixel.avgMaxHeight,
            };

            return(calcAvgMaxHeightJob.Schedule(dependencies));
        }
        public static JobHandle ScheduleBlendLocationTerrainJob(ref MapPixelData mapPixel, JobHandle dependencies)
        {
            BlendLocationTerrainJob blendLocationTerrainJob = new BlendLocationTerrainJob()
            {
                heightmapData = mapPixel.heightmapData,
                avgMaxHeight  = mapPixel.avgMaxHeight,
                hDim          = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension,
                locationRect  = mapPixel.locationRect,
            };

            return(blendLocationTerrainJob.Schedule(dependencies));
        }
Exemple #12
0
        public static JobHandle ScheduleUpdateTileMapDataJob(ref MapPixelData mapPixel, JobHandle dependencies)
        {
            int tilemapDim = MapsFile.WorldMapTileDim;
            UpdateTileMapDataJob updateTileMapDataJob = new UpdateTileMapDataJob()
            {
                tilemapData = mapPixel.tilemapData,
                tileMap     = mapPixel.tileMap,
                tDim        = tilemapDim,
            };

            return(updateTileMapDataJob.Schedule(tilemapDim * tilemapDim, 64, dependencies));
        }
        public override JobHandle ScheduleGenerateSamplesJob(ref MapPixelData mapPixel)
        {
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Divisor ensures continuous 0-1 range of height samples
            float div = (HeightmapDimension - 1) / 3f;

            // Read neighbouring height samples for this map pixel
            int  mx   = mapPixel.mapPixelX;
            int  my   = mapPixel.mapPixelY;
            byte sDim = 4;
            NativeArray <byte> shm = new NativeArray <byte>(dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange1Dim(mx - 2, my - 2, sDim), Allocator.TempJob);

            // Convert & flatten large height samples 2d array into 1d native array.
            byte[,] lhm2 = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3);
            NativeArray <byte> lhm = new NativeArray <byte>(lhm2.Length, Allocator.TempJob);
            byte lDim = (byte)lhm2.GetLength(0);
            int  i    = 0;

            for (int y = 0; y < lDim; y++)
            {
                for (int x = 0; x < lDim; x++)
                {
                    lhm[i++] = lhm2[x, y];
                }
            }

            // Add both working native arrays to disposal list.
            mapPixel.nativeArrayList.Add(shm);
            mapPixel.nativeArrayList.Add(lhm);

            // Extract height samples for all chunks
            int hDim = HeightmapDimension;
            GenerateSamplesJob generateSamplesJob = new GenerateSamplesJob
            {
                shm              = shm,
                lhm              = lhm,
                heightmapData    = mapPixel.heightmapData,
                sd               = sDim,
                ld               = lDim,
                hDim             = hDim,
                div              = div,
                mapPixelX        = mapPixel.mapPixelX,
                mapPixelY        = mapPixel.mapPixelY,
                maxTerrainHeight = MaxTerrainHeight,
            };

            JobHandle generateSamplesHandle = generateSamplesJob.Schedule(hDim * hDim, 64);     // Batch = 1 breaks it since shm not copied... test again later

            return(generateSamplesHandle);
        }
        public static JobHandle ScheduleUpdateTileMapDataJob(ref MapPixelData mapPixel, JobHandle dependencies)
        {
            int  tilemapDim   = MapsFile.WorldMapTileDim;
            bool convertWater = DaggerfallUnity.Instance.TerrainTexturing.ConvertWaterTiles();
            UpdateTileMapDataJob updateTileMapDataJob = new UpdateTileMapDataJob()
            {
                tilemapData  = mapPixel.tilemapData,
                tileMap      = mapPixel.tileMap,
                tDim         = tilemapDim,
                convertWater = convertWater,
            };

            return(updateTileMapDataJob.Schedule(tilemapDim * tilemapDim, 64, dependencies));
        }
Exemple #15
0
        void CacheTileData(ITerrainSampler terrainSampler, ref MapPixelData mapData)
        {
            // Create array if required
            int dim = MapsFile.WorldMapTileDim + 1;

            if (tileData == null)
            {
                tileData = new int[dim, dim];
            }

            // Populate array with tile metadata
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    // Height sample for ocean and beach tiles
                    float height = TerrainHelper.GetClampedHeight(
                        ref mapData,
                        terrainSampler.HeightmapDimension,
                        (float)x / (float)dim,
                        (float)y / (float)dim) * terrainSampler.MaxTerrainHeight;

                    // Ocean texture
                    if (height <= terrainSampler.OceanElevation)
                    {
                        tileData[x, y] = water;
                        continue;
                    }

                    // Get latitude and longitude of this tile
                    int latitude  = (int)(mapData.mapPixelX * MapsFile.WorldMapTileDim + x);
                    int longitude = (int)(MapsFile.MaxWorldTileCoordZ - mapData.mapPixelY * MapsFile.WorldMapTileDim + y);

                    // Beach texture
                    // Adds a little +/- randomness to threshold so beach line isn't too regular
                    if (height <= terrainSampler.BeachElevation + UnityEngine.Random.Range(-1.5f, 1.5f))
                    {
                        tileData[x, y] = dirt;
                        continue;
                    }

                    // Set texture tile using weighted noise
                    float weight = 0;
                    weight += NoiseWeight(latitude, longitude);
                    // TODO: Add other weights to influence texture tile generation
                    tileData[x, y] = GetWeightedRecord(weight);
                }
            }
        }
Exemple #16
0
        // Makes terrain sampler implementations backwards compatible with jobs system terrain data generation.
        public virtual JobHandle ScheduleGenerateSamplesJob(ref MapPixelData mapPixel)
        {
            GenerateSamples(ref mapPixel);

            // Convert generated samples to the flattened native array used by jobs.
            int hDim = HeightmapDimension;

            for (int y = 0; y < hDim; y++)
            {
                for (int x = 0; x < hDim; x++)
                {
                    mapPixel.heightmapData[JobA.Idx(y, x, hDim)] = mapPixel.heightmapSamples[y, x];
                }
            }
            return(new JobHandle());
        }
        // Very basic marching squares for water > dirt > grass > stone transitions.
        // Cannot handle water > grass or water > stone, etc.
        // Will improve this at later date to use a wider range of transitions.
        public void AssignTiles(ref MapPixelData mapData, bool march = true)
        {
            // Cache tile data to minimise noise sampling
            CacheTileData(ref mapData);

            // Assign tile data to terrain
            int dim = TerrainHelper.terrainSampleDim;

            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    int offset = y * dim + x;

                    // Do nothing if location tile as texture already set
                    if (mapData.samples[offset].location)
                    {
                        continue;
                    }

                    // Assign tile texture
                    if (march)
                    {
                        // Get sample points
                        int b0 = tileData[x, y];
                        int b1 = tileData[x + 1, y];
                        int b2 = tileData[x, y + 1];
                        int b3 = tileData[x + 1, y + 1];

                        int shape  = (b0 & 1) | (b1 & 1) << 1 | (b2 & 1) << 2 | (b3 & 1) << 3;
                        int ring   = (b0 + b1 + b2 + b3) >> 2;
                        int tileID = shape | ring << 4;

                        byte val = lookupTable[tileID];
                        mapData.samples[offset].record = val & 63;
                        mapData.samples[offset].rotate = ((val & 64) == 64);
                        mapData.samples[offset].flip   = ((val & 128) == 128);
                    }
                    else
                    {
                        mapData.samples[offset].record = tileData[x, y];
                    }
                }
            }
        }
Exemple #18
0
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Create samples array
            int dim = TerrainHelper.terrainSampleDim;

            mapPixel.samples = new WorldSample[dim * dim];

            // Populate samples
            float averageHeight = 0;
            float maxHeight     = float.MinValue;

            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    // It is important to use a continous noise function to avoid gaps between tiles
                    float latitude  = mapPixel.mapPixelX * MapsFile.WorldMapTileDim + x;
                    float longitude = MapsFile.MaxWorldTileCoordZ - mapPixel.mapPixelY * MapsFile.WorldMapTileDim + y;
                    float height    = TerrainHelper.GetNoise(dfUnity.ContentReader.Noise, latitude, longitude, 0.0025f, 0.9f, 0.7f, 2);
                    height *= baseHeightScale;

                    // Set sample
                    mapPixel.samples[y * dim + x] = new WorldSample()
                    {
                        scaledHeight = height,
                    };

                    // Accumulate averages and max height
                    averageHeight += height;
                    if (height > maxHeight)
                    {
                        maxHeight = height;
                    }
                }
            }

            // Average and max heights are passed back for flattening location areas
            mapPixel.averageHeight = averageHeight /= (float)(dim * dim);
            mapPixel.maxHeight     = maxHeight;
        }
        // Very basic marching squares for water > dirt > grass > stone transitions.
        // Cannot handle water > grass or water > stone, etc.
        // Will improve this at later date to use a wider range of transitions.
        public void AssignTiles(ITerrainSampler terrainSampler, ref MapPixelData mapData, bool march = true)
        {
            // Cache tile data to minimise noise sampling
            CacheTileData(terrainSampler, ref mapData);

            // Assign tile data to terrain
            int dim = TerrainHelper.terrainSampleDim;
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    int offset = y * dim + x;

                    // Do nothing if location tile as texture already set
                    if (mapData.samples[offset].location)
                        continue;

                    // Assign tile texture
                    if (march)
                    {
                        // Get sample points
                        int b0 = tileData[x, y];
                        int b1 = tileData[x + 1, y];
                        int b2 = tileData[x, y + 1];
                        int b3 = tileData[x + 1, y + 1];

                        int shape = (b0 & 1) | (b1 & 1) << 1 | (b2 & 1) << 2 | (b3 & 1) << 3;
                        int ring = (b0 + b1 + b2 + b3) >> 2;
                        int tileID = shape | ring << 4;

                        byte val = lookupTable[tileID];
                        mapData.samples[offset].record = val & 63;
                        mapData.samples[offset].rotate = ((val & 64) == 64);
                        mapData.samples[offset].flip = ((val & 128) == 128);
                    }
                    else
                    {
                        mapData.samples[offset].record = tileData[x, y];
                    }
                }
            }
        }
Exemple #20
0
        /// <summary>
        /// Gets map pixel data for any location in world.
        /// </summary>
        public static MapPixelData GetMapPixelData(ContentReader contentReader, int mapPixelX, int mapPixelY)
        {
            // Read general data from world maps
            int worldHeight  = contentReader.WoodsFileReader.GetHeightMapValue(mapPixelX, mapPixelY);
            int worldClimate = contentReader.MapFileReader.GetClimateIndex(mapPixelX, mapPixelY);
            int worldPolitic = contentReader.MapFileReader.GetPoliticIndex(mapPixelX, mapPixelY);

            // Get location if present
            int    id = -1, regionIndex = -1, mapIndex = -1;
            string locationName = string.Empty;

            ContentReader.MapSummary mapSummary = new ContentReader.MapSummary();
            bool hasLocation = contentReader.HasLocation(mapPixelX, mapPixelY, out mapSummary);

            if (hasLocation)
            {
                id          = mapSummary.ID;
                regionIndex = mapSummary.RegionIndex;
                mapIndex    = mapSummary.MapIndex;
                DFLocation location = contentReader.MapFileReader.GetLocation(regionIndex, mapIndex);
                locationName = location.Name;
            }

            // Create map pixel data
            MapPixelData mapPixel = new MapPixelData()
            {
                inWorld          = true,
                mapPixelX        = mapPixelX,
                mapPixelY        = mapPixelY,
                worldHeight      = worldHeight,
                worldClimate     = worldClimate,
                worldPolitic     = worldPolitic,
                hasLocation      = hasLocation,
                mapRegionIndex   = regionIndex,
                mapLocationIndex = mapIndex,
                locationID       = id,
                locationName     = locationName,
                LocationType     = mapSummary.LocationType
            };

            return(mapPixel);
        }
Exemple #21
0
        public static JobHandle ScheduleBlendLocationTerrainJob(ref MapPixelData mapPixel, JobHandle dependencies)
        {
            BlendLocationTerrainJob blendLocationTerrainJob = new BlendLocationTerrainJob()
            {
                heightmapData = mapPixel.heightmapData,
                avgMaxHeight  = mapPixel.avgMaxHeight,
                hDim          = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension,
                locationRect  = mapPixel.locationRect,
            };
            int extraBlendSpace = ExtraBlendSpace(mapPixel.LocationType);

            if (extraBlendSpace > 0)
            {
                blendLocationTerrainJob.locationRect.xMin -= extraBlendSpace;
                blendLocationTerrainJob.locationRect.xMax += extraBlendSpace;
                blendLocationTerrainJob.locationRect.yMin -= extraBlendSpace;
                blendLocationTerrainJob.locationRect.yMax += extraBlendSpace;
            }
            return(blendLocationTerrainJob.Schedule(dependencies));
        }
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Create samples array
            int dim = TerrainHelper.terrainSampleDim;
            mapPixel.samples = new WorldSample[dim * dim];

            // Populate samples
            float averageHeight = 0;
            float maxHeight = float.MinValue;
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    // It is important to use a continous noise function to avoid gaps between tiles
                    float latitude = mapPixel.mapPixelX * MapsFile.WorldMapTileDim + x;
                    float longitude = MapsFile.MaxWorldTileCoordZ - mapPixel.mapPixelY * MapsFile.WorldMapTileDim + y;
                    float height = TerrainHelper.GetNoise(dfUnity.ContentReader.Noise, latitude, longitude, 0.0025f, 0.9f, 0.7f, 2);
                    height *= baseHeightScale;

                    // Set sample
                    mapPixel.samples[y * dim + x] = new WorldSample()
                    {
                        scaledHeight = height,
                    };

                    // Accumulate averages and max height
                    averageHeight += height;
                    if (height > maxHeight)
                        maxHeight = height;
                }
            }

            // Average and max heights are passed back for flattening location areas
            mapPixel.averageHeight = averageHeight /= (float)(dim * dim);
            mapPixel.maxHeight = maxHeight;
        }
Exemple #23
0
        public JobHandle ScheduleAssignTilesJob(ITerrainSampler terrainSampler, ref MapPixelData mapData, JobHandle dependencies, bool march = true)
        {
            // Cache tile data to minimise noise sampling during march.
            NativeArray <byte>  tileData    = new NativeArray <byte>(tileDataDim * tileDataDim, Allocator.TempJob);
            GenerateTileDataJob tileDataJob = new GenerateTileDataJob
            {
                heightmapData    = mapData.heightmapData,
                tileData         = tileData,
                tdDim            = tileDataDim,
                hDim             = terrainSampler.HeightmapDimension,
                maxTerrainHeight = terrainSampler.MaxTerrainHeight,
                oceanElevation   = terrainSampler.OceanElevation,
                beachElevation   = terrainSampler.BeachElevation,
                mapPixelX        = mapData.mapPixelX,
                mapPixelY        = mapData.mapPixelY,
            };
            JobHandle tileDataHandle = tileDataJob.Schedule(tileDataDim * tileDataDim, 64, dependencies);

            // Assign tile data to terrain
            NativeArray <byte> lookupData     = new NativeArray <byte>(lookupTable, Allocator.TempJob);
            AssignTilesJob     assignTilesJob = new AssignTilesJob
            {
                lookupTable  = lookupData,
                tileData     = tileData,
                tilemapData  = mapData.tilemapData,
                tdDim        = tileDataDim,
                tDim         = assignTilesDim,
                march        = march,
                locationRect = mapData.locationRect,
            };
            JobHandle assignTilesHandle = assignTilesJob.Schedule(assignTilesDim * assignTilesDim, 64, tileDataHandle);

            // Add both working native arrays to disposal list.
            mapData.nativeArrayList.Add(tileData);
            mapData.nativeArrayList.Add(lookupData);

            return(assignTilesHandle);
        }
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            // Create samples array
            int dim = TerrainHelper.terrainSampleDim;
            mapPixel.samples = new WorldSample[dim * dim];

            // Populate samples
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    // Set sample 
                    mapPixel.samples[y * dim + x] = new WorldSample()
                    {
                        scaledHeight = height,
                    };
                }
            }

            // Average and max heights are passed back for flattening location areas
            mapPixel.averageHeight = height;
            mapPixel.maxHeight = height;
        }
        /// <summary>
        /// Updates map pixel data based on current coordinates.
        /// Must be called before other data update methods.
        /// </summary>
        public void UpdateMapPixelData(TerrainTexturing terrainTexturing = null)
        {
            if (!ReadyCheck())
            {
                return;
            }

            // Get basic terrain data
            MapData = TerrainHelper.GetMapPixelData(dfUnity.ContentReader, MapPixelX, MapPixelY);
            TerrainHelper.GenerateSamples(dfUnity.ContentReader, ref MapData);

            // Handle terrain with location
            if (MapData.hasLocation)
            {
                TerrainHelper.SetLocationTiles(dfUnity.ContentReader, ref MapData);
                TerrainHelper.FlattenLocationTerrain(dfUnity.ContentReader, ref MapData);
            }

            // Set textures
            if (terrainTexturing != null)
            {
                terrainTexturing.AssignTiles(ref MapData);
            }
        }
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Create samples arrays
            mapPixel.tilemapSamples = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
            mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];

            // Divisor ensures continuous 0-1 range of height samples
            float div = (float)(HeightmapDimension - 1) / 3f;

            // Read neighbouring height samples for this map pixel
            int mx = mapPixel.mapPixelX;
            int my = mapPixel.mapPixelY;
            byte[,] shm = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4);
            byte[,] lhm = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3);

            // Extract height samples for all chunks
            float averageHeight = 0;
            float maxHeight = float.MinValue;
            float baseHeight, noiseHeight;
            float x1, x2, x3, x4;
            int dim = HeightmapDimension;
            mapPixel.heightmapSamples = new float[dim, dim];
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    float rx = (float)x / div;
                    float ry = (float)y / div;
                    int ix = Mathf.FloorToInt(rx);
                    int iy = Mathf.FloorToInt(ry);
                    float sfracx = (float)x / (float)(dim - 1);
                    float sfracy = (float)y / (float)(dim - 1);
                    float fracx = (float)(x - ix * div) / div;
                    float fracy = (float)(y - iy * div) / div;
                    float scaledHeight = 0;

                    // Bicubic sample small height map for base terrain elevation
                    x1 = TerrainHelper.CubicInterpolator(shm[0, 3], shm[1, 3], shm[2, 3], shm[3, 3], sfracx);
                    x2 = TerrainHelper.CubicInterpolator(shm[0, 2], shm[1, 2], shm[2, 2], shm[3, 2], sfracx);
                    x3 = TerrainHelper.CubicInterpolator(shm[0, 1], shm[1, 1], shm[2, 1], shm[3, 1], sfracx);
                    x4 = TerrainHelper.CubicInterpolator(shm[0, 0], shm[1, 0], shm[2, 0], shm[3, 0], sfracx);
                    baseHeight = TerrainHelper.CubicInterpolator(x1, x2, x3, x4, sfracy);
                    scaledHeight += baseHeight * baseHeightScale;

                    // Bicubic sample large height map for noise mask over terrain features
                    x1 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 0], lhm[ix + 1, iy + 0], lhm[ix + 2, iy + 0], lhm[ix + 3, iy + 0], fracx);
                    x2 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 1], lhm[ix + 1, iy + 1], lhm[ix + 2, iy + 1], lhm[ix + 3, iy + 1], fracx);
                    x3 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 2], lhm[ix + 1, iy + 2], lhm[ix + 2, iy + 2], lhm[ix + 3, iy + 2], fracx);
                    x4 = TerrainHelper.CubicInterpolator(lhm[ix, iy + 3], lhm[ix + 1, iy + 3], lhm[ix + 2, iy + 3], lhm[ix + 3, iy + 3], fracx);
                    noiseHeight = TerrainHelper.CubicInterpolator(x1, x2, x3, x4, fracy);
                    scaledHeight += noiseHeight * noiseMapScale;

                    // Additional noise mask for small terrain features at ground level
                    int noisex = mapPixel.mapPixelX * (HeightmapDimension - 1) + x;
                    int noisey = (MapsFile.MaxMapPixelY - mapPixel.mapPixelY) * (HeightmapDimension - 1) + y;
                    float lowFreq = TerrainHelper.GetNoise(noisex, noisey, 0.3f, 0.5f, 0.5f, 1);
                    float highFreq = TerrainHelper.GetNoise(noisex, noisey, 0.9f, 0.5f, 0.5f, 1);
                    scaledHeight += (lowFreq * highFreq) * extraNoiseScale;

                    // Clamp lower values to ocean elevation
                    if (scaledHeight < scaledOceanElevation)
                        scaledHeight = scaledOceanElevation;

                    // Set sample
                    float height = Mathf.Clamp01(scaledHeight / MaxTerrainHeight);
                    mapPixel.heightmapSamples[y, x] = height;

                    // Accumulate averages and max height
                    averageHeight += height;
                    if (height > maxHeight)
                        maxHeight = height;
                }
            }

            // Average and max heights are passed back for locations
            mapPixel.averageHeight = averageHeight /= (float)(dim * dim);
            mapPixel.maxHeight = maxHeight;
        }
 public abstract void GenerateSamples(ref MapPixelData mapPixel);
 public override void GenerateSamples(ref MapPixelData mapPixel)
 {
     // Should never get called since class has been updated to schedule work using jobs system.
     throw new System.NotImplementedException();
 }
        /// <summary>
        /// Generate initial samples from any map pixel coordinates in world range.
        /// Also sets location height in mapPixelData for location positioning.
        /// </summary>
        public static void GenerateSamples(ContentReader contentReader, ref MapPixelData mapPixel)
        {
            // Raise start event
            RaiseOnGenerateSamplesStartEvent();

            // Divisor ensures continuous 0-1 range of tile samples
            float div = (float)terrainTileDim / 3f;

            // Read neighbouring height samples for this map pixel
            int mx = mapPixel.mapPixelX;
            int my = mapPixel.mapPixelY;
            byte[,] shm = contentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4);
            byte[,] lhm = contentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3);

            // Raise new samples event
            RaiseOnNewHeightSamplesEvent();

            // Extract height samples for all chunks
            float averageHeight = 0;
            float maxHeight = float.MinValue;
            float baseHeight, noiseHeight;
            float x1, x2, x3, x4;
            int dim = terrainSampleDim;
            mapPixel.samples = new WorldSample[dim * dim];
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    float rx = (float)x / div;
                    float ry = (float)y / div;
                    int ix = Mathf.FloorToInt(rx);
                    int iy = Mathf.FloorToInt(ry);
                    float sfracx = (float)x / (float)(dim - 1);
                    float sfracy = (float)y / (float)(dim - 1);
                    float fracx = (float)(x - ix * div) / div;
                    float fracy = (float)(y - iy * div) / div;
                    float scaledHeight = 0;

                    //// TEST: Point sample small height map for base terrain
                    //baseHeight = shm[2, 2];
                    //scaledHeight += baseHeight * baseHeightScale;

                    // Bicubic sample small height map for base terrain elevation
                    x1 = CubicInterpolator(shm[0, 3], shm[1, 3], shm[2, 3], shm[3, 3], sfracx);
                    x2 = CubicInterpolator(shm[0, 2], shm[1, 2], shm[2, 2], shm[3, 2], sfracx);
                    x3 = CubicInterpolator(shm[0, 1], shm[1, 1], shm[2, 1], shm[3, 1], sfracx);
                    x4 = CubicInterpolator(shm[0, 0], shm[1, 0], shm[2, 0], shm[3, 0], sfracx);
                    baseHeight = CubicInterpolator(x1, x2, x3, x4, sfracy);
                    scaledHeight += baseHeight * baseHeightScale;

                    // Bicubic sample large height map for noise mask over terrain features
                    x1 = CubicInterpolator(lhm[ix, iy + 0], lhm[ix + 1, iy + 0], lhm[ix + 2, iy + 0], lhm[ix + 3, iy + 0], fracx);
                    x2 = CubicInterpolator(lhm[ix, iy + 1], lhm[ix + 1, iy + 1], lhm[ix + 2, iy + 1], lhm[ix + 3, iy + 1], fracx);
                    x3 = CubicInterpolator(lhm[ix, iy + 2], lhm[ix + 1, iy + 2], lhm[ix + 2, iy + 2], lhm[ix + 3, iy + 2], fracx);
                    x4 = CubicInterpolator(lhm[ix, iy + 3], lhm[ix + 1, iy + 3], lhm[ix + 2, iy + 3], lhm[ix + 3, iy + 3], fracx);
                    noiseHeight = CubicInterpolator(x1, x2, x3, x4, fracy);
                    scaledHeight += noiseHeight * noiseMapScale;

                    // TODO: Developers must be able to override above settings via event or some other mechanism
                    // Will implement this before final 1.3 version

                    // Additional noise mask for small terrain features at ground level
                    float latitude = mapPixel.mapPixelX * MapsFile.WorldMapTileDim + x;
                    float longitude = MapsFile.MaxWorldTileCoordZ - mapPixel.mapPixelY * MapsFile.WorldMapTileDim + y;
                    float lowFreq = GetNoise(contentReader, latitude, longitude, 0.1f, 0.5f, 0.5f, 1);
                    float highFreq = GetNoise(contentReader, latitude, longitude, 6f, 0.5f, 0.5f, 1);
                    scaledHeight += (lowFreq * highFreq) * extraNoiseScale;

                    // Clamp lower values to ocean elevation
                    if (scaledHeight < scaledOceanElevation)
                        scaledHeight = scaledOceanElevation;

                    // Accumulate average height
                    averageHeight += scaledHeight;

                    // Get max height
                    if (scaledHeight > maxHeight)
                        maxHeight = scaledHeight;

                    // Set sample
                    mapPixel.samples[y * dim + x] = new WorldSample()
                    {
                        scaledHeight = scaledHeight,
                        record = 2,
                    };
                }
            }

            // Average and max heights are passed back for locations
            mapPixel.averageHeight = averageHeight /= (float)(dim * dim);
            mapPixel.maxHeight = maxHeight;

            // Raise end event
            RaiseOnGenerateSamplesEndEvent();
        }
        /// <summary>
        /// Gets map pixel data for any location in world.
        /// </summary>
        public static MapPixelData GetMapPixelData(ContentReader contentReader, int mapPixelX, int mapPixelY)
        {
            // Read general data from world maps
            int worldHeight = contentReader.WoodsFileReader.GetHeightMapValue(mapPixelX, mapPixelY);
            int worldClimate = contentReader.MapFileReader.GetClimateIndex(mapPixelX, mapPixelY);
            int worldPolitic = contentReader.MapFileReader.GetPoliticIndex(mapPixelX, mapPixelY);

            // Get location if present
            int id = -1, regionIndex = -1, mapIndex = -1;
            string locationName = string.Empty;
            ContentReader.MapSummary mapSummary = new ContentReader.MapSummary();
            bool hasLocation = contentReader.HasLocation(mapPixelX, mapPixelY, out mapSummary);
            if (hasLocation)
            {
                id = mapSummary.ID;
                regionIndex = mapSummary.RegionIndex;
                mapIndex = mapSummary.MapIndex;
                DFLocation location = contentReader.MapFileReader.GetLocation(regionIndex, mapIndex);
                locationName = location.Name;
            }

            // Create map pixel data
            MapPixelData mapPixel = new MapPixelData()
            {
                inWorld = true,
                mapPixelX = mapPixelX,
                mapPixelY = mapPixelY,
                worldHeight = worldHeight,
                worldClimate = worldClimate,
                worldPolitic = worldPolitic,
                hasLocation = hasLocation,
                mapRegionIndex = regionIndex,
                mapLocationIndex = mapIndex,
                locationID = id,
                locationName = locationName,
            };

            return mapPixel;
        }
Exemple #31
0
        // Set location tilemap data
        public static void SetLocationTiles(ref MapPixelData mapPixel)
        {
            // Get location
            DaggerfallUnity dfUnity  = DaggerfallUnity.Instance;
            DFLocation      location = dfUnity.ContentReader.MapFileReader.GetLocation(mapPixel.mapRegionIndex, mapPixel.mapLocationIndex);

            // Position tiles inside terrain area
            DFPosition tilePos = TerrainHelper.GetLocationTerrainTileOrigin(location);

            // Full 8x8 locations have "terrain blend space" around walls to smooth down random terrain towards flat area.
            // This is indicated by texture index > 55 (ground texture range is 0-55), larger values indicate blend space.
            // We need to know rect of actual city area so we can use blend space outside walls.
            int xmin = int.MaxValue, ymin = int.MaxValue;
            int xmax = 0, ymax = 0;

            // Iterate blocks of this location
            for (int blockY = 0; blockY < location.Exterior.ExteriorData.Height; blockY++)
            {
                for (int blockX = 0; blockX < location.Exterior.ExteriorData.Width; blockX++)
                {
                    // Get block data
                    DFBlock block;
                    string  blockName = dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, blockX, blockY);
                    if (!dfUnity.ContentReader.GetBlock(blockName, out block))
                    {
                        continue;
                    }

                    // Copy ground tile info
                    for (int tileY = 0; tileY < RMBLayout.RMBTilesPerBlock; tileY++)
                    {
                        for (int tileX = 0; tileX < RMBLayout.RMBTilesPerBlock; tileX++)
                        {
                            DFBlock.RmbGroundTiles tile = block.RmbBlock.FldHeader.GroundData.GroundTiles[tileX, (RMBLayout.RMBTilesPerBlock - 1) - tileY];
                            int xpos = tilePos.X + blockX * RMBLayout.RMBTilesPerBlock + tileX;
                            int ypos = tilePos.Y + blockY * RMBLayout.RMBTilesPerBlock + tileY;

                            if (tile.TextureRecord < 56)
                            {
                                // Track interior bounds of location tiled area
                                if (xpos < xmin)
                                {
                                    xmin = xpos;
                                }
                                if (xpos > xmax)
                                {
                                    xmax = xpos;
                                }
                                if (ypos < ymin)
                                {
                                    ymin = ypos;
                                }
                                if (ypos > ymax)
                                {
                                    ymax = ypos;
                                }

                                // Store texture data from block
                                mapPixel.tilemapData[JobA.Idx(xpos, ypos, MapsFile.WorldMapTileDim)] = tile.TileBitfield == 0 ? byte.MaxValue : tile.TileBitfield;
                            }
                        }
                    }
                }
            }

            // Update location rect with extra clearance
            int  extraClearance = location.MapTableData.LocationType == DFRegion.LocationTypes.TownCity ? 3 : 2;
            Rect locationRect   = new Rect();

            locationRect.xMin     = xmin - extraClearance;
            locationRect.xMax     = xmax + extraClearance;
            locationRect.yMin     = ymin - extraClearance;
            locationRect.yMax     = ymax + extraClearance;
            mapPixel.locationRect = locationRect;
        }
 public override void GenerateSamples(ref MapPixelData mapPixel)
 {
     mapPixel.tilemapSamples   = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
     mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];
 }
        /// <summary>
        /// Updates map pixel data based on current coordinates.
        /// Must be called before other data update methods.
        /// </summary>
        public void UpdateMapPixelData(TerrainTexturing terrainTexturing = null)
        {
            if (!ReadyCheck())
                return;

            //System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
            //long startTime = stopwatch.ElapsedMilliseconds;

            // Get basic terrain data
            MapData = TerrainHelper.GetMapPixelData(dfUnity.ContentReader, MapPixelX, MapPixelY);
            dfUnity.TerrainSampler.GenerateSamples(ref MapData);

            // Handle terrain with location
            if (MapData.hasLocation)
            {
                TerrainHelper.SetLocationTiles(ref MapData);
                TerrainHelper.BlendLocationTerrain(ref MapData);
            }

            // Set textures
            if (terrainTexturing != null)
            {
                terrainTexturing.AssignTiles(dfUnity.TerrainSampler, ref MapData);
            }

            //long totalTime = stopwatch.ElapsedMilliseconds - startTime;
            //DaggerfallUnity.LogMessage(string.Format("Time to update map pixel data: {0}ms", totalTime), true);
        }
 public override void GenerateSamples(ref MapPixelData mapPixel)
 {
     mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];
 }
Exemple #35
0
        // Flattens location terrain and blends flat area with surrounding terrain
        // Not entirely happy with this, need to revisit later
        public static void FlattenLocationTerrain(ContentReader contentReader, ref MapPixelData mapPixel, float noiseStrength = 3f)
        {
            // Get range between bounds of sample data and interior location rect
            // The location rect is always smaller than the sample area
            float leftRange   = 1f / (mapPixel.locationRect.xMin);
            float topRange    = 1f / (mapPixel.locationRect.yMin);
            float rightRange  = 1f / (terrainSampleDim - mapPixel.locationRect.xMax);
            float bottomRange = 1f / (terrainSampleDim - mapPixel.locationRect.yMax);

            float desiredHeight = mapPixel.averageHeight;
            float strength = 0;
            float u, v;

            for (int y = 1; y < terrainSampleDim - 1; y++)
            {
                for (int x = 1; x < terrainSampleDim - 1; x++)
                {
                    // Create a height scale from location to edge of terrain using
                    // linear interpolation on straight edges and bilinear in corners
                    if (x <= mapPixel.locationRect.xMin && y >= mapPixel.locationRect.yMin && y <= mapPixel.locationRect.yMax)
                    {
                        strength = x * leftRange;
                    }
                    else if (x >= mapPixel.locationRect.xMax && y >= mapPixel.locationRect.yMin && y <= mapPixel.locationRect.yMax)
                    {
                        strength = (terrainSampleDim - x) * rightRange;
                    }
                    else if (y <= mapPixel.locationRect.yMin && x >= mapPixel.locationRect.xMin && x <= mapPixel.locationRect.xMax)
                    {
                        strength = y * topRange;
                    }
                    else if (y >= mapPixel.locationRect.yMax && x >= mapPixel.locationRect.xMin && x <= mapPixel.locationRect.xMax)
                    {
                        strength = (terrainSampleDim - y) * bottomRange;
                    }
                    else if (x <= mapPixel.locationRect.xMin && y <= mapPixel.locationRect.yMin)
                    {
                        u        = x * leftRange;
                        v        = y * topRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }
                    else if (x >= mapPixel.locationRect.xMax && y <= mapPixel.locationRect.yMin)
                    {
                        u        = (terrainSampleDim - x) * rightRange;
                        v        = y * topRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }
                    else if (x <= mapPixel.locationRect.xMin && y >= mapPixel.locationRect.yMax)
                    {
                        u        = x * leftRange;
                        v        = (terrainSampleDim - y) * bottomRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }
                    else if (x >= mapPixel.locationRect.xMax && y >= mapPixel.locationRect.yMax)
                    {
                        u        = (terrainSampleDim - x) * rightRange;
                        v        = (terrainSampleDim - y) * bottomRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }

                    // Apply a little noise to gradient so it doesn't look perfectly smooth
                    float extraNoise = GetNoise(contentReader.Noise, x, y, 0.1f, 0.5f, 0.5f, 1) * noiseStrength * (1f - strength);

                    int   offset    = y * terrainSampleDim + x;
                    float curHeight = mapPixel.samples[offset].scaledHeight;
                    if (!mapPixel.samples[offset].location)
                    {
                        mapPixel.samples[offset].scaledHeight = (desiredHeight * strength) + (curHeight * (1 - strength)) + extraNoise;
                    }
                    else
                    {
                        mapPixel.samples[offset].scaledHeight = desiredHeight;
                    }
                }
            }
        }
        // Set texture and height data for city tiles
        public static void SetLocationTiles(ContentReader contentReader, ref MapPixelData mapPixel)
        {
            const int tileDim = 16;
            const int chunkDim = 8;

            // Get location
            DFLocation location = contentReader.MapFileReader.GetLocation(mapPixel.mapRegionIndex, mapPixel.mapLocationIndex);

            // Centre location tiles inside terrain area
            int startX = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Width * tileDim) / 2;
            int startY = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Height * tileDim) / 2;

            // Full 8x8 locations have "terrain blend space" around walls to smooth down random terrain towards flat area.
            // This is indicated by texture index > 55 (ground texture range is 0-55), larger values indicate blend space.
            // We need to know rect of actual city area so we can use blend space outside walls.
            int xmin = terrainSampleDim, ymin = terrainSampleDim;
            int xmax = 0, ymax = 0;

            // Iterate blocks of this location
            for (int blockY = 0; blockY < location.Exterior.ExteriorData.Height; blockY++)
            {
                for (int blockX = 0; blockX < location.Exterior.ExteriorData.Width; blockX++)
                {
                    // Get block data
                    DFBlock block;
                    string blockName = contentReader.MapFileReader.GetRmbBlockName(ref location, blockX, blockY);
                    if (!contentReader.GetBlock(blockName, out block))
                        continue;

                    // Copy ground tile info
                    for (int tileY = 0; tileY < tileDim; tileY++)
                    {
                        for (int tileX = 0; tileX < tileDim; tileX++)
                        {
                            DFBlock.RmbGroundTiles tile = block.RmbBlock.FldHeader.GroundData.GroundTiles[tileX, (tileDim - 1) - tileY];
                            int xpos = startX + blockX * tileDim + tileX;
                            int ypos = startY + blockY * tileDim + tileY;
                            int offset = (ypos * terrainSampleDim) + xpos;

                            int record = tile.TextureRecord;
                            if (tile.TextureRecord < 56)
                            {
                                // Track interior bounds of location tiled area
                                if (xpos < xmin) xmin = xpos;
                                if (xpos > xmax) xmax = xpos;
                                if (ypos < ymin) ymin = ypos;
                                if (ypos > ymax) ymax = ypos;

                                // Store texture data from block
                                mapPixel.samples[offset].record = record;
                                mapPixel.samples[offset].flip = tile.IsFlipped;
                                mapPixel.samples[offset].rotate = tile.IsRotated;
                                mapPixel.samples[offset].location = true;
                            }
                        }
                    }
                }
            }

            // Update location rect with extra clearance
            const int extraClearance = 2;
            Rect locationRect = new Rect();
            locationRect.xMin = xmin - extraClearance;
            locationRect.xMax = xmax + extraClearance;
            locationRect.yMin = ymin - extraClearance;
            locationRect.yMax = ymax + extraClearance;
            mapPixel.locationRect = locationRect;
        }
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Create samples arrays
            mapPixel.tilemapSamples   = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
            mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];

            // Divisor ensures continuous 0-1 range of height samples
            float div = (float)(HeightmapDimension - 1) / 3f;

            // Read neighbouring height samples for this map pixel
            int mx = mapPixel.mapPixelX;
            int my = mapPixel.mapPixelY;

            byte[,] shm = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4);
            byte[,] lhm = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3);

            // Extract height samples for all chunks
            float averageHeight = 0;
            float maxHeight = float.MinValue;
            float baseHeight, noiseHeight;
            float x1, x2, x3, x4;
            int   dim = HeightmapDimension;

            mapPixel.heightmapSamples = new float[dim, dim];
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    float rx           = (float)x / div;
                    float ry           = (float)y / div;
                    int   ix           = Mathf.FloorToInt(rx);
                    int   iy           = Mathf.FloorToInt(ry);
                    float sfracx       = (float)x / (float)(dim - 1);
                    float sfracy       = (float)y / (float)(dim - 1);
                    float fracx        = (float)(x - ix * div) / div;
                    float fracy        = (float)(y - iy * div) / div;
                    float scaledHeight = 0;

                    // Bicubic sample small height map for base terrain elevation
                    x1            = TerrainHelper.CubicInterpolator(shm[0, 3], shm[1, 3], shm[2, 3], shm[3, 3], sfracx);
                    x2            = TerrainHelper.CubicInterpolator(shm[0, 2], shm[1, 2], shm[2, 2], shm[3, 2], sfracx);
                    x3            = TerrainHelper.CubicInterpolator(shm[0, 1], shm[1, 1], shm[2, 1], shm[3, 1], sfracx);
                    x4            = TerrainHelper.CubicInterpolator(shm[0, 0], shm[1, 0], shm[2, 0], shm[3, 0], sfracx);
                    baseHeight    = TerrainHelper.CubicInterpolator(x1, x2, x3, x4, sfracy);
                    scaledHeight += baseHeight * baseHeightScale;

                    // Bicubic sample large height map for noise mask over terrain features
                    x1            = TerrainHelper.CubicInterpolator(lhm[ix, iy + 0], lhm[ix + 1, iy + 0], lhm[ix + 2, iy + 0], lhm[ix + 3, iy + 0], fracx);
                    x2            = TerrainHelper.CubicInterpolator(lhm[ix, iy + 1], lhm[ix + 1, iy + 1], lhm[ix + 2, iy + 1], lhm[ix + 3, iy + 1], fracx);
                    x3            = TerrainHelper.CubicInterpolator(lhm[ix, iy + 2], lhm[ix + 1, iy + 2], lhm[ix + 2, iy + 2], lhm[ix + 3, iy + 2], fracx);
                    x4            = TerrainHelper.CubicInterpolator(lhm[ix, iy + 3], lhm[ix + 1, iy + 3], lhm[ix + 2, iy + 3], lhm[ix + 3, iy + 3], fracx);
                    noiseHeight   = TerrainHelper.CubicInterpolator(x1, x2, x3, x4, fracy);
                    scaledHeight += noiseHeight * noiseMapScale;

                    // Additional noise mask for small terrain features at ground level
                    int   noisex   = mapPixel.mapPixelX * (HeightmapDimension - 1) + x;
                    int   noisey   = (MapsFile.MaxMapPixelY - mapPixel.mapPixelY) * (HeightmapDimension - 1) + y;
                    float lowFreq  = TerrainHelper.GetNoise(noisex, noisey, 0.3f, 0.5f, 0.5f, 1);
                    float highFreq = TerrainHelper.GetNoise(noisex, noisey, 0.9f, 0.5f, 0.5f, 1);
                    scaledHeight += (lowFreq * highFreq) * extraNoiseScale;

                    // Clamp lower values to ocean elevation
                    if (scaledHeight < scaledOceanElevation)
                    {
                        scaledHeight = scaledOceanElevation;
                    }

                    // Set sample
                    float height = Mathf.Clamp01(scaledHeight / MaxTerrainHeight);
                    mapPixel.heightmapSamples[y, x] = height;

                    // Accumulate averages and max height
                    averageHeight += height;
                    if (height > maxHeight)
                    {
                        maxHeight = height;
                    }
                }
            }

            // Average and max heights are passed back for locations
            mapPixel.averageHeight = averageHeight /= (float)(dim * dim);
            mapPixel.maxHeight     = maxHeight;
        }
Exemple #38
0
        public override void GenerateSamples(ref MapPixelData mapPixel)
        {
            //System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();
            //long startTime = stopwatch.ElapsedMilliseconds;

            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Create samples arrays
            mapPixel.tilemapSamples   = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
            mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];

            // Divisor ensures continuous 0-1 range of height samples
            float div = (float)(HeightmapDimension - 1) / 3f;

            // Read neighbouring height samples for this map pixel
            int mx = mapPixel.mapPixelX;
            int my = mapPixel.mapPixelY;

            byte[,] shm = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4);
            byte[,] lhm = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3);

            // the number of parallel tasks (note Nystul: use logical processor count for now - seems to be a good value)
            int numParallelTasks = Environment.ProcessorCount;

            // events used to synchronize thread computations (wait for them to finish)
            var doneEvents = new ManualResetEvent[numParallelTasks];

            // the array of instances of the height computations helper class
            var heightsComputationTaskArray = new HeightsComputationTask[numParallelTasks];

            // array of the data needed by the different tasks
            var dataForTasks = new HeightsComputationTask.DataForTask[numParallelTasks];

            for (int i = 0; i < numParallelTasks; i++)
            {
                doneEvents[i] = new ManualResetEvent(false);
                var heightsComputationTask = new HeightsComputationTask(doneEvents[i]);
                heightsComputationTaskArray[i] = heightsComputationTask;
                dataForTasks[i]                    = new HeightsComputationTask.DataForTask();
                dataForTasks[i].numTasks           = numParallelTasks;
                dataForTasks[i].currentTask        = i;
                dataForTasks[i].HeightmapDimension = HeightmapDimension;
                dataForTasks[i].MaxTerrainHeight   = MaxTerrainHeight;
                dataForTasks[i].div                = div;
                dataForTasks[i].shm                = shm;
                dataForTasks[i].lhm                = lhm;
                dataForTasks[i].mapPixel           = mapPixel;
                ThreadPool.QueueUserWorkItem(heightsComputationTask.ProcessTaskThread, dataForTasks[i]);
            }

            // wait for all tasks to finish computation
            WaitHandle.WaitAll(doneEvents);

            // computed average and max height in a second pass (after threaded tasks computed all heights)
            float averageHeight = 0;
            float maxHeight     = float.MinValue;

            int dim = HeightmapDimension;

            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    // get sample
                    float height = mapPixel.heightmapSamples[y, x];

                    // Accumulate average height
                    averageHeight += height;

                    // Get max height
                    if (height > maxHeight)
                    {
                        maxHeight = height;
                    }
                }
            }

            // Average and max heights are passed back for locations
            mapPixel.averageHeight = (averageHeight /= (float)(dim * dim));
            mapPixel.maxHeight     = maxHeight;

            //long totalTime = stopwatch.ElapsedMilliseconds - startTime;
            //DaggerfallUnity.LogMessage(string.Format("GenerateSamples took: {0}ms", totalTime), true);
        }
        // Set location tilemap data
        public static void SetLocationTiles(ref MapPixelData mapPixel)
        {
            //const int tileDim = 16;
            //const int chunkDim = 8;

            DaggerfallUnity dfUnity = DaggerfallUnity.Instance;

            // Get location
            DFLocation location = dfUnity.ContentReader.MapFileReader.GetLocation(mapPixel.mapRegionIndex, mapPixel.mapLocationIndex);

            // Centre location tiles inside terrain area
            //int startX = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Width * tileDim) / 2;
            //int startY = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Height * tileDim) / 2;

            // Position tiles inside terrain area
            int width = location.Exterior.ExteriorData.Width;
            int height = location.Exterior.ExteriorData.Height;
            DFPosition tilePos = TerrainHelper.GetLocationTerrainTileOrigin(width, height);

            // Full 8x8 locations have "terrain blend space" around walls to smooth down random terrain towards flat area.
            // This is indicated by texture index > 55 (ground texture range is 0-55), larger values indicate blend space.
            // We need to know rect of actual city area so we can use blend space outside walls.
            int xmin = int.MaxValue, ymin = int.MaxValue;
            int xmax = 0, ymax = 0;

            // Iterate blocks of this location
            for (int blockY = 0; blockY < location.Exterior.ExteriorData.Height; blockY++)
            {
                for (int blockX = 0; blockX < location.Exterior.ExteriorData.Width; blockX++)
                {
                    // Get block data
                    DFBlock block;
                    string blockName = dfUnity.ContentReader.MapFileReader.GetRmbBlockName(ref location, blockX, blockY);
                    if (!dfUnity.ContentReader.GetBlock(blockName, out block))
                        continue;

                    // Copy ground tile info
                    for (int tileY = 0; tileY < RMBLayout.RMBTilesPerBlock; tileY++)
                    {
                        for (int tileX = 0; tileX < RMBLayout.RMBTilesPerBlock; tileX++)
                        {
                            DFBlock.RmbGroundTiles tile = block.RmbBlock.FldHeader.GroundData.GroundTiles[tileX, (RMBLayout.RMBTilesPerBlock - 1) - tileY];
                            int xpos = tilePos.X + blockX * RMBLayout.RMBTilesPerBlock + tileX;
                            int ypos = tilePos.Y + blockY * RMBLayout.RMBTilesPerBlock + tileY;

                            int record = tile.TextureRecord;
                            if (tile.TextureRecord < 56)
                            {
                                // Track interior bounds of location tiled area
                                if (xpos < xmin) xmin = xpos;
                                if (xpos > xmax) xmax = xpos;
                                if (ypos < ymin) ymin = ypos;
                                if (ypos > ymax) ymax = ypos;

                                // Store texture data from block
                                mapPixel.tilemapSamples[xpos, ypos].record = record;
                                mapPixel.tilemapSamples[xpos, ypos].flip = tile.IsFlipped;
                                mapPixel.tilemapSamples[xpos, ypos].rotate = tile.IsRotated;
                                mapPixel.tilemapSamples[xpos, ypos].location = true;
                            }
                        }
                    }
                }
            }

            // Update location rect with extra clearance
            const int extraClearance = 2;
            Rect locationRect = new Rect();
            locationRect.xMin = xmin - extraClearance;
            locationRect.xMax = xmax + extraClearance;
            locationRect.yMin = ymin - extraClearance;
            locationRect.yMax = ymax + extraClearance;
            mapPixel.locationRect = locationRect;
        }
 public override void GenerateSamples(ref MapPixelData mapPixel)
 {
     mapPixel.tilemapSamples = new TilemapSample[MapsFile.WorldMapTileDim, MapsFile.WorldMapTileDim];
     mapPixel.heightmapSamples = new float[HeightmapDimension, HeightmapDimension];
 }
        public static float GetClampedHeight(ref MapPixelData mapPixel, int heightmapDimension, float u, float v)
        {
            int x = (int)Mathf.Clamp(heightmapDimension * u, 0, heightmapDimension - 1);
            int y = (int)Mathf.Clamp(heightmapDimension * v, 0, heightmapDimension - 1);

            return mapPixel.heightmapSamples[y, x];
        }
        // Flattens location terrain and blends flat area with surrounding terrain
        // Not entirely happy with this, need to revisit later
        public static void FlattenLocationTerrain(ContentReader contentReader, ref MapPixelData mapPixel)
        {
            // Get range between bounds of sample data and interior location rect
            // The location rect is always smaller than the sample area
            float leftRange = 1f / (mapPixel.locationRect.xMin);
            float topRange = 1f / (mapPixel.locationRect.yMin);
            float rightRange = 1f / (terrainSampleDim - mapPixel.locationRect.xMax);
            float bottomRange = 1f / (terrainSampleDim - mapPixel.locationRect.yMax);

            float desiredHeight = mapPixel.averageHeight;
            float strength = 0;
            float u, v;
            for (int y = 1; y < terrainSampleDim - 1; y++)
            {
                for (int x = 1; x < terrainSampleDim - 1; x++)
                {
                    // Create a height scale from location to edge of terrain using
                    // linear interpolation on straight edges and bilinear in corners
                    if (x <= mapPixel.locationRect.xMin && y >= mapPixel.locationRect.yMin && y <= mapPixel.locationRect.yMax)
                    {
                        strength = x * leftRange;
                    }
                    else if (x >= mapPixel.locationRect.xMax && y >= mapPixel.locationRect.yMin && y <= mapPixel.locationRect.yMax)
                    {
                        strength = (terrainSampleDim - x) * rightRange;
                    }
                    else if (y <= mapPixel.locationRect.yMin && x >= mapPixel.locationRect.xMin && x <= mapPixel.locationRect.xMax)
                    {
                        strength = y * topRange;
                    }
                    else if (y >= mapPixel.locationRect.yMax && x >= mapPixel.locationRect.xMin && x <= mapPixel.locationRect.xMax)
                    {
                        strength = (terrainSampleDim - y) * bottomRange;
                    }
                    else if (x <= mapPixel.locationRect.xMin && y <= mapPixel.locationRect.yMin)
                    {
                        u = x * leftRange;
                        v = y * topRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }
                    else if (x >= mapPixel.locationRect.xMax && y <= mapPixel.locationRect.yMin)
                    {
                        u = (terrainSampleDim - x) * rightRange;
                        v = y * topRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }
                    else if (x <= mapPixel.locationRect.xMin && y >= mapPixel.locationRect.yMax)
                    {
                        u = x * leftRange;
                        v = (terrainSampleDim - y) * bottomRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }
                    else if (x >= mapPixel.locationRect.xMax && y >= mapPixel.locationRect.yMax)
                    {
                        u = (terrainSampleDim - x) * rightRange;
                        v = (terrainSampleDim - y) * bottomRange;
                        strength = BilinearInterpolator(0, 0, 0, 1, u, v);
                    }

                    // Apply a little noise to gradient so it doesn't look perfectly smooth
                    // Noise strength is the inverse of scalemap strength
                    float extraNoise = GetNoise(contentReader, x, y, 0.1f, 0.5f, 0.5f, 1) * extraNoiseScale * (1f - strength);

                    int offset = y * terrainSampleDim + x;
                    float curHeight = mapPixel.samples[offset].scaledHeight;
                    if (!mapPixel.samples[offset].location)
                    {
                        mapPixel.samples[offset].scaledHeight = (desiredHeight * strength) + (curHeight * (1 - strength)) + extraNoise;
                    }
                    else
                    {
                        mapPixel.samples[offset].scaledHeight = desiredHeight;
                    }
                }
            }
        }
 // Set all sample tiles to same base index
 public static void FillTilemapSamples(ref MapPixelData mapPixel, byte record)
 {
     for (int y = 0; y < MapsFile.WorldMapTileDim; y++)
     {
         for (int x = 0; x < MapsFile.WorldMapTileDim; x++)
         {
             mapPixel.tilemapSamples[x, y].record = record;
         }
     }
 }
        void CacheTileData(ITerrainSampler terrainSampler, ref MapPixelData mapData)
        {
            // Create array if required
            int dim = MapsFile.WorldMapTileDim + 1;
            if (tileData == null)
                tileData = new int[dim, dim];

            // Populate array with tile metadata
            for (int y = 0; y < dim; y++)
            {
                for (int x = 0; x < dim; x++)
                {
                    // Height sample for ocean and beach tiles
                    float height = TerrainHelper.GetClampedHeight(
                        ref mapData,
                        terrainSampler.HeightmapDimension,
                        (float)x / (float)dim,
                        (float)y / (float)dim) * terrainSampler.MaxTerrainHeight;

                    // Ocean texture
                    if (height <= terrainSampler.OceanElevation)
                    {
                        tileData[x, y] = water;
                        continue;
                    }

                    // Get latitude and longitude of this tile
                    int latitude = (int)(mapData.mapPixelX * MapsFile.WorldMapTileDim + x);
                    int longitude = (int)(MapsFile.MaxWorldTileCoordZ - mapData.mapPixelY * MapsFile.WorldMapTileDim + y);

                    // Beach texture
                    // Adds a little +/- randomness to threshold so beach line isn't too regular
                    if (height <= terrainSampler.BeachElevation + UnityEngine.Random.Range(-1.5f, 1.5f))
                    {
                        tileData[x, y] = dirt;
                        continue;
                    }

                    // Set texture tile using weighted noise
                    float weight = 0;
                    weight += NoiseWeight(latitude, longitude);
                    // TODO: Add other weights to influence texture tile generation
                    tileData[x, y] = GetWeightedRecord(weight);
                }
            }
        }
        /// <summary>
        /// Updates map pixel data based on current coordinates.
        /// Must be called before other data update methods.
        /// </summary>
        public void UpdateMapPixelData(TerrainTexturing terrainTexturing = null)
        {
            if (!ReadyCheck())
                return;

            // Get basic terrain data
            MapData = TerrainHelper.GetMapPixelData(dfUnity.ContentReader, MapPixelX, MapPixelY);
            TerrainHelper.GenerateSamples(dfUnity.ContentReader, ref MapData);

            // Handle terrain with location
            if (MapData.hasLocation)
            {
                TerrainHelper.SetLocationTiles(dfUnity.ContentReader, ref MapData);
                TerrainHelper.FlattenLocationTerrain(dfUnity.ContentReader, ref MapData);
            }

            // Set textures
            if (terrainTexturing != null)
            {
                terrainTexturing.AssignTiles(ref MapData);
            }
        }
        // Flattens location terrain and blends with surrounding terrain
        public static void BlendLocationTerrain(ref MapPixelData mapPixel, float noiseStrength = 4f)
        {
            int heightmapDimension = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension;

            // Convert from rect in tilemap space to interior corners in 0-1 range
            float xMin = mapPixel.locationRect.xMin / MapsFile.WorldMapTileDim;
            float xMax = mapPixel.locationRect.xMax / MapsFile.WorldMapTileDim;
            float yMin = mapPixel.locationRect.yMin / MapsFile.WorldMapTileDim;
            float yMax = mapPixel.locationRect.yMax / MapsFile.WorldMapTileDim;

            // Scale values for converting blend space into 0-1 range
            float leftScale   = 1 / xMin;
            float rightScale  = 1 / (1 - xMax);
            float topScale    = 1 / yMin;
            float bottomScale = 1 / (1 - yMax);

            // Flatten location area and blend with surrounding heights
            float strength     = 0;
            float targetHeight = mapPixel.averageHeight;

            for (int y = 0; y < heightmapDimension; y++)
            {
                float v       = (float)y / (float)(heightmapDimension - 1);
                bool  insideY = (v >= yMin && v <= yMax);

                for (int x = 0; x < heightmapDimension; x++)
                {
                    float u       = (float)x / (float)(heightmapDimension - 1);
                    bool  insideX = (u >= xMin && u <= xMax);

                    float height = mapPixel.heightmapSamples[y, x];

                    if (insideX || insideY)
                    {
                        if (insideY && u <= xMin)
                        {
                            strength = u * leftScale;
                        }
                        else if (insideY && u >= xMax)
                        {
                            strength = (1 - u) * rightScale;
                        }
                        else if (insideX && v <= yMin)
                        {
                            strength = v * topScale;
                        }
                        else if (insideX && v >= yMax)
                        {
                            strength = (1 - v) * bottomScale;
                        }
                    }
                    else
                    {
                        float xs = 0, ys = 0;
                        if (u <= xMin)
                        {
                            xs = u * leftScale;
                        }
                        else if (u >= xMax)
                        {
                            xs = (1 - u) * rightScale;
                        }
                        if (v <= yMin)
                        {
                            ys = v * topScale;
                        }
                        else if (v >= yMax)
                        {
                            ys = (1 - v) * bottomScale;
                        }
                        strength = BilinearInterpolator(0, 0, 0, 1, xs, ys);
                    }

                    if (insideX && insideY)
                    {
                        height = targetHeight;
                    }
                    else
                    {
                        height = Mathf.Lerp(height, targetHeight, strength);
                    }

                    mapPixel.heightmapSamples[y, x] = height;
                }
            }
        }
 public abstract void GenerateSamples(ref MapPixelData mapPixel);
Exemple #48
0
        // Set texture and height data for city tiles
        public static void SetLocationTiles(ContentReader contentReader, ref MapPixelData mapPixel)
        {
            const int tileDim  = 16;
            const int chunkDim = 8;

            // Get location
            DFLocation location = contentReader.MapFileReader.GetLocation(mapPixel.mapRegionIndex, mapPixel.mapLocationIndex);

            // Centre location tiles inside terrain area
            int startX = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Width * tileDim) / 2;
            int startY = ((chunkDim * tileDim) - location.Exterior.ExteriorData.Height * tileDim) / 2;

            // Full 8x8 locations have "terrain blend space" around walls to smooth down random terrain towards flat area.
            // This is indicated by texture index > 55 (ground texture range is 0-55), larger values indicate blend space.
            // We need to know rect of actual city area so we can use blend space outside walls.
            int xmin = terrainSampleDim, ymin = terrainSampleDim;
            int xmax = 0, ymax = 0;

            // Iterate blocks of this location
            for (int blockY = 0; blockY < location.Exterior.ExteriorData.Height; blockY++)
            {
                for (int blockX = 0; blockX < location.Exterior.ExteriorData.Width; blockX++)
                {
                    // Get block data
                    DFBlock block;
                    string  blockName = contentReader.MapFileReader.GetRmbBlockName(ref location, blockX, blockY);
                    if (!contentReader.GetBlock(blockName, out block))
                    {
                        continue;
                    }

                    // Copy ground tile info
                    for (int tileY = 0; tileY < tileDim; tileY++)
                    {
                        for (int tileX = 0; tileX < tileDim; tileX++)
                        {
                            DFBlock.RmbGroundTiles tile = block.RmbBlock.FldHeader.GroundData.GroundTiles[tileX, (tileDim - 1) - tileY];
                            int xpos   = startX + blockX * tileDim + tileX;
                            int ypos   = startY + blockY * tileDim + tileY;
                            int offset = (ypos * terrainSampleDim) + xpos;

                            int record = tile.TextureRecord;
                            if (tile.TextureRecord < 56)
                            {
                                // Track interior bounds of location tiled area
                                if (xpos < xmin)
                                {
                                    xmin = xpos;
                                }
                                if (xpos > xmax)
                                {
                                    xmax = xpos;
                                }
                                if (ypos < ymin)
                                {
                                    ymin = ypos;
                                }
                                if (ypos > ymax)
                                {
                                    ymax = ypos;
                                }

                                // Store texture data from block
                                mapPixel.samples[offset].record   = record;
                                mapPixel.samples[offset].flip     = tile.IsFlipped;
                                mapPixel.samples[offset].rotate   = tile.IsRotated;
                                mapPixel.samples[offset].location = true;
                            }
                        }
                    }
                }
            }

            // Update location rect with extra clearance
            const int extraClearance = 2;
            Rect      locationRect = new Rect();

            locationRect.xMin     = xmin - extraClearance;
            locationRect.xMax     = xmax + extraClearance;
            locationRect.yMin     = ymin - extraClearance;
            locationRect.yMax     = ymax + extraClearance;
            mapPixel.locationRect = locationRect;
        }