public static void RunWateryScenario() { Bitmap jranjana = new Bitmap("C:\\Users\\Justin Murray\\Desktop\\maps\\input\\rivers_lr.png"); Field2d <float> field = new Utils.FieldFromBitmap(jranjana); BrownianTree tree = BrownianTree.CreateFromOther(field, (x) => x > 0.5f ? BrownianTree.Availability.Available : BrownianTree.Availability.Unavailable); tree.RunDefaultTree(); HydrologicalField hydro = new HydrologicalField(tree); var sets = hydro.FindContiguousSets(); List <TreeNode <Point2d> > riverForest = new List <TreeNode <Point2d> >(); foreach (var river in sets[HydrologicalField.LandType.Shore]) { riverForest.Add(river.MakeTreeFromContiguousSet(pt => { // Warning: naive non-boundary-checking test-only implementation. This will probably CRASH THE PROGRAM // if a river happens to border the edge of the map. return (hydro[pt.y + 1, pt.x + 1] == HydrologicalField.LandType.Ocean || hydro[pt.y + 1, pt.x + 0] == HydrologicalField.LandType.Ocean || hydro[pt.y + 1, pt.x - 1] == HydrologicalField.LandType.Ocean || hydro[pt.y + 0, pt.x - 1] == HydrologicalField.LandType.Ocean || hydro[pt.y - 1, pt.x - 1] == HydrologicalField.LandType.Ocean || hydro[pt.y - 1, pt.x + 0] == HydrologicalField.LandType.Ocean || hydro[pt.y - 1, pt.x + 1] == HydrologicalField.LandType.Ocean || hydro[pt.y + 0, pt.x + 1] == HydrologicalField.LandType.Ocean); })); } DrainageField draino = new DrainageField(hydro, riverForest); List <TreeNode <TreeNode <Point2d> > > riverSets = new List <TreeNode <TreeNode <Point2d> > >(); foreach (var river in riverForest) { riverSets.Add(river.GetMajorSubtrees(node => node.Depth() > 15)); } using (var file = System.IO.File.OpenWrite("C:\\Users\\Justin Murray\\Desktop\\maps\\output\\report.txt")) using (var writer = new System.IO.StreamWriter(file)) { riverSets.OrderByDescending(set => set.Size()).Select(riverSet => { writer.WriteLine("River of size " + riverSet.value.Size() + " with " + riverSet.Size() + " separate sub-rivers."); foreach (var river in riverSet.ToArray().OrderByDescending(t => t.Depth())) { writer.WriteLine("\tPart of river with " + river.value.Depth() + " depth and " + (river.Size() - 1) + " tributaries."); } writer.WriteLine(); return(0); }).ToArray(); } Utils.OutputAsTributaryMap(sets, riverSets, draino, jranjana, "C:\\Users\\Justin Murray\\Desktop\\maps\\output\\tree.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)); }