// Copies climate data from source to destination if destination is ocean // Used to dilate climate data around shorelines as a pre-process private static void TransferLandToOcean(ContentReader contentReader, ref byte[] dstClimateArray, int srcX, int srcY, int dstX, int dstY) { const int oceanClimate = 223; // Source must be land int srcOffset = srcY * PakFile.pakWidthValue + (srcX + 1); int srcClimate = contentReader.MapFileReader.ClimateFile.Buffer[srcOffset]; if (srcClimate == oceanClimate) return; // Destination must be ocean int dstOffset = dstY * PakFile.pakWidthValue + (dstX + 1); int dstClimate = contentReader.MapFileReader.ClimateFile.Buffer[dstOffset]; if (dstClimate == oceanClimate) { dstClimateArray[dstOffset] = (byte)srcClimate; } }
/// <summary> /// initializes resources (mapDistanceSquaredFromWater, mapDistanceSquaredFromLocations, mapMultipliers) and smoothes small height map /// </summary> public static void InitImprovedWorldTerrain(ContentReader contentReader) { if (!init) { #if CREATE_PERSISTENT_LOCATION_RANGE_MAPS { int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; mapLocationRangeX = new byte[width * height]; mapLocationRangeY = new byte[width * height]; //int y = 204; //int x = 718; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { //MapPixelData MapData = TerrainHelper.GetMapPixelData(contentReader, x, y); //if (MapData.hasLocation) //{ // int locationRangeX = (int)MapData.locationRect.xMax - (int)MapData.locationRect.xMin; // int locationRangeY = (int)MapData.locationRect.yMax - (int)MapData.locationRect.yMin; //} ContentReader.MapSummary mapSummary; int regionIndex = -1, mapIndex = -1; bool hasLocation = contentReader.HasLocation(x, y, out mapSummary); if (hasLocation) { regionIndex = mapSummary.RegionIndex; mapIndex = mapSummary.MapIndex; DFLocation location = contentReader.MapFileReader.GetLocation(regionIndex, mapIndex); byte locationRangeX = location.Exterior.ExteriorData.Width; byte locationRangeY = location.Exterior.ExteriorData.Height; mapLocationRangeX[y * width + x] = locationRangeX; mapLocationRangeY[y * width + x] = locationRangeY; } } } // save to files FileStream ostream; ostream = new FileStream(Path.Combine(Application.dataPath, out_filepathMapLocationRangeX), FileMode.Create, FileAccess.Write); BinaryWriter writerMapLocationRangeX = new BinaryWriter(ostream, Encoding.UTF8); writerMapLocationRangeX.Write(mapLocationRangeX, 0, width * height); writerMapLocationRangeX.Close(); ostream.Close(); ostream = new FileStream(Path.Combine(Application.dataPath, out_filepathMapLocationRangeY), FileMode.Create, FileAccess.Write); BinaryWriter writerMapLocationRangeY = new BinaryWriter(ostream, Encoding.UTF8); writerMapLocationRangeY.Write(mapLocationRangeY, 0, width * height); writerMapLocationRangeY.Close(); ostream.Close(); } #else { int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; mapLocationRangeX = new byte[width * height]; mapLocationRangeY = new byte[width * height]; MemoryStream istream; TextAsset assetMapLocationRangeX = Resources.Load<TextAsset>(filenameMapLocationRangeX); if (assetMapLocationRangeX != null) { istream = new MemoryStream(assetMapLocationRangeX.bytes); BinaryReader readerMapLocationRangeX = new BinaryReader(istream, Encoding.UTF8); readerMapLocationRangeX.Read(mapLocationRangeX, 0, width * height); readerMapLocationRangeX.Close(); istream.Close(); } TextAsset assetMapLocationRangeY = Resources.Load<TextAsset>(filenameMapLocationRangeY); if (assetMapLocationRangeY) { istream = new MemoryStream(assetMapLocationRangeY.bytes); BinaryReader readerMapLocationRangeY = new BinaryReader(istream, Encoding.UTF8); readerMapLocationRangeY.Read(mapLocationRangeY, 0, width * height); readerMapLocationRangeY.Close(); istream.Close(); } //FileStream istream; //istream = new FileStream(filepathMapLocationRangeX, FileMode.Open, FileAccess.Read); //BinaryReader readerMapLocationRangeX = new BinaryReader(istream, Encoding.UTF8); //readerMapLocationRangeX.Read(mapLocationRangeX, 0, width * height); //readerMapLocationRangeX.Close(); //istream.Close(); //istream = new FileStream(filepathMapLocationRangeY, FileMode.Open, FileAccess.Read); //BinaryReader readerMapLocationRangeY = new BinaryReader(istream, Encoding.UTF8); //readerMapLocationRangeY.Read(mapLocationRangeY, 0, width * height); //readerMapLocationRangeY.Close(); //istream.Close(); } #endif if (mapDistanceSquaredFromWater == null) { byte[] heightMapArray = contentReader.WoodsFileReader.Buffer.Clone() as byte[]; int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (heightMapArray[y * width + x] <= 2) heightMapArray[y * width + x] = 1; else heightMapArray[y * width + x] = 0; } } //now set image borders to "water" (this is a workaround to prevent mountains to become too high in north-east and south-east edge of map) for (int y = 0; y < height; y++) { heightMapArray[y * width + 0] = 1; heightMapArray[y * width + width - 1] = 1; } for (int x = 0; x < width; x++) { heightMapArray[0 * width + x] = 1; heightMapArray[(height - 1) * width + x] = 1; } mapDistanceSquaredFromWater = imageDistanceTransform(heightMapArray, width, height, 1); heightMapArray = null; } if (mapDistanceSquaredFromLocations == null) { int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; mapLocations = new byte[width * height]; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { ContentReader.MapSummary summary; if (contentReader.HasLocation(x + 1, height - 1 - y, out summary)) mapLocations[y * width + x] = 1; else mapLocations[y * width + x] = 0; } } mapDistanceSquaredFromLocations = imageDistanceTransform(mapLocations, width, height, 1); } if (mapMultipliers == null) { int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; mapMultipliers = new float[width * height]; // compute the multiplier and store it in mapMultipliers for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float distanceFromWater = (float)Math.Sqrt(mapDistanceSquaredFromWater[y * WoodsFile.mapWidthValue + x]); float distanceFromLocation = (float)Math.Sqrt(mapDistanceSquaredFromLocations[y * WoodsFile.mapWidthValue + x]); float multiplierLocation = (distanceFromLocation * extraExaggerationFactorLocationDistance + 1.0f); // terrain distant from location gets extra exaggeration if (distanceFromWater < minDistanceFromWaterForExtraExaggeration) // except if it is near water multiplierLocation = 1.0f; mapMultipliers[y * width + x] = (Math.Min(maxHeightsExaggerationMultiplier, multiplierLocation * Math.Max(1.0f, distanceFromWater * exaggerationFactorWaterDistance))); } } // multipliedMap gets smoothed float[] newmapMultipliers = mapMultipliers.Clone() as float[]; float[,] weights = { { 0.0625f, 0.125f, 0.0625f }, { 0.125f, 0.25f, 0.125f }, { 0.0625f, 0.125f, 0.0625f } }; for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (mapDistanceSquaredFromLocations[y * width + x] <= 2) // at and around locations ( <= 2 ... only map pixels in 8-connected neighborhood (distanceFromLocationMaps stores squared distances...)) { newmapMultipliers[y * width + x] = weights[0, 0] * mapMultipliers[(y - 1) * width + (x - 1)] + weights[0, 1] * mapMultipliers[(y - 1) * width + (x)] + weights[0, 2] * mapMultipliers[(y - 1) * width + (x + 1)] + weights[1, 0] * mapMultipliers[(y - 0) * width + (x - 1)] + weights[1, 1] * mapMultipliers[(y - 0) * width + (x)] + weights[1, 2] * mapMultipliers[(y - 0) * width + (x + 1)] + weights[2, 0] * mapMultipliers[(y + 1) * width + (x - 1)] + weights[2, 1] * mapMultipliers[(y + 1) * width + (x)] + weights[2, 2] * mapMultipliers[(y + 1) * width + (x + 1)]; } } } mapMultipliers = newmapMultipliers; newmapMultipliers = null; weights = null; } //the height map gets smoothed as well { int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; byte[] heightMapBuffer = contentReader.WoodsFileReader.Buffer.Clone() as byte[]; int[,] intWeights = { { 1, 2, 1 }, { 2, 4, 2 }, { 1, 2, 1 } }; for (int y = 1; y < height - 1; y++) { for (int x = 1; x < width - 1; x++) { if (mapDistanceSquaredFromWater[y * width + x] > 0) // check if squared distance from water is greater than zero -> if it is no water pixel { int value = intWeights[0, 0] * (int)heightMapBuffer[(y - 1) * width + (x - 1)] + intWeights[0, 1] * (int)heightMapBuffer[(y - 1) * width + (x)] + intWeights[0, 2] * (int)heightMapBuffer[(y - 1) * width + (x + 1)] + intWeights[1, 0] * (int)heightMapBuffer[(y - 0) * width + (x - 1)] + intWeights[1, 1] * (int)heightMapBuffer[(y - 0) * width + (x)] + intWeights[1, 2] * (int)heightMapBuffer[(y - 0) * width + (x + 1)] + intWeights[2, 0] * (int)heightMapBuffer[(y + 1) * width + (x - 1)] + intWeights[2, 1] * (int)heightMapBuffer[(y + 1) * width + (x)] + intWeights[2, 2] * (int)heightMapBuffer[(y + 1) * width + (x + 1)]; heightMapBuffer[y * width + x] = (byte)(value / 16); } } } contentReader.WoodsFileReader.Buffer = heightMapBuffer; heightMapBuffer = null; intWeights = null; } // build tree coverage map if (mapTreeCoverage == null) { int width = WoodsFile.mapWidthValue; int height = WoodsFile.mapHeightValue; mapTreeCoverage = new byte[width * height]; #if !LOAD_TREE_COVERAGE_MAP { float startTreeCoverageAtElevation = ImprovedTerrainSampler.baseHeightScale * 2.0f; // ImprovedTerrainSampler.scaledBeachElevation; float minTreeCoverageSaturated = ImprovedTerrainSampler.baseHeightScale * 6.0f; float maxTreeCoverageSaturated = ImprovedTerrainSampler.baseHeightScale * 60.0f; float endTreeCoverageAtElevation = ImprovedTerrainSampler.baseHeightScale * 80.0f; //float maxElevation = 0.0f; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int readIndex = (height - 1 - y) * width + x; float w = 0.0f; //float elevation = ((float)contentReader.WoodsFileReader.Buffer[(height - 1 - y) * width + x]) / 255.0f; // *mapMultipliers[index]; float elevation = ((float)contentReader.WoodsFileReader.Buffer[readIndex]) * mapMultipliers[readIndex]; //maxElevation = Math.Max(maxElevation, elevation); if ((elevation > minTreeCoverageSaturated) && (elevation < maxTreeCoverageSaturated)) { w = 1.0f; } else if ((elevation >= startTreeCoverageAtElevation) && (elevation <= minTreeCoverageSaturated)) { w = (elevation - startTreeCoverageAtElevation) / (minTreeCoverageSaturated - startTreeCoverageAtElevation); } else if ((elevation >= maxTreeCoverageSaturated) && (elevation <= endTreeCoverageAtElevation)) { w = 1.0f - ((elevation - maxTreeCoverageSaturated) / (endTreeCoverageAtElevation - maxTreeCoverageSaturated)); } //w = 0.65f * w + 0.35f * Math.Min(6.0f, (float)Math.Sqrt(mapDistanceSquaredFromLocations[y * width + x])) / 6.0f; mapTreeCoverage[(y) * width + x] = Convert.ToByte(w * 255.0f); //if (elevation>0.05f) // mapTreeCoverage[index] = Convert.ToByte(250); //w * 255.0f); //else mapTreeCoverage[index] = Convert.ToByte(0); //if (elevation >= startTreeCoverageAtElevation) //{ // mapTreeCoverage[(y) * width + x] = Convert.ToByte(255.0f); //} else{ // mapTreeCoverage[(y) * width + x] = Convert.ToByte(0.0f); //} } } } #else { MemoryStream istream; TextAsset assetMapTreeCoverage = Resources.Load<TextAsset>(filenameTreeCoverageMap); if (assetMapTreeCoverage) { istream = new MemoryStream(assetMapTreeCoverage.bytes); BinaryReader readerMapTreeCoverage = new BinaryReader(istream, Encoding.UTF8); readerMapTreeCoverage.Read(mapTreeCoverage, 0, width * height); readerMapTreeCoverage.Close(); istream.Close(); } } #endif #if CREATE_PERSISTENT_TREE_COVERAGE_MAP { FileStream ostream = new FileStream(Path.Combine(Application.dataPath, out_filepathOutTreeCoverageMap), FileMode.Create, FileAccess.Write); BinaryWriter writerMapTreeCoverage = new BinaryWriter(ostream, Encoding.UTF8); writerMapTreeCoverage.Write(mapTreeCoverage, 0, width * height); writerMapTreeCoverage.Close(); ostream.Close(); } #endif //Debug.Log(string.Format("max elevation: {0}", maxElevation)); } init = true; } }
/// <summary> /// If a location map pixel is on a gradient greater than threshold, then /// smooth surrounding Moore neighbourhood with location height /// </summary> public static void SmoothLocationNeighbourhood(ContentReader contentReader, int threshold = 20) { //System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); //long startTime = stopwatch.ElapsedMilliseconds; // Get in-memory height array byte[] heightArray = contentReader.WoodsFileReader.Buffer; // Search for locations for (int y = 1; y < WoodsFile.mapHeightValue - 1; y++) { for (int x = 1; x < WoodsFile.mapWidthValue - 1; x++) { ContentReader.MapSummary summary; if (contentReader.HasLocation(x, y, out summary)) { // Use Sobel filter for gradient float x0y0 = heightArray[y * WoodsFile.mapWidthValue + x]; float x1y0 = heightArray[y * WoodsFile.mapWidthValue + (x + 1)]; float x0y1 = heightArray[(y + 1) * WoodsFile.mapWidthValue + x]; float gradient = GetGradient(x0y0, x1y0, x0y1); if (gradient > threshold) { AverageHeights(ref heightArray, x, y); } } } } //long totalTime = stopwatch.ElapsedMilliseconds - startTime; //DaggerfallUnity.LogMessage(string.Format("Time to smooth location neighbourhoods: {0}ms", totalTime), true); }
// Get noise sample at coordinates private static float GetNoise( ContentReader reader, float x, float y, float frequency, float amplitude, float persistance, int octaves) { float finalValue = 0f; for (int i = 0; i < octaves; ++i) { finalValue += reader.Noise.Generate(x * frequency, y * frequency) * amplitude; frequency *= 2.0f; amplitude *= persistance; } return Mathf.Clamp(finalValue, -1, 1); }
/// <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; }
// 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; }
/// <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(); }
// 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; } } } }
/// <summary> /// Terrain interpolation causes Daggerfall's square coastline to become nicely raised and curvy. /// A side effect of this is that underwater climate areas are raised above sea-level. /// This function dilates coastal land climate into nearby ocean to hide this issue. /// Intended to be called once at startup. Modifies runtime copy of CLIMATE.PAK buffer. /// </summary> public static void DilateCoastalClimate(ContentReader contentReader, int passes) { //System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew(); //long startTime = stopwatch.ElapsedMilliseconds; for (int pass = 0; pass < passes; pass++) { // Get clone of in-memory climate array byte[] climateArray = contentReader.MapFileReader.ClimateFile.Buffer.Clone() as byte[]; // Dilate coastal areas for (int y = 1; y < WoodsFile.mapHeightValue - 1; y++) { for (int x = 1; x < WoodsFile.mapWidthValue - 1; x++) { // Transfer climate of this pixel to any ocean pixel in Moore neighbourhood TransferLandToOcean(contentReader, ref climateArray, x, y, x - 1, y - 1); TransferLandToOcean(contentReader, ref climateArray, x, y, x, y - 1); TransferLandToOcean(contentReader, ref climateArray, x, y, x + 1, y - 1); TransferLandToOcean(contentReader, ref climateArray, x, y, x - 1, y); TransferLandToOcean(contentReader, ref climateArray, x, y, x + 1, y); TransferLandToOcean(contentReader, ref climateArray, x, y, x - 1, y + 1); TransferLandToOcean(contentReader, ref climateArray, x, y, x, y + 1); TransferLandToOcean(contentReader, ref climateArray, x, y, x + 1, y + 1); } } // Store modified climate array contentReader.MapFileReader.ClimateFile.Buffer = climateArray; } //long totalTime = stopwatch.ElapsedMilliseconds - startTime; //DaggerfallUnity.LogMessage(string.Format("Time to dilate coastal climates: {0}ms", totalTime), true); }
private void SetupContentReaders(bool force = false) { if (reader == null || force) { // Ensure content readers available even when path not valid if (isPathValidated) { DaggerfallUnity.LogMessage(string.Format("Setting up content readers with arena2 path '{0}'.", Arena2Path)); reader = new ContentReader(Arena2Path); } else { DaggerfallUnity.LogMessage(string.Format("Setting up content readers without arena2 path. Not all features will be available.")); reader = new ContentReader(string.Empty); } } }
private void SetupContentReaders(bool force = false) { if (reader == null || force) { // Ensure content readers available even when path not valid if (isPathValidated) { DaggerfallUnity.LogMessage(string.Format("Setting up content readers with arena2 path '{0}'.", Arena2Path)); reader = new ContentReader(Arena2Path); } else { DaggerfallUnity.LogMessage(string.Format("Setting up content readers without arena2 path. Not all features will be available.")); reader = new ContentReader(string.Empty); } // Allow external code to set their own terrain sampler at start RaiseOnSetTerrainSamplerEvent(); } }