public override JobHandle ScheduleGenerateSamplesJob(ref MapPixelData mapPixel) { GenerateSamples(ref mapPixel); return(new JobHandle()); }
public override void GenerateSamples(ref MapPixelData mapPixel) { mapPixel.maxHeight = MaxTerrainHeight; var computer = TerrainComputer.Create(mapPixel, this); computer.DispatchAndProcess(InterestingTerrains.csPrototype, ref mapPixel, InterestingTerrains.instance.csParams); }
void HandleBaseMapSampleParams(ref MapPixelData mapPixel, ref ComputeShader cs, int k) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; // Divisor ensures continuous 0-1 range of height samples float div = (sampler.HeightmapDimension - 1) / 3f; // Read neighbouring height samples for this map pixel int mx = mapPixel.mapPixelX; int my = mapPixel.mapPixelY; int sDim = 4; var shmByte = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange1Dim(mx - 2, my - 2, sDim); var shm = new float[shmByte.Length]; int i; for (i = 0; i < shm.Length; i++) { shm[i] = Convert.ToSingle(shmByte[i]); } // Convert & flatten large height samples 2d array into 1d native array. byte[,] lhm2 = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3); float[] lhm = new float[lhm2.Length]; int lDim = lhm2.GetLength(0); i = 0; for (int y = 0; y < lDim; y++) { for (int x = 0; x < lDim; x++) { lhm[i++] = Convert.ToSingle(lhm2[x, y]); } } // Extract height samples for all chunks int hDim = sampler.HeightmapDimension; // Create buffers with extracted heightmap data heightmapBuffers.shm = new ComputeBuffer(shm.Length, sizeof(float)); heightmapBuffers.shm.SetData(shm); heightmapBuffers.lhm = new ComputeBuffer(lhm.Length, sizeof(float)); heightmapBuffers.lhm.SetData(lhm); // Assign properties to CS cs.SetBuffer(k, "shm", heightmapBuffers.shm); cs.SetBuffer(k, "lhm", heightmapBuffers.lhm); cs.SetInt("sd", sDim); cs.SetInt("ld", lDim); cs.SetInt("hDim", hDim); cs.SetFloat("div", div); cs.SetInt("mapPixelX", mapPixel.mapPixelX); cs.SetInt("mapPixelY", mapPixel.mapPixelY); cs.SetFloat("maxTerrainHeight", 2308.5f); cs.SetFloat("baseHeightScale", 8f); cs.SetFloat("noiseMapScale", 4f); cs.SetFloat("extraNoiseScale", 10f); cs.SetFloat("scaledOceanElevation", 27.2f); }
private void MessageReceiver(string message, object data, DFModMessageCallback callBack) { try { Vector2Int mpCoords; byte point; switch (message) { case GET_PATH_DATA: callBack?.Invoke(GET_PATH_DATA, roadTexturing.GetPathData((int)data)); break; case GET_ROAD_POINT: mpCoords = (Vector2Int)data; point = roadTexturing.GetPathDataPoint(BasicRoadsTexturing.roads, mpCoords.x, mpCoords.y); callBack?.Invoke(GET_ROAD_POINT, point); break; case GET_TRACK_POINT: mpCoords = (Vector2Int)data; point = roadTexturing.GetPathDataPoint(BasicRoadsTexturing.tracks, mpCoords.x, mpCoords.y); callBack?.Invoke(GET_TRACK_POINT, point); break; case GET_PATHS_POINT: mpCoords = (Vector2Int)data; byte roadPt = roadTexturing.GetPathDataPoint(BasicRoadsTexturing.roads, mpCoords.x, mpCoords.y); byte trackPt = roadTexturing.GetPathDataPoint(BasicRoadsTexturing.tracks, mpCoords.x, mpCoords.y); point = (byte)(roadPt | trackPt); callBack?.Invoke(GET_PATHS_POINT, point); break; case SCHEDULE_ROADS_JOB: // Get the parameters object[] paramArray = (object[])data; MapPixelData mapData = (MapPixelData)paramArray[0]; NativeArray <byte> tileData = (NativeArray <byte>)paramArray[1]; JobHandle dependencies = (JobHandle)paramArray[2]; // Instantiate PaintRoadsJob, schedule, then return job handle JobHandle paintRoadsHandle = roadTexturing.SchedulePaintRoadsJob(ref mapData, ref tileData, dependencies); callBack?.Invoke(SCHEDULE_ROADS_JOB, paintRoadsHandle); break; default: Debug.LogErrorFormat("{0}: unknown message received ({1}).", this, message); break; } } catch { Debug.LogErrorFormat("{0}: error handling message ({1}).", this, message); callBack?.Invoke("error", "Data passed is invalid for " + message); } }
public JobHandle SchedulePaintRoadsJob(ref MapPixelData mapData, ref NativeArray <byte> tileData, JobHandle dependencies) { // Assign tile data to terrain, painting paths in the process int pathsIndex = mapData.mapPixelX + (mapData.mapPixelY * MapsFile.MaxMapPixelX); byte roadDataPt = pathsData[roads][pathsIndex]; byte roadCorners = (byte)(InRange(pathsIndex) ? (pathsData[roads][pathsIndex + 1] & 0x5) | (pathsData[roads][pathsIndex - 1] & 0x50) : 0); byte trackDataPt = pathsData[tracks][pathsIndex]; byte trackCorners = (byte)(InRange(pathsIndex) ? (pathsData[tracks][pathsIndex + 1] & 0x5) | (pathsData[tracks][pathsIndex - 1] & 0x50) : 0); if (editorEnabled) { roadDataPt = BasicRoadsPathEditor.pathsData[roads][pathsIndex]; roadCorners = (byte)(InRange(pathsIndex) ? (BasicRoadsPathEditor.pathsData[roads][pathsIndex + 1] & 0x5) | (BasicRoadsPathEditor.pathsData[roads][pathsIndex - 1] & 0x50) : 0); trackDataPt = BasicRoadsPathEditor.pathsData[tracks][pathsIndex]; trackCorners = (byte)(InRange(pathsIndex) ? (BasicRoadsPathEditor.pathsData[tracks][pathsIndex + 1] & 0x5) | (BasicRoadsPathEditor.pathsData[tracks][pathsIndex - 1] & 0x50) : 0); } PaintRoadsJob paintRoadsJob = new PaintRoadsJob { tileData = tileData, tilemapData = mapData.tilemapData, tdDim = tileDataDim, tDim = assignTilesDim, locationRect = mapData.locationRect, midLo = (assignTilesDim / 2) - 1, midHi = assignTilesDim / 2, roadDataPt = roadDataPt, roadCorners = roadCorners, trackDataPt = trackDataPt, trackCorners = trackCorners, }; JobHandle paintRoadsHandle = paintRoadsJob.Schedule(assignTilesDim * assignTilesDim, 64, dependencies); JobHandle returnHandle = paintRoadsHandle; if (smoothPaths) { SmoothRoadsTerrainJob smoothRoadTerrainJob = new SmoothRoadsTerrainJob() { heightmapData = mapData.heightmapData, tilemapData = mapData.tilemapData, hDim = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension, tDim = assignTilesDim, locationRect = mapData.locationRect, }; JobHandle smoothRoadHandle = smoothRoadTerrainJob.Schedule(paintRoadsHandle); returnHandle = smoothRoadHandle; } return(returnHandle); }
/// <summary> /// Create a TerrainComputer for a specific mapData/terrain instance. /// </summary> /// <param name="mapPixelData"></param> /// <param name="sampler"></param> /// <returns></returns> public static TerrainComputer Create(MapPixelData mapPixelData, InterestingTerrainSampler sampler) { var tSize = Utility.GetTerrainVertexSize(); return(new TerrainComputer() { sampler = sampler, heightmapBuffers = BufferIO.CreateHeightmapBuffers(), heightmapResolution = (int)InterestingTerrains.settings.heightmapResolution, locationRect = mapPixelData.hasLocation ? mapPixelData.locationRect : new Rect(-10, -10, 1, 1), terrainPosition = Utility.GetTerrainVertexPosition(mapPixelData.mapPixelX, mapPixelData.mapPixelY), terrainSize = new Vector2(tSize, tSize) }); }
public override JobHandle ScheduleAssignTilesJob(ITerrainSampler terrainSampler, ref MapPixelData mapData, JobHandle dependencies, bool march = true) { // Cache tile data to minimise noise sampling during march (using default job) 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); // Schedule painting of roads, including smoothing if enabled JobHandle paintRoadsHandle = SchedulePaintRoadsJob(ref mapData, ref tileData, tileDataHandle); // Assign tile data to terrain (using default job) 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, paintRoadsHandle); // Add both working native arrays to disposal list. mapData.nativeArrayList.Add(tileData); mapData.nativeArrayList.Add(lookupData); return(assignTilesHandle); }
public override JobHandle ScheduleGenerateSamplesJob(ref MapPixelData mapPixel) { DaggerfallUnity dfUnity = DaggerfallUnity.Instance; // Divisor ensures continuous 0-1 range of tile samples float div = (float)(HeightmapDimension - 1) / 3f; // Read neighbouring height samples for this map pixel int mx = mapPixel.mapPixelX; int my = mapPixel.mapPixelY; // Seed random with terrain key UnityEngine.Random.InitState(TerrainHelper.MakeTerrainKey(mx, my)); byte[,] shm = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4); byte[,] lhm = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3); float[,] baseHeightValue = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); baseHeightValue[x, y] = shm[x, y] * ImprovedWorldTerrain.computeHeightMultiplier(mapPixelX, mapPixelY); } } float[,] waterMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { if (shm[x, y] <= 2) // mappixel is water { waterMap[x, y] = 0.0f; } else { waterMap[x, y] = 1.0f; } } } float[,] climateMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); climateMap[x, y] = GetNoiseMapScaleBasedOnClimate(mapPixelX, mapPixelY); } } float[,] waterDistanceMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); waterDistanceMap[x, y] = (float)Math.Sqrt(ImprovedWorldTerrain.MapDistanceSquaredFromWater[mapPixelY * WoodsFile.mapWidthValue + mapPixelX]); } } float[,] noiseHeightMultiplierMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { // interpolation multiplier taking near coast map pixels into account // (multiply with 0 at coast line and 1 at interpolationEndDistanceFromWaterForNoiseScaleMultiplier) float multFact = (Mathf.Min(interpolationEndDistanceFromWaterForNoiseScaleMultiplier, waterDistanceMap[x, y]) / interpolationEndDistanceFromWaterForNoiseScaleMultiplier); // blend watermap with climatemap taking into account multFact noiseHeightMultiplierMap[x, y] = waterMap[x, y] * climateMap[x, y] * multFact; } } float extraNoiseScaleBasedOnClimate = GetExtraNoiseScaleBasedOnClimate(mx, my); byte sDim = 4; NativeArray <float> baseHeightValueNativeArray = new NativeArray <float>(shm.Length, Allocator.TempJob); int i = 0; for (int y = 0; y < sDim; y++) { for (int x = 0; x < sDim; x++) { baseHeightValueNativeArray[i++] = baseHeightValue[x, y]; } } i = 0; NativeArray <float> noiseHeightMultiplierNativeArray = new NativeArray <float>(noiseHeightMultiplierMap.Length, Allocator.TempJob); for (int y = 0; y < sDim; y++) { for (int x = 0; x < sDim; x++) { noiseHeightMultiplierNativeArray[i++] = noiseHeightMultiplierMap[x, y]; } } // TODO - shortcut conversion & flattening. NativeArray <byte> lhmNativeArray = new NativeArray <byte>(lhm.Length, Allocator.TempJob); byte lDim = (byte)lhm.GetLength(0); i = 0; for (int y = 0; y < lDim; y++) { for (int x = 0; x < lDim; x++) { lhmNativeArray[i++] = lhm[x, y]; } } // Add the working native arrays to list for later disposal. mapPixel.nativeArrayList.Add(baseHeightValueNativeArray); mapPixel.nativeArrayList.Add(noiseHeightMultiplierNativeArray); mapPixel.nativeArrayList.Add(lhmNativeArray); // Extract height samples for all chunks int hDim = HeightmapDimension; GenerateSamplesJob generateSamplesJob = new GenerateSamplesJob { baseHeightValue = baseHeightValueNativeArray, lhm = lhmNativeArray, noiseHeightMultiplierMap = noiseHeightMultiplierNativeArray, heightmapData = mapPixel.heightmapData, sd = sDim, ld = lDim, hDim = hDim, div = div, mapPixelX = mapPixel.mapPixelX, mapPixelY = mapPixel.mapPixelY, maxTerrainHeight = MaxTerrainHeight, extraNoiseScaleBasedOnClimate = extraNoiseScaleBasedOnClimate, }; JobHandle generateSamplesHandle = generateSamplesJob.Schedule(hDim * hDim, 64); // Batch = 1 breaks it since shm not copied... test again later return(generateSamplesHandle); }
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.heightmapSamples = new float[HeightmapDimension, HeightmapDimension]; // Divisor ensures continuous 0-1 range of tile samples float div = (float)(HeightmapDimension - 1) / 3f; // Read neighbouring height samples for this map pixel int mx = mapPixel.mapPixelX; int my = mapPixel.mapPixelY; // Seed random with terrain key UnityEngine.Random.InitState(TerrainHelper.MakeTerrainKey(mx, my)); byte[,] shm = dfUnity.ContentReader.WoodsFileReader.GetHeightMapValuesRange(mx - 2, my - 2, 4); byte[,] lhm = dfUnity.ContentReader.WoodsFileReader.GetLargeHeightMapValuesRange(mx - 1, my, 3); float[,] baseHeightValue = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); baseHeightValue[x, y] = shm[x, y] * ImprovedWorldTerrain.computeHeightMultiplier(mapPixelX, mapPixelY); } } float[,] waterMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { if (shm[x, y] <= 2) // mappixel is water { waterMap[x, y] = 0.0f; } else { waterMap[x, y] = 1.0f; } } } float[,] climateMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); climateMap[x, y] = GetNoiseMapScaleBasedOnClimate(mapPixelX, mapPixelY); } } float[,] waterDistanceMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); waterDistanceMap[x, y] = (float)Math.Sqrt(ImprovedWorldTerrain.MapDistanceSquaredFromWater[mapPixelY * WoodsFile.mapWidthValue + mapPixelX]); } } float[,] noiseHeightMultiplierMap = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { // interpolation multiplier taking near coast map pixels into account // (multiply with 0 at coast line and 1 at interpolationEndDistanceFromWaterForNoiseScaleMultiplier) float multFact = (Mathf.Min(interpolationEndDistanceFromWaterForNoiseScaleMultiplier, waterDistanceMap[x, y]) / interpolationEndDistanceFromWaterForNoiseScaleMultiplier); // blend watermap with climatemap taking into account multFact noiseHeightMultiplierMap[x, y] = waterMap[x, y] * climateMap[x, y] * multFact; } } //float[,] noiseHeightMultiplierMap = new float[4, 4]; //for (int y = 0; y < 4; y++) //{ // for (int x = 0; x < 4; x++) // { // int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); // int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); // float climateValue = GetNoiseMapScaleBasedOnClimate(mapPixelX, mapPixelY); // float waterDistance = (float)Math.Sqrt(ImprovedWorldTerrain.MapDistanceSquaredFromWater[mapPixelY * WoodsFile.mapWidthValue + mapPixelX]); // float waterValue; // if (shm[x, y] <= 2) // mappixel is water // waterValue = 0.0f; // else // waterValue = 1.0f; // // interpolation multiplier taking near coast map pixels into account // // (multiply with 0 at coast line and 1 at interpolationEndDistanceFromWaterForNoiseScaleMultiplier) // float multFact = (Mathf.Min(interpolationEndDistanceFromWaterForNoiseScaleMultiplier, waterDistance) / interpolationEndDistanceFromWaterForNoiseScaleMultiplier); // // blend watermap with climatemap taking into account multFact // noiseHeightMultiplierMap[x, y] = waterValue * climateValue * multFact; // } //} //int numWorkerThreads = 0, completionPortThreads = 0; //int numMinWorkerThreads = 0, numMaxWorkerThreads = 0; //ThreadPool.GetAvailableThreads(out numWorkerThreads, out completionPortThreads); //ThreadPool.GetMinThreads(out numMinWorkerThreads, out completionPortThreads); //ThreadPool.GetMaxThreads(out numMaxWorkerThreads, out completionPortThreads); //Debug.Log(String.Format("available threads: {0}, numMinWorkerThreads: {1}, numMaxWorkerThreads: {2}", numWorkerThreads, numMinWorkerThreads, numMaxWorkerThreads)); float extraNoiseScaleBasedOnClimate = GetExtraNoiseScaleBasedOnClimate(mx, my); // the number of parallel tasks (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].baseHeightValue = baseHeightValue; dataForTasks[i].lhm = lhm; dataForTasks[i].noiseHeightMultiplierMap = noiseHeightMultiplierMap; dataForTasks[i].extraNoiseScaleBasedOnClimate = extraNoiseScaleBasedOnClimate; dataForTasks[i].mapPixel = mapPixel; ThreadPool.QueueUserWorkItem(heightsComputationTask.ThreadProc, 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); }
/// <summary> /// Works exactly like the default TerrainTexturer, except that it skips the GenerateTileDataJob /// and uses the tileData generated during terrain sampling instead. /// Use the mod messaging system to obtain tileData for a map pixel in a custom TerrainTexturer. /// </summary> /// /// To access tileData in a separate mod, you can use this code: /// <code> /// byte[] tData = null; /// ModManager.Instance.SendModMessage("Interesting Terrains", "getTileData", new int[] { mapData.mapPixelX, mapData.mapPixelY }, (string message, object data) => /// { /// if (message == "error") /// Debug.LogError(data as string); /// else /// tData = data as byte[]; /// }); /// </code> public override JobHandle ScheduleAssignTilesJob(ITerrainSampler terrainSampler, ref MapPixelData mapData, JobHandle dependencies, bool march = true) { // Load tile data generated by the Terrain Sampler var tData = InterestingTerrains.tileDataCache.Get(mapData.mapPixelX, mapData.mapPixelY); NativeArray <byte> tileData = new NativeArray <byte>(tData, Allocator.TempJob); // Schedule the paint roads jobs if basic roads mod is enabled JobHandle preAssignTilesHandle = dependencies; if (CompatibilityUtils.BasicRoadsLoaded) { ModManager.Instance.SendModMessage("BasicRoads", "scheduleRoadsJob", new object[] { mapData, tileData, dependencies }, (string message, object data) => { if (message == "error") { Debug.LogError(data as string); } else { preAssignTilesHandle = (JobHandle)data; } }); } // 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, preAssignTilesHandle); // 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) { 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 tile 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); float[,] multiplierValue = new float[4, 4]; for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { int mapPixelX = Math.Max(0, Math.Min(mx + x - 2, WoodsFile.mapWidthValue)); int mapPixelY = Math.Max(0, Math.Min(my + y - 2, WoodsFile.mapHeightValue)); multiplierValue[x, y] = ImprovedWorldTerrain.computeHeightMultiplier(mapPixelX, mapPixelY); } } // 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] * multiplierValue[0, 3], shm[1, 3] * multiplierValue[1, 3], shm[2, 3] * multiplierValue[2, 3], shm[3, 3] * multiplierValue[3, 3], sfracx); x2 = TerrainHelper.CubicInterpolator(shm[0, 2] * multiplierValue[0, 2], shm[1, 2] * multiplierValue[1, 2], shm[2, 2] * multiplierValue[2, 2], shm[3, 2] * multiplierValue[3, 2], sfracx); x3 = TerrainHelper.CubicInterpolator(shm[0, 1] * multiplierValue[0, 1], shm[1, 1] * multiplierValue[1, 1], shm[2, 1] * multiplierValue[2, 1], shm[3, 1] * multiplierValue[3, 1], sfracx); x4 = TerrainHelper.CubicInterpolator(shm[0, 0] * multiplierValue[0, 0], shm[1, 0] * multiplierValue[1, 0], shm[2, 0] * multiplierValue[2, 0], shm[3, 0] * multiplierValue[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; } // Accumulate average height averageHeight += scaledHeight; // Get max height if (scaledHeight > maxHeight) { maxHeight = scaledHeight; } // Set sample float height = Mathf.Clamp01(scaledHeight / MaxTerrainHeight); mapPixel.heightmapSamples[y, x] = height; } } // Average and max heights are passed back for locations mapPixel.averageHeight = (averageHeight /= (float)(dim * dim)) / MaxTerrainHeight; mapPixel.maxHeight = maxHeight / MaxTerrainHeight; }
public override 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, painting paths in the process int pathsIndex = mapData.mapPixelX + (mapData.mapPixelY * MapsFile.MaxMapPixelX); byte roadDataPt = pathsData[roads][pathsIndex]; byte roadCorners = (byte)(InRange(pathsIndex) ? (pathsData[roads][pathsIndex + 1] & 0x5) | (pathsData[roads][pathsIndex - 1] & 0x50) : 0); byte trackDataPt = pathsData[tracks][pathsIndex]; byte trackCorners = (byte)(InRange(pathsIndex) ? (pathsData[tracks][pathsIndex + 1] & 0x5) | (pathsData[tracks][pathsIndex - 1] & 0x50) : 0); if (editorEnabled) { roadDataPt = BasicRoadsPathEditor.pathsData[roads][pathsIndex]; roadCorners = (byte)(InRange(pathsIndex) ? (BasicRoadsPathEditor.pathsData[roads][pathsIndex + 1] & 0x5) | (BasicRoadsPathEditor.pathsData[roads][pathsIndex - 1] & 0x50) : 0); trackDataPt = BasicRoadsPathEditor.pathsData[tracks][pathsIndex]; trackCorners = (byte)(InRange(pathsIndex) ? (BasicRoadsPathEditor.pathsData[tracks][pathsIndex + 1] & 0x5) | (BasicRoadsPathEditor.pathsData[tracks][pathsIndex - 1] & 0x50) : 0); } NativeArray <byte> lookupData = new NativeArray <byte>(lookupTable, Allocator.TempJob); AssignTilesWithRoadsJob assignTilesJob = new AssignTilesWithRoadsJob { lookupTable = lookupData, tileData = tileData, tilemapData = mapData.tilemapData, tdDim = tileDataDim, tDim = assignTilesDim, hDim = terrainSampler.HeightmapDimension, march = march, locationRect = mapData.locationRect, midLo = (assignTilesDim / 2) - 1, midHi = assignTilesDim / 2, roadDataPt = roadDataPt, roadCorners = roadCorners, trackDataPt = trackDataPt, trackCorners = trackCorners, }; JobHandle assignTilesHandle = assignTilesJob.Schedule(assignTilesDim * assignTilesDim, 64, tileDataHandle); JobHandle returnHandle = assignTilesHandle; if (smoothPaths) { SmoothRoadsTerrainJob smoothRoadTerrainJob = new SmoothRoadsTerrainJob() { heightmapData = mapData.heightmapData, tilemapData = mapData.tilemapData, hDim = DaggerfallUnity.Instance.TerrainSampler.HeightmapDimension, tDim = assignTilesDim, locationRect = mapData.locationRect, }; JobHandle smoothRoadHandle = smoothRoadTerrainJob.Schedule(assignTilesHandle); returnHandle = smoothRoadHandle; } // Add both working native arrays to disposal list. mapData.nativeArrayList.Add(tileData); mapData.nativeArrayList.Add(lookupData); return(returnHandle); }
/// <summary> /// Extracts data from the ComputeBuffers, applies it to the terrain, then disposes of the buffers. /// </summary> /// <param name="buffers"></param> /// <param name="mapData"></param> /// public static void ProcessBufferValuesAndDispose(HeightmapBufferCollection buffers, ref MapPixelData mapData) { // Extract heights, generated by TerrainComputer, as float array var heightValues = new float[buffers.heightmapBuffer.count]; buffers.heightmapBuffer.GetData(heightValues); // Assign generated heights to mapData CopyToNative(heightValues, ref mapData.heightmapData); mapData.heightmapSamples = To2D(mapData.heightmapData, HeightmapRes()); // Extract tileData, generated by TerrainComputer, as int array int res = MapsFile.WorldMapTileDim + 1; var tilemapInts = new int[res * res]; buffers.tilemapData.GetData(tilemapInts); // Convert to byte array var tileData = tilemapInts.Select(v => AsByte(v)).ToArray(); // Add to internal tileData storage for use in TerrainTexturer var pos = new DFPosition(mapData.mapPixelX, mapData.mapPixelY); InterestingTerrains.tileDataCache.Add(pos, tileData); // Cleanup buffers.Dispose(); }
/// <summary> /// Initializes and runs a TerrainComputer GPU job, then processes the generated data. /// </summary> /// <param name="csPrototype"></param> /// <param name="mapData"></param> /// <param name="csParams"></param> public void DispatchAndProcess(ComputeShader csPrototype, ref MapPixelData mapData, TerrainComputerParams csParams) { var woodsFile = DaggerfallUnity.Instance.ContentReader.WoodsFileReader; var cs = UnityEngine.Object.Instantiate(csPrototype); var k = cs.FindKernel("TerrainComputer"); uint _x, _y, _z; cs.GetKernelThreadGroupSizes(k, out _x, out _y, out _z); int res = heightmapResolution + 1; DaggerfallUnity dfUnity; DaggerfallUnity.FindDaggerfallUnity(out dfUnity); int searchSize = 16; var locations = new List <Rect>(); int x, y; for (x = -searchSize; x <= searchSize; x++) { for (y = -searchSize; y <= searchSize; y++) { var mpx = mapData.mapPixelX + x; var mpy = mapData.mapPixelY + y; var key = new DoubleInt() { Item1 = mpx, Item2 = mpy }; if (locationRectCache.ContainsKey(key)) { locations.Add(locationRectCache[key]); continue; } var mapPixelPos = new DFPosition(mpx, mpy); var mapPixelData = TerrainHelper.GetMapPixelData(dfUnity.ContentReader, mpx, mpy); if (!mapPixelData.hasLocation) { continue; } var location = dfUnity.ContentReader.MapFileReader.GetLocation(mapPixelData.mapRegionIndex, mapPixelData.mapLocationIndex); var locationRect = GetLocationRect(location); if (locationRect.width == 0 || locationRect.height == 0) { continue; } locationRect = ExpandInEachDirection(locationRect, 1); locationRectCache.Add(key, locationRect); locations.Add(locationRect); } } x = (int)_x; y = (int)_y; cs.SetVector("terrainPosition", terrainPosition); cs.SetVector("terrainSize", terrainSize); cs.SetInt("heightmapResolution", heightmapResolution); cs.SetVector("locationPosition", locationRect.min); cs.SetVector("locationSize", locationRect.size); cs.SetVectorArray("locationPositions", locations.Select(r => new Vector4(r.min.x, r.min.y)).ToArray()); cs.SetVectorArray("locationSizes", locations.Select(r => new Vector4(r.size.x, r.size.y)).ToArray()); cs.SetInt("locationCount", locations.Count); cs.SetTexture(k, "BiomeMap", InterestingTerrains.biomeMap); cs.SetTexture(k, "DerivMap", InterestingTerrains.derivMap); cs.SetTexture(k, "tileableNoise", InterestingTerrains.tileableNoise); cs.SetFloat("originalHeight", Utility.GetOriginalTerrainHeight()); cs.SetFloat("newHeight", Constants.TERRAIN_HEIGHT); cs.SetTexture(k, "mapPixelHeights", baseHeightmap); cs.SetBuffer(k, "heightmapBuffer", heightmapBuffers.heightmapBuffer); cs.SetBuffer(k, "rawNoise", heightmapBuffers.rawNoise); cs.SetBuffer(k, "locationHeightData", locationHeightData); cs.SetVector("worldSize", Utility.GetWorldVertexSize()); var rd = Compatibility.BasicRoadsUtils.GetRoadData(mapData.mapPixelX, mapData.mapPixelY); cs.SetVectorArray("NW_NE_SW_SE", rd.NW_NE_SW_SE); cs.SetVectorArray("N_E_S_W", rd.N_E_S_W); csParams.ApplyToCS(cs); woodsFile.Buffer = originalHeightmapBuffer; HandleBaseMapSampleParams(ref mapData, ref cs, k); woodsFile.Buffer = alteredHeightmapBuffer; cs.Dispatch(k, res / x, res / y, 1); k = cs.FindKernel("TilemapComputer"); cs.SetTexture(k, "BiomeMap", InterestingTerrains.biomeMap); cs.SetTexture(k, "DerivMap", InterestingTerrains.derivMap); cs.SetBuffer(k, "heightmapBuffer", heightmapBuffers.heightmapBuffer); cs.SetBuffer(k, "tilemapData", heightmapBuffers.tilemapData); cs.SetBuffer(k, "rawNoise", heightmapBuffers.rawNoise); cs.Dispatch(k, res / x, res / y, 1); BufferIO.ProcessBufferValuesAndDispose(heightmapBuffers, ref mapData); }
public virtual 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); currentMapData = mapData; 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, worldClimate = mapData.worldClimate, }; JobHandle tileDataHandle = tileDataJob.Schedule(tileDataDim * tileDataDim, 64, dependencies); // Schedule the paint roads jobs if basic roads mod is enabled JobHandle preAssignTilesHandle = tileDataHandle; if (basicRoadsEnabled) { ModManager.Instance.SendModMessage("BasicRoads", "scheduleRoadsJob", new object[] { mapData, tileData, tileDataHandle }, (string message, object data) => { if (message == "error") { Debug.LogError(data as string); } else { preAssignTilesHandle = (JobHandle)data; } }); } // 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, preAssignTilesHandle); // Add both working native arrays to disposal list. mapData.nativeArrayList.Add(tileData); mapData.nativeArrayList.Add(lookupData); return(assignTilesHandle); }