private static IField2d <float> GetKernel(int radius) { var seed = new SparseField2d <float>(2 * radius + 1, 2 * radius + 1, 0f); seed[radius, radius] = 1f; // Because "radius" to the blurred field roughly equates to "standard deviation" while // it means "bounding dimension" in the context of the kernel, we halve the kernel in // in order to (very roughly) convert between the paradigms. var blurred = new BlurredField(seed, radius / 2); // Normalize. float sum = 0f; for (int y = 0; y < blurred.Height; y++) { for (int x = 0; x < blurred.Width; x++) { sum += blurred[y, x]; } } // If I return "seed" here, then it operates perfectly normally. There's some // kind of strange oscillation case that results from the area-based nature of // using a larger kernel. But what? return(new ScaleTransform(blurred, 1f / sum)); }
public static List <Point2d> GetSettlementLocations(WaterTableField wtf, IField2d <float> wateriness, float noiseScale = 0.1f, float noiseBlur = 3f, float noiseContribution = 0.15f) { IField2d <float> noise = new BlurredField(new MountainNoise(wtf.Width, wtf.Height, noiseScale), noiseBlur); Transformation2d <float, float, float> combination = new Transformation2d <float, float, float>(wateriness, noise, (w, n) => w + noiseContribution * n); List <Point2d> locations = new List <Point2d>(); for (int y = 1; y < combination.Height - 1; y++) { for (int x = 1; x < combination.Width - 1; x++) { if (wtf[y, x] > 0f && combination[y - 1, x - 1] < combination[y, x] && combination[y - 1, x + 0] < combination[y, x] && combination[y - 1, x + 1] < combination[y, x] && combination[y + 0, x + 1] < combination[y, x] && combination[y + 1, x + 1] < combination[y, x] && combination[y + 1, x + 0] < combination[y, x] && combination[y + 1, x - 1] < combination[y, x] && combination[y + 0, x - 1] < combination[y, x]) { locations.Add(new Point2d(x, y)); } } } return(locations); }
public void OutputMapForRectangle(Rectangle sourceRect, Bitmap bmp, string dir = "C:\\Users\\Justin Murray\\Desktop\\terrain\\", string name = "submap") { // Hack that adds buffers, use to work around unidentified bugs and differences of behavior near edges. Rectangle rect = new Rectangle( sourceRect.Left - sourceRect.Width / 20, sourceRect.Top - sourceRect.Height / 20, sourceRect.Width * 11 / 10, sourceRect.Height * 11 / 10); int width = (int)Math.Ceiling(bmp.Width * 1.1f); int height = (int)Math.Ceiling(bmp.Height * 1.1f); IField2d <float> waterTable = new BlurredField(new SubContinuum <float>(width, height, new ContinuousField(this.wtf), rect), 0.5f * width / rect.Width); IField2d <float> mountains = new SubContinuum <float>(width, height, this.mountainNoise, rect); // TODO: hills? IField2d <float> roughness = new BlurredField(new SubContinuum <float>(width, height, new ContinuousField(this.args.roughnessDrawing), rect), 0.5f * width / rect.Width); IField2d <float> riverbeds = GetRiverFieldForRectangle(width, height, rect); IField2d <float> damping = GetDampingFieldForRectangle(rect, riverbeds); IField2d <float> heightmap; { IField2d <float> dampedNoise = new Transformation2d <float, float, float>(mountains, damping, (m, d) => Math.Max(1f - d, 0f) * m); IField2d <float> scaledDampedNoise = new Transformation2d <float, float, float>(dampedNoise, roughness, (n, r) => n * r * this.args.mountainHeightMaxInMeters); IField2d <float> groundHeight = new Transformation2d <float, float, float>(waterTable, scaledDampedNoise, (w, m) => w + m); // TODO: Erode the groundHeight. heightmap = new Transformation2d <float, float, float>(groundHeight, riverbeds, Math.Min); //DEBUG heightmap = Erosion.DropletHydraulic( heightmap, 2 * heightmap.Width * heightmap.Height, 100, maxHeight: this.args.baseHeightMaxInMeters + this.args.mountainHeightMaxInMeters, radius: (int)(this.args.erosionRadiusInMeters / (this.args.metersPerPixel * sourceRect.Width / heightmap.Width)) ); } IField2d <float> riverField = new SubField <float>(riverbeds, new Rectangle(bmp.Width / 20, bmp.Height / 20, bmp.Width, bmp.Height)); IField2d <float> heightField = new SubField <float>(heightmap, new Rectangle(bmp.Width / 20, bmp.Height / 20, bmp.Width, bmp.Height)); // TODO: DEBUG OutputAsOBJ(heightField, new Transformation2d <float, bool>(riverField, r => !float.IsPositiveInfinity(r)), sourceRect, bmp, dir, name); OutputAsPreciseHeightmap(heightField, riverField, dir + name + ".png"); }
private IField2d <float> GetDampingFieldForRectangle(Rectangle rect, IField2d <float> riverbeds) { float metersPerPixel = this.args.metersPerPixel * rect.Width / riverbeds.Width; IField2d <float> upres = new BlurredField(new SubContinuum <float>(riverbeds.Width, riverbeds.Height, this.distanceToWater, rect), riverbeds.Width / rect.Width); IField2d <float> manhats = new ScaleTransform(new ManhattanDistanceField(new Transformation2d <float, bool>(riverbeds, r => !float.IsPositiveInfinity(r))), metersPerPixel); IField2d <float> dists = new BlurredField(new Transformation2d <float, float, float>(upres, manhats, Math.Min), 200f / metersPerPixel); return(new Transformation2d(dists, d => { float valleyFactor = 1f - d / this.args.valleyRadiusInMeters; float canyonFactor = 1f - (float)Math.Pow(d / this.args.canyonRadiusInMeters, 2f); return Math.Max(Math.Max(valleyFactor * this.args.valleyStrength, canyonFactor * this.args.canyonStrength), 0f); })); }
public static void RunBlurryScenario() { Bitmap jranjana = new Bitmap("C:\\Users\\Justin Murray\\Desktop\\maps\\input\\rivers_hr.png"); Field2d <float> field = new Utils.FieldFromBitmap(jranjana); BlurredField blurred = new BlurredField(field); Bitmap output = new Bitmap(blurred.Width, blurred.Height); for (int x = 0, y = 0; y < blurred.Height; y += ++x / blurred.Width, x %= blurred.Width) { float v = blurred[y, x]; int value = (int)(255f * v); output.SetPixel(x, y, Color.FromArgb(value, value, value)); } output.Save("C:\\Users\\Justin Murray\\Desktop\\maps\\output\\blurred.png"); }
public WaterTableField( IField2d <float> baseField, IField2d <HydrologicalField.LandType> hydroField, float shoreHeightAboveRiver = 0.01f, int blurIterations = 10, int minWaterwayLength = 5, float minGrade = 0f, Func <float> getCarve = null) : base(baseField.Width, baseField.Height) { getCarve = getCarve ?? (() => { return(1f); }); this.HydroField = hydroField; GeographicFeatures = this.HydroField.FindContiguousSets(); Waterways = GeographicFeatures.GetRiverSystems(this.HydroField).Where(ww => ww.Depth() >= minWaterwayLength).ToList(); RiverSystems = Waterways.GetRivers(); DrainageField = new DrainageField(this.HydroField, Waterways); foreach (var sea in GeographicFeatures[HydrologicalField.LandType.Ocean]) { foreach (var p in sea) { this[p.y, p.x] = 0f; } } // Set the heights of all the river systems. foreach (var river in RiverSystems) { Queue <TreeNode <TreeNode <Point2d> > > mouths = new Queue <TreeNode <TreeNode <Point2d> > >(); mouths.Enqueue(river); Point2d p = river.value.value; this[p.y, p.x] = 0f; while (mouths.Count > 0) { var mouth = mouths.Dequeue(); // Discarded alternative approach: instead of using an incrementor, use a low pass filter // against base field altitude (with a no-lowering caveat). Produces some nice effects and // leaves mountains remarkably well intact; however, abandoned because, when faced with a // river that flows all the way through a mountain range, the filter will choose to extend // the mountain range rather than carve a valley through it. List <Point2d> points = new List <Point2d>(); mouth.IteratePrimarySubtree().Iterate(node => points.Add(node.value)); p = points[0]; float mouthAlti = this[p.y, p.x]; p = points[points.Count - 1]; float sourceAlti = Math.Max(baseField[p.y, p.x], mouthAlti + points.Count * minGrade); float carve = getCarve(); Func <float, float> ceiling = t => { float lrp = (float)Math.Pow(t, carve); return(mouthAlti * (1f - lrp) + sourceAlti * lrp); }; Func <float, float> floor = t => { return(mouthAlti + points.Count * t * minGrade); }; float val = mouthAlti; for (int idx = 0; idx < points.Count; idx++) { float t = 1f * idx / points.Count; p = points[idx]; val += minGrade; val = Math.Max(val, floor(t)); val = Math.Max(val, baseField[p.y, p.x]); val = Math.Min(val, ceiling(t)); this[p.y, p.x] = val; } foreach (var child in mouth.children) { mouths.Enqueue(child); } } } // At this point, all the water pixels have a defined height; set every // land pixel to be the same height as its drain iff it drains to a river. foreach (var land in GeographicFeatures[HydrologicalField.LandType.Land]) { foreach (var p in land) { Point2d drain = DrainageField[p.y, p.x]; if (this.HydroField[drain.y, drain.x] == HydrologicalField.LandType.Shore) { this[p.y, p.x] = this[drain.y, drain.x] + shoreHeightAboveRiver; } else { this[p.y, p.x] = baseField[p.y, p.x] + shoreHeightAboveRiver; } } } for (int idx = 0; idx < blurIterations; idx++) { BlurredField bf = new BlurredField(this, 1); foreach (var land in GeographicFeatures[HydrologicalField.LandType.Land]) { foreach (var p in land) { this[p.y, p.x] = bf[p.y, p.x]; } } } //System.Diagnostics.Debug.Assert(Waterways.AreWaterwaysLegalForField(this)); }
public static void RunZoomedInScenario() { // 32x32 up to 1024x1024 will, from the fifth-of-a-mile-per-pixel source, get us approximately 10m per pixel. // 16x16 would get us 5 // 8x8 would get us 2.5 // 4x4 would get us 1.25 // 2x2 would get us .75 // 1x1 would get us .375, which is slightly over 1 foot. // I think 16x16 is the sweet spot. That's just over 9 square miles per small map. const int SMALL_MAP_SIDE_LEN = 64; const float STARTING_SCALE = 0.005f * SMALL_MAP_SIDE_LEN / 32; const int SMALL_MAP_RESIZED_LEN = 1024; Random random = new Random(); WaterTableArgs args = new WaterTableArgs(); Bitmap bmp = new Bitmap(args.inputPath + "rivers.png"); IField2d <float> baseMap = new Utils.FieldFromBitmap(new Bitmap(args.inputPath + "base_heights.png")); baseMap = new ReResField(baseMap, (float)bmp.Width / baseMap.Width); var wtf = Utils.GenerateWaters(bmp, baseMap); Utils.OutputAsColoredMap(wtf, wtf.RiverSystems, bmp, args.outputPath + "colored_map.png"); var hasWater = new Transformation2d <HydrologicalField.LandType, float>(wtf.HydroField, t => t == HydrologicalField.LandType.Land ? 0f : 1f); var noiseDamping = new Transformation2d(new BlurredField(hasWater, 2f), v => 3.5f * v); // Create the spline map. SparseField2d <List <SplineTree> > relevantSplines = new SparseField2d <List <SplineTree> >(wtf.Width, wtf.Height, null); { //HashSet<TreeNode<Point2d>> relevantRivers = new HashSet<TreeNode<Point2d>>(); foreach (var system in wtf.RiverSystems) { SplineTree tree = null; foreach (var p in system.value) { if (relevantSplines[p.value.y, p.value.x] == null) { relevantSplines[p.value.y, p.value.x] = new List <SplineTree>(); } relevantSplines[p.value.y, p.value.x].Add(tree ?? (tree = new SplineTree(system.value, wtf, random))); } } } Rectangle rect = new Rectangle(518 + 15, 785 + 45, SMALL_MAP_SIDE_LEN, SMALL_MAP_SIDE_LEN); var smallMap = new SubField <float>(wtf, rect); var scaledUp = new BlurredField(new ReResField(smallMap, SMALL_MAP_RESIZED_LEN / smallMap.Width), SMALL_MAP_RESIZED_LEN / (4 * SMALL_MAP_SIDE_LEN)); var smallDamp = new SubField <float>(noiseDamping, rect); var scaledDamp = new BlurredField(new ReResField(smallDamp, SMALL_MAP_RESIZED_LEN / smallMap.Width), SMALL_MAP_RESIZED_LEN / (4 * SMALL_MAP_SIDE_LEN)); // Do spline-y things. Field2d <float> riverbeds; List <SplineTree> splines = new List <SplineTree>(); { // Collect a comprehensive list of the spline trees for the local frame. for (int y = rect.Top - 1; y <= rect.Bottom + 1; y++) { for (int x = rect.Left - 1; x <= rect.Right + 1; x++) { List <SplineTree> trees = relevantSplines[y, x]; if (trees != null) { splines.AddRange(trees); } } } // Crafts the actual river kernel. Probably not the best way to go about this. riverbeds = new Field2d <float>(new ConstantField <float>(SMALL_MAP_RESIZED_LEN, SMALL_MAP_RESIZED_LEN, float.MaxValue)); foreach (var s in splines) { var samples = s.GetSamplesPerControlPoint(1f * SMALL_MAP_RESIZED_LEN / SMALL_MAP_SIDE_LEN); int priorX = int.MinValue; int priorY = int.MinValue; foreach (var p in samples) { int x = (int)((p[0] - rect.Left) * SMALL_MAP_RESIZED_LEN / SMALL_MAP_SIDE_LEN); int y = (int)((p[1] - rect.Top) * SMALL_MAP_RESIZED_LEN / SMALL_MAP_SIDE_LEN); if (x == priorX && y == priorY) { continue; } else { priorX = x; priorY = y; } if (0 <= x && x < SMALL_MAP_RESIZED_LEN && 0 <= y && y < SMALL_MAP_RESIZED_LEN) { const int r = 1024 / SMALL_MAP_SIDE_LEN; for (int j = -r; j <= r; j++) { for (int i = -r; i <= r; i++) { int xx = x + i; int yy = y + j; if (0 <= xx && xx < SMALL_MAP_RESIZED_LEN && 0 <= yy && yy < SMALL_MAP_RESIZED_LEN) { float dSq = i * i + j * j; riverbeds[yy, xx] = Math.Min(riverbeds[yy, xx], p[2] + dSq / (512f * 32 / SMALL_MAP_SIDE_LEN)); //scaledDamp[yy, xx] = 1f; //scaledUp[yy, xx] = Math.Min(scaledUp[yy, xx], p[2] + (float)Math.Sqrt(xx * xx + yy * yy) / 1f); } } } } } } //Utils.OutputField(riverbeds, new Bitmap(riverbeds.Width, riverbeds.Height), args.outputPath + "river_field.png"); } var mountainous = new ScaleTransform(new MountainNoise(1024, 1024, STARTING_SCALE), 1f); var hilly = new ScaleTransform(new Simplex2D(1024, 1024, STARTING_SCALE * 4), 0.1f); var terrainNoise = new Transformation2d <float, float, float>(mountainous, hilly, (x, y, m, h) => { float a = scaledUp[y, x]; float sh = Math.Max(-2f * Math.Abs(a - 0.2f) / 3f + 1f, 0f); float sm = Math.Min(1.3f * a, 1f); return(h * sh + m * sm); }); IField2d <float> combined = new NormalizedComposition2d <float>( new Transformation2d <float, float, float>(riverbeds, new Composition2d <float>(scaledUp, new Transformation2d <float, float, float>(scaledDamp, terrainNoise, (s, m) => (1 - Math.Min(1, s)) * m) ), (r, c) => r == float.MaxValue ? c : Math.Min(r, c)) ); Bitmap img = new Bitmap(combined.Width, combined.Height); Utils.OutputField(combined, img, args.outputPath + "combined.png"); Utils.OutputAsOBJ(combined, splines, rect, img, args.outputPath); }
public static void RunStreamedMapCombinerScenario(int cacheSize = 16) { ImageServer server = new ImageServer(); string[] fileNames = Directory.GetFiles("C:\\Users\\Justin Murray\\Desktop\\egwethoon\\", "submap*.png"); foreach (string fileName in fileNames) { server.AddImage(fileName); } StreamedChunkedPreciseHeightField streamedField = new StreamedChunkedPreciseHeightField(512 * 256 / 32, 512 * 256 / 32, cacheSize, (x, y) => { var chunkToLoad = server.TryGetPathForPoint(x, y); if (chunkToLoad.path != null) { return(new ChunkField <float> .Chunk(chunkToLoad.x, chunkToLoad.y, new FieldFromPreciseBitmap(new Bitmap(chunkToLoad.path)))); } return(null); }); Field2d <float> output = new Field2d <float>(new ConstantField <float>(streamedField.Width, streamedField.Height, 0f)); foreach (var chunkToLoad in server.GetImages()) { var chunk = new ChunkField <float> .Chunk(chunkToLoad.x, chunkToLoad.y, new FieldFromPreciseBitmap(new Bitmap(chunkToLoad.path))); for (int y = 0; y < chunk.Field.Height; y++) { for (int x = 0; x < chunk.Field.Width; x++) { output[y + chunk.MinPoint.Y / 2, x + chunk.MinPoint.X / 2] = chunk.Field[y, x]; } } } output = new BlurredField(output, 1f); Bitmap bmp = new Bitmap(output.Width, output.Height); for (int y = 0; y < output.Height; y++) { for (int x = 0; x < output.Width; x++) { float val = output[y, x]; if (val > 2250f) { bmp.SetPixel(x, y, Lerp(Color.Gray, Color.White, (val - 2250f) / 1750f)); } else if (val > 1250f) { bmp.SetPixel(x, y, Lerp(Color.Red, Color.Gray, (val - 1250f) / 1000f)); } else if (val > 750f) { bmp.SetPixel(x, y, Lerp(Color.Yellow, Color.Red, (val - 750f) / 500f)); } else if (val > 250f) { bmp.SetPixel(x, y, Lerp(Color.Green, Color.Yellow, (val - 250f) / 500f)); } else if (val > 5f) { bmp.SetPixel(x, y, Lerp(Color.DarkGreen, Color.Green, (val - 5f) / 245f)); } else { bmp.SetPixel(x, y, Color.Aqua); } } } bmp.Save("C:\\Users\\Justin Murray\\Desktop\\egwethoon\\bigmap.png"); }