// 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); }
/// <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; } } }
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)); }
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)); }
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); } } }
// 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]; } } } }
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]; } } } }
/// <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); }
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; }
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; }
// 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]; }
// 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; }
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);
// 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; }