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 static IField2d <float> GetWaterinessMap(WaterTableField wtf, IField2d <float> rainfall, float waterPortability = 5f, float waterinessAttenuation = 20f) { // Generate "water flow" map using the rainfall map and the water table map, characterizing // how much water is in an area based on upstream. Note that, in the simplistic case of // identical universal rainfall, this is just a scalar on depth; this whole shindig is // intended to support variable rainfall, as characterized by the rainfall map. Field2d <float> waterFlow = new Field2d <float>(rainfall); float maxValue = float.MinValue; foreach (TreeNode <Point2d> waterway in wtf.Waterways) { List <TreeNode <Point2d> > flattenedReversedRiverTree = new List <TreeNode <Point2d> >(waterway); flattenedReversedRiverTree.Reverse(); foreach (var node in flattenedReversedRiverTree) { if (node.parent != null) { Point2d cur = node.value; Point2d par = node.parent.value; waterFlow[par.y, par.x] += waterFlow[cur.y, cur.x]; } maxValue = Math.Max(maxValue, waterFlow[node.value.y, node.value.x]); } } IField2d <float> waterinessUnblurred = new FunctionField <float>(waterFlow.Width, waterFlow.Height, (x, y) => 1f / (1f + (float)Math.Pow(Math.E, -waterinessAttenuation * waterFlow[y, x] / maxValue))); return(new BlurredField(waterinessUnblurred, waterPortability)); }
public void OutputAsPreciseHeightmap(IField2d <float> heights, IField2d <float> rivers, string outputPath) { Bitmap bmp = new Bitmap(heights.Width, heights.Height); for (int y = 0; y < heights.Height; y++) { for (int x = 0; x < heights.Width; x++) { float h = Math.Max(heights[y, x], 0f); float r = Math.Max(rivers[y, x], 0f); if (float.IsNaN(h)) { Debug.Assert(false, "TODO: This should never have occurred."); h = 0; } int m1k = (int)(h / 1000); int m10 = (int)((h - m1k * 1000) / 10); int m_1 = (int)((h - m1k * 1000 - m10 * 10) * 10); int w = float.IsPositiveInfinity(r) ? 255 : 128; bmp.SetPixel(x, y, Color.FromArgb(w, m1k, m10, m_1)); } } bmp.Save(outputPath); }
private static LandType Classify(IField2d <BrownianTree.Availability> field, int x, int y, int sensitivity, float shoreThreshold) { if (field[y, x] == BrownianTree.Availability.Available) { return(LandType.Land); } Point2d pos = new Point2d(x, y); float landNum = 0f; float totalNum = 0f; for (int j = (int)Math.Max(0, y - sensitivity); j < Math.Min(field.Height, y + sensitivity + 1); j++) { for (int i = (int)Math.Max(0, x - sensitivity); i < Math.Min(field.Width, x + sensitivity + 1); i++) { if (Point2d.SqDist(new Point2d(i, j), pos) < sensitivity * sensitivity) { if (field[j, i] == BrownianTree.Availability.Available) { landNum++; } totalNum++; } } } if (landNum / totalNum > shoreThreshold) { return(LandType.Shore); } return(LandType.Ocean); }
// TODO: Consider a more sophisticated gradient function, as it recommends in the paper. // But for now, I can't imagine subpixel accuracy is going to make a whole lot of difference. private static vFloat GradientAtPoint(this IField2d <float> field, vFloat point) { int pX = (int)point[0]; int pY = (int)point[1]; float x, y; if (pX % 2 == 1 || pX == field.Width - 1) { x = field[pY, pX - 1] - field[pY, pX]; } else { x = field[pY, pX] - field[pY, pX + 1]; } if (pY % 2 == 1 || pY == field.Height - 1) { y = field[pY - 1, pX] - field[pY, pX]; } else { y = field[pY, pX] - field[pY + 1, pX]; } return(new vFloat(x, y)); }
public Args(IField2d <float> waters, IField2d <float> heights, IField2d <float> roughness, IField2d <float> rain) { this.watersDrawing = waters; this.heightsDrawing = heights; this.roughnessDrawing = roughness; this.rainDrawing = rain; }
public DrainageField(IField2d <HydrologicalField.LandType> hydroField, List <TreeNode <Point2d> > rivers) : base(new Transformation2d <HydrologicalField.LandType, Point2d>(hydroField, (x, y, landType) => { switch (landType) { case HydrologicalField.LandType.Ocean: return(new Point2d(x, y)); case HydrologicalField.LandType.Shore: return(default(Point2d)); // Handled in local constructor. case HydrologicalField.LandType.Land: Point2d?drain = Utils.Bfs( new Point2d(x, y), pt => x >= 0 && x < hydroField.Width && y >= 0 && y < hydroField.Height, pt => hydroField[pt.y, pt.x] != HydrologicalField.LandType.Land); return(drain.HasValue ? drain.Value : new Point2d(x, y)); default: throw new Exception(); } })) { foreach (var river in rivers) { SetRiverDrainage(river); } }
public static WaterTableField GenerateWaters(Bitmap bmp, IField2d <float> baseField = null, WaterTableArgs args = null, Random random = null) { args = args ?? new WaterTableArgs() { seed = System.DateTime.UtcNow.Ticks }; random = random ?? new Random((int)args.seed); baseField = baseField ?? new Simplex2D(bmp.Width, bmp.Height, args.baseNoiseScale, args.seed); Field2d <float> field = new FieldFromBitmap(bmp); baseField = new NormalizedComposition2d <float>(baseField, new ScaleTransform(new Simplex2D(baseField.Width, baseField.Height, args.baseNoiseScale, args.seed), args.baseNoiseScalar)); BrownianTree tree = BrownianTree.CreateFromOther(field, (x) => x > 0.5f ? BrownianTree.Availability.Available : BrownianTree.Availability.Unavailable, random); tree.RunDefaultTree(); HydrologicalField hydro = new HydrologicalField(tree, args.hydroSensitivity, args.hydroShoreThreshold); WaterTableField wtf = new WaterTableField(baseField, hydro, args.wtfShore, args.wtfIt, args.wtfLen, args.wtfGrade, () => { return((float)(args.wtfCarveAdd + random.NextDouble() * args.wtfCarveMul)); }); return(wtf); }
/// <summary> /// /// </summary> private static IEnumerable <Vector2d> IntegrateFromEuler(IField2d <Vector2d> field, Vector2d point, double stepSize) { while (true) { point += field.ValueAt(point) * stepSize; yield return(point); } }
public Transformation2d(IField2d <TFrom1> field1, IField2d <TFrom2> field2, Func <int, int, TFrom1, TFrom2, TTo> transformation) { System.Diagnostics.Debug.Assert(field1.Width == field2.Width && field1.Height == field2.Height, "Inputs to transformations must have equal dimensions."); this.field1 = field1; this.field2 = field2; this.transformation = transformation; }
public Field2d(IField2d <T> field) { this.values = new T[field.Height, field.Width]; for (int x = 0, y = 0; y < this.Height; y += ++x / this.Width, x %= this.Width) { this.values[y, x] = field[y, x]; } }
/// <summary> /// /// </summary> private static IEnumerable <Vector2d> IntegrateFromRK2(IField2d <Vector2d> field, Vector2d point, double stepSize) { while (true) { var v0 = field.ValueAt(point); var v1 = field.ValueAt(point + v0 * stepSize); point += (v0 + v1) * 0.5 * stepSize; yield return(point); } }
public static BrownianTree CreateFromOther <T>(IField2d <T> baseField, Func <T, Availability> conversion, Random random = null) { BrownianTree tree = new BrownianTree(baseField.Width, baseField.Height); for (int x = 0, y = 0; y < tree.Height; y += ++x / tree.Width, x %= tree.Width) { tree[y, x] = conversion(baseField[y, x]); } tree.random = random ?? new Random(); return(tree); }
public static void GaussBlur(IField2d <float> source, Field2d <float> target, float radius, int n = 3) { var boxSizes = BoxRadiiForGaussBlur(radius, n); Field2d <float> buffer = new Field2d <float>(source); RecursivelyBoxBlur(buffer, target, boxSizes); // If n was an even number, then the final blurring wound up // in the buffer; replicate it to the target. if (n % 2 == 0) { target.Replicate(buffer); } }
public bool TryAddChunk(int x, int y, IField2d <T> field) { if (GetChunkForPositionImpl(x, y).HasValue) { return(false); } else { Chunk chunk = new Chunk(x, y, field); this.chunks.Add(chunk); CacheChunk(chunk); return(true); } }
/// <summary> /// /// </summary> /// <param name="field"></param> /// <param name="points"></param> /// <param name="stepSize"></param> /// <param name="stepCount"></param> /// <param name="mode"></param> private PolylineCurve[] SolveInstanceImpl(IField2d <Vec2d> field, List <Point3d> points, double stepSize, int stepCount) { var result = new PolylineCurve[points.Count]; Parallel.For(0, points.Count, i => { Vec3d p0 = points[i]; var z = p0.Z; var pts = field.IntegrateFrom(p0, stepSize, _mode).Take(stepCount).Select(p => new Point3d(p.X, p.Y, z)); result[i] = new PolylineCurve(pts); }); return(result); }
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 static void DropSedimentFromKernel(Field2d <float> field, IField2d <float> kernel, int centerX, int centerY, float targetSediment) { for (int y = 0; y < kernel.Height; y++) { for (int x = 0; x < kernel.Width; x++) { int cX = centerX + x - kernel.Width / 2; int cY = centerY + y - kernel.Height / 2; if (cX >= 0 && cY >= 0 && cX < field.Width && cY < field.Height) { field[cY, cX] += targetSediment * kernel[y, x]; } } } }
/// <summary> /// Returns a streamline through the given vector field starting at the given point. /// </summary> /// <param name="field"></param> /// <param name="point"></param> /// <param name="stepSize"></param> /// <param name="mode"></param> /// <returns></returns> public static IEnumerable <Vector2d> IntegrateFrom(IField2d <Vector2d> field, Vector2d point, double stepSize, IntegrationMode mode = IntegrationMode.Euler) { switch (mode) { case IntegrationMode.Euler: return(IntegrateFromEuler(field, point, stepSize)); case IntegrationMode.RK2: return(IntegrateFromRK2(field, point, stepSize)); case IntegrationMode.RK4: return(IntegrateFromRK4(field, point, stepSize)); } throw new NotSupportedException(); }
/// <summary> /// /// </summary> private static IEnumerable <Vector2d> IntegrateFromRK4(IField2d <Vector2d> field, Vector2d point, double stepSize) { double dt2 = stepSize * 0.5; double dt6 = stepSize / 6.0; while (true) { var v0 = field.ValueAt(point); var v1 = field.ValueAt(point + v0 * dt2); var v2 = field.ValueAt(point + v1 * dt2); var v3 = field.ValueAt(point + v2 * stepSize); point += (v0 + 2.0 * v1 + 2.0 * v2 + v3) * dt6; yield return(point); } }
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 SplineTree(TreeNode <Point2d> tree, IField2d <float> altitudes, Random random, int minSizeForFork = 3, float alpha = 0.5f) { this.splines = new List <CenCatRomSpline>(); this.altitudes = altitudes; this.random = random; this.minSizeForFork = minSizeForFork; this.alpha = alpha; var lastList = BuildSplinesRecursively(tree, null); lastList.Add(GetParentPoint(lastList)); this.splines.Add(new CenCatRomSpline(lastList.ToArray(), this.alpha)); this.altitudes = null; this.random = null; }
public ManhattanDistanceField(IField2d <bool> isTarget) : base(new Transformation2d <bool, float>(isTarget, b => b ? 0f : float.PositiveInfinity)) { float lastWater; for (int y = 0; y < this.Height; y++) { lastWater = float.PositiveInfinity; for (int x = 0; x < this.Width; x++) { lastWater = Math.Min(lastWater + 1f, this[y, x]); this[y, x] = lastWater; } } for (int y = 0; y < this.Height; y++) { lastWater = float.PositiveInfinity; for (int x = this.Width - 1; x >= 0; x--) { lastWater = Math.Min(lastWater + 1f, this[y, x]); this[y, x] = lastWater; } } for (int x = 0; x < this.Width; x++) { lastWater = float.PositiveInfinity; for (int y = 0; y < this.Height; y++) { lastWater = Math.Min(lastWater + 1f, this[y, x]); this[y, x] = lastWater; } } for (int x = 0; x < this.Width; x++) { lastWater = float.PositiveInfinity; for (int y = this.Height - 1; y >= 0; y--) { lastWater = Math.Min(lastWater + 1f, this[y, x]); this[y, x] = lastWater; } } }
// TODO: Don't allow the field to erode below zero. // TODO: Here, at last, is the source of the insane bug. The way this bug works is easiest to understand // if one envisions a kernel of small radius and a "trough" consisting of two elevated walls and a lower // middle. The nature of a trough is that a particle can become "trapped" in one, rolling back and forth // as each wall turns the particle back. If a trough is narrow enough that a particle dropping sediment in // the middle will also drop sediment on both walls by virtue of its kernel, then oscillation in the trough // can cause the particle to "build towers." This happens because when the particle is picking up sediment-- // i.e., when it's beginning to go down-hill--its kernel covers part of the trough and a little bit outside; // however, when the particle is depositing sediment--i.e., when it's beginning to go uphill--its kernel is // entirely over the trough. By repeated action, this allows the particle to "dig" sediment from just outside // the walls of its trough and bring that sediment back into the trough, creating the extremely // characteristic "bars" of high and low elevation in extremely close proximity. The solution to this, // presumably, is to prevent this "digging" behavior, presumably by taking sediment in a more cautious // manner that won't allow erosion computed from a high place to induce the removal of sediment from a low // place. private static void PickUpSedimentFromKernel(Field2d <float> field, IField2d <float> kernel, int centerX, int centerY, float targetSediment) { if (targetSediment == 0) { return; } float targetMin = field[centerY, centerX] - kernel[kernel.Height / 2, kernel.Width / 2] * targetSediment; float collected = 0f; for (int y = 0; y < kernel.Height; y++) { for (int x = 0; x < kernel.Width; x++) { int cX = centerX + x - kernel.Width / 2; int cY = centerY + y - kernel.Height / 2; if (cX >= 0 && cY >= 0 && cX < field.Width && cY < field.Height && field[cY, cX] >= targetMin) { collected += targetSediment * kernel[y, x]; } } } float scalar = targetSediment / collected; for (int y = 0; y < kernel.Height; y++) { for (int x = 0; x < kernel.Width; x++) { int cX = centerX + x - kernel.Width / 2; int cY = centerY + y - kernel.Height / 2; if (cX >= 0 && cY >= 0 && cX < field.Width && cY < field.Height && field[cY, cX] >= targetMin) { field[cY, cX] -= targetSediment * kernel[y, x] * scalar; } } } }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="U"></typeparam> /// <param name="field"></param> /// <param name="other"></param> /// <param name="converter"></param> /// <param name="parallel"></param> public static void Sample <T, U>(this IDiscreteField2d <T> field, IField2d <U> other, Func <U, T> converter, bool parallel = false) { if (parallel) { Parallel.ForEach(Partitioner.Create(0, field.Count), range => Body(range.Item1, range.Item2)); } else { Body(0, field.Count); } void Body(int from, int to) { var vals = field.Values; for (int i = from; i < to; i++) { vals[i] = converter(other.ValueAt(field.CoordinateAt(i))); } } }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="U"></typeparam> /// <param name="field"></param> /// <param name="other"></param> /// <param name="converter"></param> /// <param name="parallel"></param> public static void Sample <T, U>(this IDiscreteField2d <T> field, IField2d <U> other, Func <U, T> converter, bool parallel = false) { var vals = field.Values; Action <Tuple <int, int> > body = range => { for (int i = range.Item1; i < range.Item2; i++) { vals[i] = converter(other.ValueAt(field.CoordinateAt(i))); } }; if (parallel) { Parallel.ForEach(Partitioner.Create(0, field.Count), body); } else { body(Tuple.Create(0, field.Count)); } }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="field"></param> /// <param name="other"></param> /// <param name="parallel"></param> public static void Sample <T>(this ISampledField2d <T> field, IField2d <T> other, bool parallel = false) { if (parallel) { Parallel.ForEach(Partitioner.Create(0, field.Count), range => Body(range.Item1, range.Item2)); } else { Body(0, field.Count); } void Body(int from, int to) { var vals = field.Values; for (int i = from; i < to; i++) { vals[i] = other.ValueAt(field.PointAt(i)); } } }
/// <summary> /// /// </summary> /// <param name="field"></param> /// <param name="point"></param> /// <param name="stepSize"></param> /// <param name="mode"></param> /// <returns></returns> public static IEnumerable <Vec2d> IntegrateFrom(this IField2d <Vec2d> field, Vec2d point, double stepSize, IntegrationMode mode = IntegrationMode.Euler) { return(SimulationUtil.IntegrateFrom(field, point, stepSize, mode)); }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="transform"></param> /// <param name="other"></param> /// <returns></returns> public static IField2d <T> CreateTransformed <T>(IField2d <T> other, Transform2d transform) { transform.Invert(); return(Create(p => other.ValueAt(transform.Apply(p)))); }
// Based on the approach by Hans Theobald Beyer, "Implementation of a method for hydraulic erosion," 2015 public static Field2d <float> DropletHydraulic(IField2d <float> inputHeightmap, int numDroplets, int iterationsPerDrop, float minSlope = 0f, float maxHeight = 1f, int radius = 0) { Random random = new Random(); float pFriction = 0.3f; float pCapacity = 1f; float pErode = 0.3f; float pDeposit = 0.3f; float pGravity = 0.8f / maxHeight; float pEvaporate = 0.01f; const int STARTING_DIRECTION_GRANULARITY = 32; var kernel = GetKernel(radius); Field2d <float> heightmap = new Field2d <float>(inputHeightmap); for (int idx = 0; idx < numDroplets; idx++) { Droplet droplet = new Droplet() { Position = new vFloat(random.Next(heightmap.Width), random.Next(heightmap.Height)), Direction = new vFloat(random.Next(STARTING_DIRECTION_GRANULARITY) - STARTING_DIRECTION_GRANULARITY / 2, random.Next(STARTING_DIRECTION_GRANULARITY) - STARTING_DIRECTION_GRANULARITY / 2).norm(), Speed = 0, Water = 1, Sediment = 0, }; for (int iteration = 0; iteration < iterationsPerDrop; iteration++) { (int x, int y)oldPos = ((int)droplet.Position[0], (int)droplet.Position[1]); float oldHeight = heightmap[oldPos.y, oldPos.x]; var gradient = heightmap.GradientAtPoint(droplet.Position); droplet.Direction = (droplet.Direction + gradient) * (1 - pFriction); if (droplet.Direction.magSq() > 0) { droplet.Position += droplet.Direction.norm(); } if (droplet.Position[0] < 0f || droplet.Position[1] < 0f || droplet.Position[0] >= heightmap.Width || droplet.Position[1] >= heightmap.Height) { break; } (int x, int y)newPos = ((int)droplet.Position[0], (int)droplet.Position[1]); float newHeight = heightmap[newPos.y, newPos.x]; if (newHeight > oldHeight) { float droppedSediment = Math.Min(newHeight - oldHeight, droplet.Sediment); DropSedimentFromKernel(heightmap, kernel, oldPos.x, oldPos.y, droppedSediment); droplet.Sediment -= droppedSediment; } else if (newHeight < oldHeight) { float capacity = Math.Max(oldHeight - newHeight, minSlope) * droplet.Speed * droplet.Water * pCapacity; if (droplet.Sediment > capacity) { float droppedSediment = (droplet.Sediment - capacity) * pDeposit; DropSedimentFromKernel(heightmap, kernel, oldPos.x, oldPos.y, droppedSediment); droplet.Sediment -= droppedSediment; } else { float pickedUpSediment = Math.Min((capacity - droplet.Sediment) * pErode, oldHeight - newHeight); PickUpSedimentFromKernel(heightmap, kernel, oldPos.x, oldPos.y, pickedUpSediment); droplet.Sediment += pickedUpSediment; } } // This is from the paper, but it's super weird. So, the drops will pick up speed even if they go uphill? // I think speed is the wrong term for this variable. In fact, this whole concept is very magical. Speed should // be determined by the magnitude of the velocity, not by some random accumulator. On the other hand, I tried // that, and this works way better. So... droplet.Speed = (float)Math.Sqrt(droplet.Speed * droplet.Speed + Math.Abs(newHeight - oldHeight) * pGravity); droplet.Water = droplet.Water * (1 - pEvaporate); } } return(heightmap); }