private List <GraphicsPath> GenerateTerrainOutline() { // travel around an ellipse, with noise applied to it // 0.001 to 0.01 seems to work best for noise frequency, 100 works well for noise amplitude float frequency = 0.005f; float amplitude = Math.Min(Width, Height) / 2; var terrainNoiseX = new PerlinNoise(Random.Next(), frequency, amplitude, 4); var terrainNoiseY = new PerlinNoise(Random.Next(), frequency, amplitude, 4); var landmasses = new List <List <PointF> >(); var mainland = new List <PointF>(); landmasses.Add(mainland); var prevPoint = new PointF(Width / 2f, Height / 2f); float ellipseWidth = Width * 0.75f; float ellipseHeight = Height * 0.75f; int step = 0; for (float angle = (float)Math.PI * 2; angle > 0; angle -= 0.01f /* roughly 500 steps */) { step++; float ellipseX = Width / 2f + (float)Math.Cos(angle) * ellipseWidth / 2; float ellipseY = Height / 2f + (float)Math.Sin(angle) * ellipseHeight / 2; float placeX = ellipseX + terrainNoiseX.GetValue(ellipseX, ellipseY); float placeY = ellipseY + terrainNoiseY.GetValue(ellipseX, ellipseY); var nextPoint = new PointF(placeX, placeY); // See if prevPoint - nextPoint crosses ANY of the previous lines. If it does, we have a loop / dangler. // Ignore the first line and the most recent line, cos we may well touch those. for (int i = mainland.Count - 3; i > 1; i--) { var testEnd = mainland[i - 1]; var testStart = mainland[i]; if (LineSegmentsCross(prevPoint, nextPoint, testStart, testEnd)) { // Because we are ALWAYS working our way anticlockwise around the main landmass: // "outties" always have the "older" line cross the "newer" one from left to right. // "innies" always have the "older" line cross the "newer" one from right to left. // UNLESS we are dealing with "nested" outties or innies, in which case the direction swaps. // But maybe its ok to swap what we do there, as we will get larger islands / inlets that way, // as opposed to "chains" of smaller ones, which are perhaps less important - especially on a political map. bool chopOff = IsLeftOfLine(prevPoint, nextPoint, testEnd); HandleTerrainBoundaryLoop(landmasses, mainland, i, chopOff); break; } } mainland.Add(nextPoint); prevPoint = nextPoint; } ScaleToFitBounds(landmasses); Landmasses = landmasses .Select(points => { var types = new byte[points.Count]; for (var i = types.Length - 1; i >= 0; i--) { types[i] = 1; } return(new GraphicsPath(points.Select(p => new System.Drawing.PointF(p.X, p.Y)).ToArray(), types)); }) .ToList(); return(Landmasses); }