/// <summary> /// Must be called after changing one cell geometry. /// </summary> void UpdateProvinceGeometry(MapProvince prov, WorldMapStrategyKit.MapGenerator.Geom.Polygon poly) { // Copy new polygon definition prov.region.polygon = poly; // Update segments list prov.region.segments.Clear(); List <Segment> segmentCache = new List <Segment>(cellNeighbourHit.Keys); WorldMapStrategyKit.MapGenerator.Geom.Contour contour0 = poly.contours[0]; int pointCount = contour0.points.Count; int segmentCacheCount = segmentCache.Count; for (int k = 0; k < pointCount; k++) { Segment s = contour0.GetSegment(k); bool found = false; // Search this segment in the segment cache for (int j = 0; j < segmentCacheCount; j++) { Segment o = segmentCache[j]; if ((Point.EqualsBoth(o.start, s.start) && Point.EqualsBoth(o.end, s.end)) || (Point.EqualsBoth(o.end, s.start) && Point.EqualsBoth(o.start, s.end))) { prov.region.segments.Add(o); o.territoryIndex = prov.countryIndex; found = true; break; } } if (!found) { prov.region.segments.Add(s); } } // Refresh neighbours ProvincesUpdateNeighbours(); // Refresh rect2D ProvincesUpdateBounds(prov); // Refresh territories FindCountryFrontiers(); UpdateCountryBoundaries(); }
void ProvincesUpdateBounds(MapProvince cell) { if (cell.region.polygon.contours.Count == 0) { return; } Vector2[] points = cell.region.polygon.contours[0].GetVector2Points(); cell.region.points = points; // Update bounding rect float minx, miny, maxx, maxy; minx = miny = float.MaxValue; maxx = maxy = float.MinValue; int pointCount = points.Length; for (int p = 0; p < pointCount; p++) { Vector2 point = points[p]; if (point.x < minx) { minx = point.x; } if (point.x > maxx) { maxx = point.x; } if (point.y < miny) { miny = point.y; } if (point.y > maxy) { maxy = point.y; } } float rectWidth = maxx - minx; float rectHeight = maxy - miny; cell.region.rect2D = new Rect(minx, miny, rectWidth, rectHeight); cell.region.rect2DArea = rectWidth * rectHeight; }
void ProvincesFindNeighbours() { if (cellNeighbourHit == null) { cellNeighbourHit = new Dictionary <Segment, MapRegion>(50000); } else { cellNeighbourHit.Clear(); } int cellsCount = mapProvinces.Count; for (int k = 0; k < cellsCount; k++) { MapProvince cell = mapProvinces[k]; MapRegion region = cell.region; int numSegments = region.segments.Count; for (int i = 0; i < numSegments; i++) { Segment seg = region.segments[i]; if (cellNeighbourHit.ContainsKey(seg)) { MapRegion neighbour = cellNeighbourHit[seg]; if (neighbour != region) { if (!region.neighbours.Contains(neighbour)) { region.neighbours.Add(neighbour); neighbour.neighbours.Add(region); } } } else { cellNeighbourHit[seg] = region; } } } }
void CreateMapCities() { if (mapCountries == null) { return; } int countryCount = mapCountries.Count; if (mapCities == null) { mapCities = new List <MapCity>(); } else { mapCities.Clear(); } int totalCityCount = 0; for (int k = 0; k < countryCount; k++) { int cityCount = UnityEngine.Random.Range(numCitiesPerCountryMin, numCitiesPerCountryMax + 1); MapCountry country = mapCountries[k]; if (!country.visible) { continue; } int provincesCount = country.provinces.Count; country.hasCapital = false; for (int j = 0; j < provincesCount; j++) { country.provinces[j].hasCapital = false; } Vector2 pos; int countryCityCount = 0; for (int q = 0; q < cityCount; q++) { if (countryCityCount >= cityCount) { break; } for (int j = 0; j < provincesCount; j++) { if (countryCityCount >= cityCount) { break; } MapProvince prov = country.provinces[j]; if (!prov.visible) { continue; } Rect rect = prov.region.rect2D; if (rect.width > 0.00001f && rect.height > 0.00001f) { do { pos.x = rect.xMin + UnityEngine.Random.value * rect.width; pos.y = rect.yMin + UnityEngine.Random.value * rect.height; } while (!prov.region.Contains(pos.x, pos.y)); CITY_CLASS cityClass; if (!country.hasCapital) { country.hasCapital = true; cityClass = CITY_CLASS.COUNTRY_CAPITAL; } else if (!prov.hasCapital) { prov.hasCapital = true; cityClass = CITY_CLASS.REGION_CAPITAL; } else { cityClass = CITY_CLASS.CITY; } MapCity c = new MapCity("City" + totalCityCount++, j, k, 0, pos, cityClass); mapCities.Add(c); countryCityCount++; } } } } }
/// <summary> /// Generate and replace provinces, city and country data /// </summary> public void GenerateMap() { try { UnityEngine.Random.InitState(seed); GenerateHeightMap(); CreateMapProvinces(); AssignHeightMapToProvinces(true); CreateMapCountries(); CreateMapCities(); UnityEngine.Random.InitState(seedNames); // Replace countries int mapCountriesCount = mapCountries.Count; List <Country> newCountries = new List <Country>(mapCountriesCount); for (int k = 0; k < mapCountriesCount; k++) { MapCountry c = mapCountries[k]; Vector2[] points = c.region.points; if (points == null || points.Length < 3) { continue; } string name = GetUniqueRandomName(0, 4, usedNames); Country newCountry = new Country(name, "World", k); Region region = new Region(newCountry, newCountry.regions.Count); newCountry.regions.Add(region); region.UpdatePointsAndRect(points); map.RefreshCountryGeometry(newCountry); newCountries.Add(newCountry); } map.countries = newCountries.ToArray(); countryChanges = true; // Replace provinces usedNames.Clear(); int mapProvincesCount = mapProvinces.Count; List <Province> newProvinces = new List <Province>(mapProvincesCount); Province[] provinces = _map.provinces; if (provinces == null) { provinces = new Province[0]; } for (int k = 0; k < mapProvincesCount; k++) { MapProvince c = mapProvinces[k]; if (!c.visible) { continue; } Vector2[] points = c.region.points; if (points == null || points.Length < 3) { continue; } int countryIndex = map.GetCountryIndex(c.center); if (countryIndex < 0) { continue; } string name = GetUniqueRandomName(0, 4, usedNames); Province newProvince = new Province(name, countryIndex, k); newProvince.regions = new List <Region>(); Region region = new Region(newProvince, newProvince.regions.Count); newProvince.attrib["mapColor"] = c.color; newProvince.regions.Add(region); region.UpdatePointsAndRect(points); map.RefreshProvinceGeometry(newProvince); newProvinces.Add(newProvince); Country country = map.countries[countryIndex]; List <Province> countryProvinces = country.provinces != null ? new List <Province>(country.provinces) : new List <Province>(); countryProvinces.Add(newProvince); country.provinces = countryProvinces.ToArray(); } map.provinces = newProvinces.ToArray(); provinceChanges = true; // Replace cities usedNames.Clear(); int mapCitiesCount = mapCities.Count; List <City> newCities = new List <City>(mapCitiesCount); string provinceName = ""; for (int k = 0; k < mapCitiesCount; k++) { MapCity c = mapCities[k]; City newCity; Province prov = map.GetProvince(c.unity2DLocation); if (prov == null) { continue; } provinceName = prov != null ? prov.name : ""; string name = GetUniqueRandomName(0, 4, usedNames); newCity = new City(name, provinceName, prov.countryIndex, c.population, c.unity2DLocation, c.cityClass, k); newCities.Add(newCity); } map.cities = newCities.ToArray(); cityChanges = true; // Generate textures GenerateWorldTexture(); // Apply some complementary style if (changeStyle && heightGradientPreset != HeightMapGradientPreset.Custom) { map.frontiersColor = Color.black; map.frontiersColorOuter = Color.black; map.provincesColor = new Color(0.5f, 0.5f, 0.5f, 0.35f); if (heightGradientPreset == HeightMapGradientPreset.BlackAndWhite || heightGradientPreset == HeightMapGradientPreset.Grayscale) { map.countryLabelsColor = Color.black; map.countryLabelsShadowColor = new Color(0.9f, 0.9f, 0.9f, 0.75f); } else { map.countryLabelsColor = Color.white; map.countryLabelsShadowColor = new Color(0.1f, 0.1f, 0.1f, 0.75f); } } if (_map.earthStyle.isScenicPlus()) { _map.earthStyle = EARTH_STYLE.Natural; } // Save map data SaveGeneratedMapData(); map.showFrontiers = true; map.showCountryNames = true; map.waterLevel = seaLevel; map.Redraw(true); } catch (Exception ex) { if (!Application.isPlaying) { Debug.LogError("Error generating map: " + ex.ToString()); #if UNITY_EDITOR EditorUtility.DisplayDialog("Error Generating Map", "An error occured while generating map. Try choosing another 'Seed' value, reducing 'Border Curvature' amount or number of provinces.", "Ok"); #endif } } }
void VoronoiTesselation() { bool usesUserDefinedSites = false; if (voronoiSites != null && voronoiSites.Count > 0) { numProvinces = voronoiSites.Count; usesUserDefinedSites = true; } if (centers == null || centers.Length != numProvinces) { centers = new Point[numProvinces]; } for (int k = 0; k < centers.Length; k++) { if (usesUserDefinedSites) { Vector2 p = voronoiSites[k]; centers[k] = new Point(p.x, p.y); } else { centers[k] = new Point(UnityEngine.Random.Range(-0.49f, 0.49f), UnityEngine.Random.Range(-0.49f, 0.49f)); } } if (voronoi == null) { voronoi = new VoronoiFortune(); } for (int k = 0; k < goodGridRelaxation; k++) { voronoi.AssignData(centers); voronoi.DoVoronoi(); if (k < goodGridRelaxation - 1) { for (int j = 0; j < numProvinces; j++) { Point centroid = voronoi.cells[j].centroid; centers[j] = (centers[j] + centroid) / 2; } } } // Make cell regions: we assume cells have only 1 region but that can change in the future for (int k = 0; k < voronoi.cells.Length; k++) { VoronoiCell voronoiCell = voronoi.cells[k]; Vector2 center = voronoiCell.center.vector3; MapProvince cell = new MapProvince(center); MapRegion cr = new MapRegion(cell); if (edgeNoise > 0) { cr.polygon = voronoiCell.GetPolygon(voronoiCell.center, edgeMaxLength, edgeNoise); } else { cr.polygon = voronoiCell.GetPolygon(); } if (cr.polygon != null) { // Add segments int segmentsCount = voronoiCell.segments.Count; for (int i = 0; i < segmentsCount; i++) { Segment s = voronoiCell.segments[i]; if (!s.deleted) { if (edgeNoise > 0) { cr.segments.AddRange(s.subdivisions); } else { cr.segments.Add(s); } } } cell.region = cr; mapProvinces.Add(cell); } } }
void CreateMapCountries() { numCountries = Mathf.Clamp(numCountries, 1, Mathf.Min(numProvinces, MAX_TERRITORIES)); if (mapCountries == null) { mapCountries = new List <MapCountry>(numCountries); } else { mapCountries.Clear(); } for (int k = 0; k < mapProvinces.Count; k++) { mapProvinces[k].countryIndex = -1; } int cellsCount = mapProvinces.Count; for (int c = 0; c < numCountries; c++) { MapCountry territory = new MapCountry(c.ToString()); int territoryIndex = mapCountries.Count; int p = UnityEngine.Random.Range(0, cellsCount); int z = 0; while ((mapProvinces[p].countryIndex != -1 || !mapProvinces[p].visible || mapProvinces[p].ignoreTerritories) && z++ <= mapProvinces.Count) { p++; if (p >= mapProvinces.Count) { p = 0; } } if (z > mapProvinces.Count) { break; // no more territories can be found - this should not happen } MapProvince prov = mapProvinces[p]; prov.countryIndex = territoryIndex; territory.capitalCenter = prov.center; territory.provinces.Add(prov); mapCountries.Add(territory); } // Continue conquering cells int[] territoryCellIndex = new int[mapCountries.Count]; // Iterate one cell per country (this is not efficient but ensures balanced distribution) bool remainingCells = true; int territoriesCount = mapCountries.Count; while (remainingCells) { while (remainingCells) { remainingCells = false; for (int k = 0; k < territoriesCount; k++) { MapCountry territory = mapCountries[k]; int territoryCellsCount = territory.provinces.Count; for (int p = territoryCellIndex[k]; p < territoryCellsCount; p++) { MapRegion cellRegion = territory.provinces[p].region; int neighboursCount = cellRegion.neighbours.Count; for (int n = 0; n < neighboursCount; n++) { MapRegion otherRegion = cellRegion.neighbours[n]; MapProvince otherProv = (MapProvince)otherRegion.entity; if (otherProv.countryIndex == -1 && otherProv.visible && !otherProv.ignoreTerritories) { otherProv.countryIndex = k; territory.provinces.Add(otherProv); remainingCells = true; p = territory.provinces.Count; break; } } if (p < territoryCellsCount) // no free neighbours left for this cell { territoryCellIndex[k]++; } } } } // Check if there's any other cell without territory for (int k = 0; k < cellsCount; k++) { MapProvince cell = mapProvinces[k]; if (cell.countryIndex == -1 && cell.visible && !cell.ignoreTerritories) { int territoryIndex = UnityEngine.Random.Range(0, territoriesCount); cell.countryIndex = territoryIndex; mapCountries[territoryIndex].provinces.Add(cell); remainingCells = true; break; } } } FindCountryFrontiers(); UpdateCountryBoundaries(); }
void FindCountryFrontiers() { if (mapCountries == null || mapCountries.Count == 0) { return; } if (territoryFrontiers == null) { territoryFrontiers = new List <Segment>(cellNeighbourHit.Count); } else { territoryFrontiers.Clear(); } if (territoryNeighbourHit == null) { territoryNeighbourHit = new Dictionary <Segment, MapRegion>(50000); } else { territoryNeighbourHit.Clear(); } int terrCount = mapCountries.Count; Connector[] connectors = new Connector[terrCount]; for (int k = 0; k < terrCount; k++) { connectors[k] = new Connector(); MapCountry territory = mapCountries[k]; territory.provinces.Clear(); if (territory.region == null) { MapRegion territoryRegion = new MapRegion(territory); territory.region = territoryRegion; } mapCountries[k].region.neighbours.Clear(); } int cellCount = mapProvinces.Count; for (int k = 0; k < cellCount; k++) { MapProvince cell = mapProvinces[k]; if (cell.countryIndex >= terrCount) { continue; } bool validCell = cell.visible && cell.countryIndex >= 0; if (validCell) { mapCountries[cell.countryIndex].provinces.Add(cell); } MapRegion region = cell.region; int numSegments = region.segments.Count; for (int i = 0; i < numSegments; i++) { Segment seg = region.segments[i]; if (seg.border) { if (validCell) { territoryFrontiers.Add(seg); int territory1 = cell.countryIndex; connectors[territory1].Add(seg); seg.territoryIndex = territory1; } continue; } if (territoryNeighbourHit.ContainsKey(seg)) { MapRegion neighbour = territoryNeighbourHit[seg]; MapProvince neighbourCell = (MapProvince)neighbour.entity; int territory1 = cell.countryIndex; int territory2 = neighbourCell.countryIndex; if (territory2 != territory1) { territoryFrontiers.Add(seg); if (validCell) { connectors[territory1].Add(seg); seg.territoryIndex = (territory2 >= 0) ? -1 : territory1; // if segment belongs to a visible cell and valid territory2, mark this segment as disputed. Otherwise make it part of territory1 if (seg.territoryIndex < 0) { // add territory neigbhours MapRegion territory1Region = mapCountries[territory1].region; MapRegion territory2Region = mapCountries[territory2].region; if (!territory1Region.neighbours.Contains(territory2Region)) { territory1Region.neighbours.Add(territory2Region); } if (!territory2Region.neighbours.Contains(territory1Region)) { territory2Region.neighbours.Add(territory1Region); } } } if (territory2 >= 0) { connectors[territory2].Add(seg); } } } else { territoryNeighbourHit.Add(seg, region); seg.territoryIndex = cell.countryIndex; } } } for (int k = 0; k < terrCount; k++) { mapCountries[k].region = new MapRegion(mapCountries[k]); if (mapCountries[k].provinces.Count > 0) { mapCountries[k].region.polygon = connectors[k].ToPolygonFromLargestLineStrip(); } else { mapCountries[k].region.polygon = null; } } }
void AssignHeightMapToProvinces(bool colorGroundCells) { if (heightMapTexture == null) { return; } if (currentHeightmapWidth == 0) { return; } if (heightGradient == null) { heightGradientPreset = HeightMapGradientPreset.Colored; } GradientColorKey[] colorKeys; switch (heightGradientPreset) { case HeightMapGradientPreset.Colored: heightGradient = new Gradient(); colorKeys = new GradientColorKey[4]; colorKeys [0] = new GradientColorKey(Color.gray, 0f); colorKeys [1] = new GradientColorKey(new Color(0.133f, 0.545f, 0.133f), seaLevel); colorKeys [2] = new GradientColorKey(new Color(0.898f, 0.898f, 0.298f), (seaLevel + 1f) * 0.5f); colorKeys [3] = new GradientColorKey(Color.white, 1f); heightGradient.colorKeys = colorKeys; break; case HeightMapGradientPreset.ColoredLight: heightGradient = new Gradient(); colorKeys = new GradientColorKey[4]; colorKeys [0] = new GradientColorKey(Color.gray, 0f); colorKeys [1] = new GradientColorKey(new Color(0.333f, 0.745f, 0.333f), seaLevel); colorKeys [2] = new GradientColorKey(new Color(0.998f, 0.998f, 0.498f), (seaLevel + 1f) * 0.5f); colorKeys [3] = new GradientColorKey(Color.white, 1f); heightGradient.colorKeys = colorKeys; break; case HeightMapGradientPreset.Grayscale: heightGradient = new Gradient(); colorKeys = new GradientColorKey[3]; colorKeys [0] = new GradientColorKey(Color.black, 0f); colorKeys [1] = new GradientColorKey(Color.gray, seaLevel); colorKeys [2] = new GradientColorKey(Color.white, 1f); heightGradient.colorKeys = colorKeys; break; case HeightMapGradientPreset.BlackAndWhite: heightGradient = new Gradient(); colorKeys = new GradientColorKey[3]; colorKeys [0] = new GradientColorKey(Color.black, 0f); colorKeys [1] = new GradientColorKey(Color.white, seaLevel); colorKeys [2] = new GradientColorKey(Color.white, 1f); heightGradient.colorKeys = colorKeys; break; } int provCount = mapProvinces.Count; for (int k = 0; k < provCount; k++) { MapProvince prov = mapProvinces [k]; int x = (int)Mathf.Clamp((prov.center.x + 0.5f) * currentHeightmapWidth, 0, currentHeightmapWidth - 1); int y = (int)Mathf.Clamp((prov.center.y + 0.5f) * currentHeightmapHeight, 0, currentHeightmapHeight - 1); int j = y * currentHeightmapWidth + x; float h = heights [j]; if (h < seaLevel) { prov.visible = false; } else { prov.visible = true; prov.color = colorGroundCells ? heightGradient.Evaluate(h) : Color.white; } } }