Wow , it has been 31 days since that last post? Damn I’m lazy. Ok so in this second part of the series , we’ll create our island and visualize it in 2D with some basic colors.
Remember we had our voronoi diagram and our polygons in previous post? Now we’ll need a logic to decide which polygons will be ocean/water and which part will be land. There are some generic algorithms like perlin noise for these stuff , but I decided to use a sine wave function which I saw in Amit’s example and actually it looks kinda better than perlin noise to me.
It’s a simple function which takes a point ( between 0 and 1 since we’ll use sine wave ) and returns a boolean to set IsLand property of the polygon.
It goes something like this ,
private bool IsLandShape(Point point) { double ISLAND_FACTOR = 1.07; int bumps = islandRandom.Next(1, 6); double startAngle = islandRandom.NextDouble() * 2 * Math.PI; double dipAngle = islandRandom.NextDouble() * 2 * Math.PI; double dipWidth = islandRandom.Next(2, 7) / 10; double angle = Math.Atan2(point.Y, point.X); double length = 0.5 * (Math.Max(Math.Abs(point.X), Math.Abs(point.Y)) + GetPointLength(point)); double r1 = 0.5 + 0.40 * Math.Sin(startAngle + bumps * angle + Math.Cos((bumps + 3) * angle)); double r2 = 0.7 - 0.20 * Math.Sin(startAngle + bumps * angle - Math.Sin((bumps + 2) * angle)); if (Math.Abs(angle - dipAngle) < dipWidth || Math.Abs(angle - dipAngle + 2 * Math.PI) < dipWidth || Math.Abs(angle - dipAngle - 2 * Math.PI) < dipWidth) { r1 = r2 = 0.2; } return (length < r1 || (length > r1 * ISLAND_FACTOR && length < r2)); }
I have to admit I just (shamelessly) converted Amit’s AS3 code to C# for this function and don’t totally understand what’s going on. I’m not really good at math and I didn’t want to waste time on this one. For all I know , it returns a “noisy” boolean series. Yes I’m a terrible person and coder , let’s move on.
Now we’ll just iterate through corners and set water/land values according to results of the function above. Not much to see here ;
foreach (var c in App.AppMap.Corners.Values) { c.Water = !InLand(c.Point); // calculate land&water corners }
Now that we have set corners’ land/water values , we’ll iterate through polygons ( centers ) and decide if a polygon is water or land according to states of it’s own corners. Also we’ll do some other stuff like fixing edges for polygons at the edge of the map and ordering corners as we’ll need them in order for creating visuals ( WPF Polygons ).
First ;
foreach (var ct in App.AppMap.Centers.Values) { ct.FixBorders(); //Fix edges at map edge, set "border" and "ocean" values ct.OrderCorners(); //Order corners clockwise as we'll need it for polygons and 3d stuff //if it touches any water-corner , it's water ct.Water = (ct.Corners.Any(x => x.Water)) ? true : false; }
As you can see I decided to set a polygon as water ( or sea , whatever ) if it has even one corner set as water. Of course you can use the exact opposite here ( land if it has one corner set as land ) or something more complex.
Then we’ll floodfill the oceans starting from map edges. Oh I set polygons at map edge as “Ocean” in FixBorders method but that method is pretty ugly and should be rewritten, so I’m not going to talk about it here. You can just go set a polygon as Ocean if it has a corner at map boundaries ( like X = 0 or Y = 0 etc )
var Oceans = new Queue<Center>(); //start with oceans at the borders foreach (Center c in App.AppMap.Centers.Values.Where(c => c.Ocean)) { Oceans.Enqueue(c); } //floodfill oceans while (Oceans.Count > 0) { Center c = Oceans.Dequeue(); foreach (Center n in c.Neighbours.Where(x => !x.Ocean)) { if (n.Corners.Any(x => x.Water)) { n.Ocean = true; if (!Oceans.Contains(n)) Oceans.Enqueue(n); } else { n.Coast = true; } } }
I guess it’s pretty clear what’s going on here. We just ahve a queue for Oceans and we floodfill starting from the polygons at the edge of the map. Then we set every polygon , which has at least one corner set as water , as ocean. A nice little touch here, if a polygon we reached by this floodfill , doesn’t have any corners set as water ( all land ) this means that’s the coast! So we set it as coast and later we’ll visualize it as beach.
One last thing , we have to set polygon “biome”. We’ll use biome for template selection at the front end. You know ocean will be blue and land will be brown and stuff like that.
foreach (Center c in App.AppMap.Centers.Values) { c.SetBiome(); }
Just wanted to clarify what it is.
Hmm we now have our island , created oceans and lands even beaches. Should be enough for this post but of course we need to visualize this all now right?
Since we already set biome for all polygons , we just need to set a visual style for that biome and use a template selector to choose polygon styles according to polygon biomes.
It’s kinda hard to write and explain Xaml stuff but I’ll try ; we’ll use this style for oceans
<DataTemplate x:Key="Ocean"> <Polygon Points="{Binding Converter={StaticResource MapPointToPointConverter}}" Fill="#363661" /> </DataTemplate>
It creates a Polygon , do change corner points according to polygon center and fill the polygon with that probably blue-ish color. You can find all styles ( and even more, old test styles, which I forgot to delete ) in MapResource.xaml in the solution.
And since we’re using a Listbox as item container, we need to give our listbox a function to help him decide how to choose styles for each polygon.
It’s much longer but I’ll just paste a part of it as sample ;
public class MapDataTemplateSelector : DataTemplateSelector { public override DataTemplate SelectTemplate(object item, DependencyObject container) { if (item != null && item is Center) { var center = item as Center; var window = Application.Current.MainWindow; return window.FindResource(center.Biome) as DataTemplate; } } }
It’s pretty straight-forward I guess. If the item is a Center ( polygon ) we just get the MainWindow ( which holds all styles , actually I doubt we need to do that but anyway ) and find the style ( FindResource ) according to that Center’s Biome. Listbox will handle the rest , easy peasy.
Now this is how it should look;
In the next episode? Hmm probably some elevation work maybe even rivers!
Hope you enjoy it!