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 float TerrainHeightScale(int x, int y)
 {
     return(ImprovedWorldTerrain.computeHeightMultiplier(x, y) * ImprovedTerrainSampler.baseHeightScale + this.GetNoiseMapScaleBasedOnClimate(x, y)); // * 0.5f;
 }
        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);
        }