// World Political Map - Globe Edition for Unity - Main Script // Created by Ramiro Oliva (Kronnect) // Don't modify this script - changes could be lost if you upgrade to a more recent version of WPM // *************************************************************************** // This is the public API file - every property or public method belongs here // *************************************************************************** using UnityEngine; using System; using System.IO; using System.Collections; using System.Collections.Generic; using System.Text; using TMPro; using WPM.ClipperLib; namespace WPM { public enum FRONTIERS_DETAIL { Low = 0, High = 1 } public enum LABELS_QUALITY { Low = 0, Medium = 1, High = 2, NotUsed = 3 } public enum LABELS_ANTIALIAS { None = 1, x2 = 2, x4 = 4, x8 = 8 } public enum TEXT_ENGINE { TextMeshStandard = 0, TextMeshPro = 1 } public enum LABELS_RENDER_METHOD { Blended = 0, WorldSpace = 1 } public enum FRONTIERS_THICKNESS { Thin = 0, Custom = 1 } public enum COUNTRY_LABELS_ORIENTATION { Automatic = 0, Horizontal = 1 } /* Event definitions */ public delegate void CountryBeforeEnterEvent(int countryIndex, int regionIndex, ref bool ignoreCountry); public delegate void CountryEvent(int countryIndex, int regionIndex); /* Public WPM Class */ public partial class WorldMapGlobe : MonoBehaviour { public event CountryBeforeEnterEvent OnCountryBeforeEnter; public event CountryEvent OnCountryEnter; public event CountryEvent OnCountryExit; public event CountryEvent OnCountryPointerDown; public event CountryEvent OnCountryPointerUp; public event CountryEvent OnCountryClick; /// /// Complete list of countries and the continent name they belong to. /// public Country[] countries { get { return _countries; } set { _countries = value; lastCountryLookupCount = -1; } } Country _countryHighlighted; /// /// Returns Country under mouse position or null if none. /// public Country countryHighlighted { get { return _countryHighlighted; } } int _countryHighlightedIndex = -1; /// /// Returns currently highlighted country index in the countries list. /// public int countryHighlightedIndex { get { return _countryHighlightedIndex; } } Region _countryRegionHighlighted; /// /// Returns currently highlightd country's region. /// /// The country region highlighted. public Region countryRegionHighlighted { get { return _countryRegionHighlighted; } } int _countryRegionHighlightedIndex = -1; /// /// Returns currently highlighted region of the country. /// public int countryRegionHighlightedIndex { get { return _countryRegionHighlightedIndex; } } int _countryLastClicked = -1; /// /// Returns the last clicked country. /// public int countryLastClicked { get { return _countryLastClicked; } } int _countryRegionLastClicked = -1; /// /// Returns the last clicked country region index. /// public int countryRegionLastClicked { get { return _countryRegionLastClicked; } } [SerializeField] bool _enableCountryHighlight = true; /// /// Enable/disable country highlight when mouse is over. /// public bool enableCountryHighlight { get { return _enableCountryHighlight; } set { if (_enableCountryHighlight != value) { _enableCountryHighlight = value; isDirty = true; } } } /// /// Set whether all regions of active country should be highlighted. /// [SerializeField] bool _highlightAllCountryRegions = false; public bool highlightAllCountryRegions { get { return _highlightAllCountryRegions; } set { if (_highlightAllCountryRegions != value) { _highlightAllCountryRegions = value; isDirty = true; } } } [SerializeField] float _countryHighlightMaxScreenAreaSize = 1f; /// /// Defines the maximum area of a highlighted country. To prevent filling the whole screen with the highlight color, you can reduce this value and if the highlighted screen area size is greater than this factor (1=whole screen) the country won't be filled (it will behave as selected though) /// public float countryHighlightMaxScreenAreaSize { get { return _countryHighlightMaxScreenAreaSize; } set { if (_countryHighlightMaxScreenAreaSize != value) { _countryHighlightMaxScreenAreaSize = value; isDirty = true; } } } [SerializeField] bool _showFrontiers = true; /// /// Toggle frontiers visibility. /// public bool showFrontiers { get { return _showFrontiers; } set { if (value != _showFrontiers) { _showFrontiers = value; isDirty = true; if (frontiersLayer != null) { frontiersLayer.SetActive(_showFrontiers); } else if (_showFrontiers) { DrawFrontiers(); } } } } [SerializeField] bool _showCoastalFrontiers = false; /// /// Toggle coastal frontiers visibility. /// public bool showCoastalFrontiers { get { return _showCoastalFrontiers; } set { if (value != _showCoastalFrontiers) { _showCoastalFrontiers = value; isDirty = true; OptimizeFrontiers(); DrawFrontiers(); DrawInlandFrontiers(); } } } [SerializeField] Color _fillColor = new Color(1, 0, 0, 0.7f); /// /// Fill color to use when the mouse hovers a country's region. /// public Color fillColor { get { if (hudMatCountry != null) { return hudMatCountry.color; } else { return _fillColor; } } set { if (_fillColor != value) { _fillColor = value; isDirty = true; if (hudMatCountry != null && _fillColor != hudMatCountry.color) { hudMatCountry.color = _fillColor; } } } } [SerializeField] Color _frontiersColor = Color.green; /// /// Global color for frontiers. /// public Color frontiersColor { get { if (frontiersMatCurrent != null) { return frontiersMatCurrent.color; } else { return _frontiersColor; } } set { if (value != _frontiersColor) { _frontiersColor = value; isDirty = true; UpdateFrontiersMat(); } } } [SerializeField] bool _showOutline = true; /// /// Toggle outline visibility. /// public bool showOutline { get { return _showOutline; } set { if (value != _showOutline) { _showOutline = value; isDirty = true; } } } [SerializeField] bool _showProvinceCountryOutline; /// /// Toggle outline visibility of the country when a province is highlighted /// public bool showProvinceCountryOutline { get { return _showProvinceCountryOutline; } set { if (value != _showProvinceCountryOutline) { _showProvinceCountryOutline = value; isDirty = true; } } } [SerializeField] Color _outlineColor = Color.black; /// /// Outline color. /// public Color outlineColor { get { if (outlineMatCurrent != null) { return outlineMatCurrent.color; } else { return _outlineColor; } } set { if (value != _outlineColor) { _outlineColor = value; isDirty = true; if (outlineMatCurrent != null && _outlineColor != outlineMatCurrent.color) { outlineMatCurrent.color = _outlineColor; } } } } [SerializeField] FRONTIERS_DETAIL _frontiersDetail = FRONTIERS_DETAIL.Low; public FRONTIERS_DETAIL frontiersDetail { get { return _frontiersDetail; } set { if (_frontiersDetail != value) { _frontiersDetail = value; isDirty = true; ReloadData(); } } } [SerializeField] FRONTIERS_THICKNESS _frontiersThicknessMode = FRONTIERS_THICKNESS.Thin; /// /// Thin or custom thickness. /// public FRONTIERS_THICKNESS frontiersThicknessMode { get { return _frontiersThicknessMode; } set { if (value != _frontiersThicknessMode) { _frontiersThicknessMode = value; isDirty = true; DrawFrontiers(); } } } [SerializeField] float _frontiersThickness = 0.01f; /// /// Thickness for the country frontiers. Only supported on systems capable of Shader Model 4+ /// public float frontiersThickness { get { return _frontiersThickness; } set { if (value != _frontiersThickness) { _frontiersThickness = value; isDirty = true; UpdateFrontiersMat(); } } } [SerializeField] bool _showCountryNames = false; public bool showCountryNames { get { return _showCountryNames; } set { if (value != _showCountryNames) { #if TRACE_CTL Debug.Log ("CTL " + DateTime.Now + ": showcountrynames!"); #endif _showCountryNames = value; isDirty = true; if (gameObject.activeInHierarchy) { if (!showCountryNames) { DestroyMapLabels(); } else { DrawMapLabels(); // Cool scrolling animation for map labels following... if (Application.isPlaying && _labelsRenderMethod == LABELS_RENDER_METHOD.Blended) { for (int k = 0; k < _countries.Length; k++) { GameObject o = _countries[k].labelTextGameObject; if (o != null) { LabelAnimator anim = o.AddComponent(); anim.destPos = o.transform.localPosition; anim.startPos = o.transform.localPosition + Misc.Vector3right * 100.0f * Mathf.Sign(o.transform.localPosition.x); anim.duration = 1.0f; anim.map = this; } } } } } } } } [SerializeField] COUNTRY_LABELS_ORIENTATION _countryLabelsOrientation = COUNTRY_LABELS_ORIENTATION.Automatic; /// /// Line orientation for country labels /// public COUNTRY_LABELS_ORIENTATION countryLabelsOrientation { get { return _countryLabelsOrientation; } set { if (_countryLabelsOrientation != value) { _countryLabelsOrientation = value; RedrawMapLabels(); isDirty = true; } } } [SerializeField] bool _countryLabelsEnableAutomaticFade = true; /// /// Automatic fading of country labels depending on camera distance and label screen size /// public bool countryLabelsEnableAutomaticFade { get { return _countryLabelsEnableAutomaticFade; } set { if (_countryLabelsEnableAutomaticFade != value) { _countryLabelsEnableAutomaticFade = value; RedrawMapLabels(); isDirty = true; } } } [SerializeField] float _countryLabelsAutoFadeMaxHeight = 0.3f; /// /// Max height of a label relative to screen height (0..1) at which fade out starts /// public float countryLabelsAutoFadeMaxHeight { get { return _countryLabelsAutoFadeMaxHeight; } set { if (value != _countryLabelsAutoFadeMaxHeight) { _countryLabelsAutoFadeMaxHeight = value; _countryLabelsAutoFadeMinHeight = Mathf.Min(_countryLabelsAutoFadeMaxHeight, _countryLabelsAutoFadeMinHeight); isDirty = true; FadeCountryLabels(); } } } [SerializeField] int _countryLabelsFadePerFrame = 25; /// /// Max number of country labels faded per frame /// public int countryLabelsFadePerFrame { get { return _countryLabelsFadePerFrame; } set { if (value != _countryLabelsFadePerFrame) { _countryLabelsFadePerFrame = value; isDirty = true; } } } [SerializeField] float _countryLabelsAutoFadeMaxHeightFallOff = 0.2f; /// /// Fall off for fade labels when height is greater than min height /// public float countryLabelsAutoFadeMaxHeightFallOff { get { return _countryLabelsAutoFadeMaxHeightFallOff; } set { if (value != _countryLabelsAutoFadeMaxHeightFallOff) { _countryLabelsAutoFadeMaxHeightFallOff = value; isDirty = true; FadeCountryLabels(); } } } [SerializeField] float _countryLabelsAutoFadeMinHeight = 0.02f; /// /// Min height of a label relative to screen height (0..1) at which fade out starts /// public float countryLabelsAutoFadeMinHeight { get { return _countryLabelsAutoFadeMinHeight; } set { if (value != _countryLabelsAutoFadeMinHeight) { _countryLabelsAutoFadeMinHeight = value; _countryLabelsAutoFadeMaxHeight = Mathf.Max(_countryLabelsAutoFadeMaxHeight, _countryLabelsAutoFadeMinHeight); isDirty = true; FadeCountryLabels(); } } } [SerializeField] float _countryLabelsAutoFadeMinHeightFallOff = 0.02f; /// /// Fall off for fade labels when height is less than min height /// public float countryLabelsAutoFadeMinHeightFallOff { get { return _countryLabelsAutoFadeMinHeightFallOff; } set { if (value != _countryLabelsAutoFadeMinHeightFallOff) { _countryLabelsAutoFadeMinHeightFallOff = value; isDirty = true; FadeCountryLabels(); } } } [SerializeField] float _countryLabelsAbsoluteMinimumSize = 0.5f; public float countryLabelsAbsoluteMinimumSize { get { return _countryLabelsAbsoluteMinimumSize; } set { if (value != _countryLabelsAbsoluteMinimumSize) { _countryLabelsAbsoluteMinimumSize = value; isDirty = true; if (_showCountryNames) RedrawMapLabels(); } } } [SerializeField] float _countryLabelsSize = 0.25f; public float countryLabelsSize { get { return _countryLabelsSize; } set { if (value != _countryLabelsSize) { _countryLabelsSize = value; isDirty = true; if (_showCountryNames) RedrawMapLabels(); } } } [SerializeField] LABELS_QUALITY _labelsQuality = LABELS_QUALITY.Medium; public LABELS_QUALITY labelsQuality { get { return _labelsQuality; } set { if (value != _labelsQuality) { _labelsQuality = value; isDirty = true; if (_showCountryNames) { DestroyOverlay(); // needs to recreate the render texture DrawMapLabels(); } } } } [SerializeField] LABELS_ANTIALIAS _labelsAntialias = LABELS_ANTIALIAS.None; public LABELS_ANTIALIAS labelsAntialias { get { return _labelsAntialias; } set { if (value != _labelsAntialias) { _labelsAntialias = value; isDirty = true; DestroyOverlay(); // needs to recreate the render texture Redraw(); } } } [SerializeField] LABELS_RENDER_METHOD _labelsRenderMethod = LABELS_RENDER_METHOD.Blended; /// /// Hoe labels should be rendered. Blended means they are rendered into the globe texture. /// public LABELS_RENDER_METHOD labelsRenderMethod { get { return _labelsRenderMethod; } set { if (value != _labelsRenderMethod) { _labelsRenderMethod = value; isDirty = true; if (_showCountryNames) { DestroyOverlay(); // needs to recreate the render texture RedrawMapLabels(); } } } } [SerializeField] float _labelsElevation = 0; public float labelsElevation { get { return _labelsElevation; } set { if (value != _labelsElevation) { _labelsElevation = value; isDirty = true; if (_labelsRenderMethod == LABELS_RENDER_METHOD.WorldSpace) { DestroyOverlay(); DrawMapLabels(); } else { if (sphereOverlayLayer != null) { AdjustSphereOverlayLayerScale(); } } } } } [SerializeField] bool _showLabelsShadow = true; /// /// Draws a shadow under map labels. Specify the color using labelsShadowColor. /// /// true if show labels shadow; otherwise, false. public bool showLabelsShadow { get { return _showLabelsShadow; } set { if (value != _showLabelsShadow) { _showLabelsShadow = value; isDirty = true; if (gameObject.activeInHierarchy) { RedrawMapLabels(); } } } } [SerializeField] Color _countryLabelsColor = Color.white; /// /// Color for map labels. /// public Color countryLabelsColor { get { return _countryLabelsColor; } set { if (value != _countryLabelsColor) { _countryLabelsColor = value; isDirty = true; if (gameObject.activeInHierarchy) { RedrawMapLabels(); } } } } [SerializeField] Color _countryLabelsShadowColor = Color.black; /// /// Color for map labels. /// public Color countryLabelsShadowColor { get { return _countryLabelsShadowColor; } set { if (value != _countryLabelsShadowColor) { _countryLabelsShadowColor = value; isDirty = true; if (gameObject.activeInHierarchy) { if (countryLabelsTextEngine == TEXT_ENGINE.TextMeshPro) { DrawMapLabels(); } else { labelsShadowMaterial.color = _countryLabelsShadowColor; } } } } } [SerializeField] float _countryLabelsShadowOffset = 1f; public float countryLabelsShadowOffset { get { return _countryLabelsShadowOffset; } set { if (value != _countryLabelsShadowOffset) { _countryLabelsShadowOffset = value; isDirty = true; if (gameObject.activeInHierarchy) { RedrawMapLabels(); } } } } [SerializeField] Font _countryLabelsFont; /// /// Gets or sets the default font for country labels /// public Font countryLabelsFont { get { return _countryLabelsFont; } set { if (value != _countryLabelsFont) { _countryLabelsFont = value; isDirty = true; ReloadFont(); RedrawMapLabels(); } } } [SerializeField] TEXT_ENGINE _countryLabelsTextEngine = TEXT_ENGINE.TextMeshStandard; public TEXT_ENGINE countryLabelsTextEngine { get { return _countryLabelsTextEngine; } set { if (_countryLabelsTextEngine != value) { _countryLabelsTextEngine = value; isDirty = true; RedrawMapLabels(); } } } [SerializeField] TMP_FontAsset _countryLabelsFontTMPro; public TMP_FontAsset countryLabelsFontTMPro { get { return _countryLabelsFontTMPro; } set { if (_countryLabelsFontTMPro != value) { _countryLabelsFontTMPro = value; ReloadFont(); RedrawMapLabels(); } } } [SerializeField] Material _countryLabelsFontTMProMaterial; public Material countryLabelsFontTMProMaterial { get { return _countryLabelsFontTMProMaterial; } set { if (_countryLabelsFontTMProMaterial != value) { _countryLabelsFontTMProMaterial = value; ReloadFont(); RedrawMapLabels(); } } } [SerializeField] bool _labelsFaceToCamera = false; /// /// Ensure labels are always readable /// public bool labelsFaceToCamera { get { return _labelsFaceToCamera; } set { if (value != _labelsFaceToCamera) { _labelsFaceToCamera = value; isDirty = true; } } } [SerializeField] bool _enableCountryEnclaves = false; /// /// Allows a country to be surrounded by another country /// public bool enableCountryEnclaves { get { return _enableCountryEnclaves; } set { if (value != _enableCountryEnclaves) { _enableCountryEnclaves = value; isDirty = true; } } } string _countryAttributeFile = COUNTRY_ATTRIB_DEFAULT_FILENAME; public string countryAttributeFile { get { return _countryAttributeFile; } set { if (value != _countryAttributeFile) { _countryAttributeFile = value; if (_countryAttributeFile == null) _countryAttributeFile = COUNTRY_ATTRIB_DEFAULT_FILENAME; isDirty = true; ReloadCountryAttributes(); } } } #region Public API area /// /// Creates a new country with no regions. /// /// Returns the index of the new country in the map.countries array. public int CountryCreate(string name, string continent) { int countryIndex = GetCountryIndex(name); if (countryIndex >= 0) return -1; Country country = new Country(name, continent); return CountryAdd(country); } /// /// Adds a new country which has been properly initialized. Used by the Map Editor. Name must be unique. /// /// -1 if country was not added, new country index otherwise. public int CountryAdd(Country country) { int countryIndex = GetCountryIndex(country.name); if (countryIndex >= 0) return -1; Country[] newCountries = new Country[countries.Length + 1]; for (int k = 0; k < countries.Length; k++) { newCountries[k] = countries[k]; } int newCountryIndex = newCountries.Length - 1; newCountries[newCountryIndex] = country; countries = newCountries; RefreshCountryGeometry(country); return newCountryIndex; } /// /// Renames the country. Name must be unique, different from current and one letter minimum. /// /// true if country was renamed, false otherwise. public bool CountryRename(string oldName, string newName) { if (newName == null || newName.Length == 0) return false; int countryIndex = GetCountryIndex(oldName); int newCountryIndex = GetCountryIndex(newName); if (countryIndex < 0 || newCountryIndex >= 0) return false; // Ensure dependencies are loaded if (_provinces == null) ReadProvincesPackedString(); if (_cities == null) ReadCitiesPackedString(); if (mountPoints == null) ReloadMountPointsData(); countries[countryIndex].name = newName; lastCountryLookupCount = -1; return true; } /// /// Deletes the country. Optionally also delete its dependencies (provinces, cities, mountpoints). /// /// true if country was deleted, false otherwise. public bool CountryDelete(int countryIndex, bool deleteDependencies, bool redraw = true) { if (internal_CountryDelete(countryIndex, deleteDependencies)) { // Update lookup dictionaries lastCountryLookupCount = -1; return true; } if (redraw) { Redraw(); } return false; } /// /// Deletes all provinces from a country. /// /// true, if provinces where deleted, false otherwise. public bool CountryDeleteProvinces(int countryIndex) { int numProvinces = provinces.Length; List newProvinces = new List(numProvinces); for (int k = 0; k < numProvinces; k++) { if (provinces[k] != null && provinces[k].countryIndex != countryIndex) { newProvinces.Add(provinces[k]); } } provinces = newProvinces.ToArray(); lastProvinceLookupCount = -1; return true; } public void CountriesDeleteFromContinent(string continentName) { HideCountryRegionHighlights(true); ProvincesDeleteOfSameContinent(continentName); CitiesDeleteFromContinent(continentName); MountPointsDeleteFromSameContinent(continentName); List newAdmins = new List(countries.Length - 1); for (int k = 0; k < countries.Length; k++) { if (!countries[k].continent.Equals(continentName)) { newAdmins.Add(countries[k]); } else { int lastIndex = newAdmins.Count - 1; // Updates country index in provinces if (provinces != null) { for (int p = 0; p < _provinces.Length; p++) { if (_provinces[p].countryIndex > lastIndex) { _provinces[p].countryIndex--; } } } // Updates country index in cities if (cities != null) { for (int c = 0; c < cities.Count; c++) { if (cities[c].countryIndex > lastIndex) { cities[c].countryIndex--; } } } // Updates country index in mount points if (mountPoints != null) { for (int c = 0; c < mountPoints.Count; c++) { if (mountPoints[c].countryIndex > lastIndex) { mountPoints[c].countryIndex--; } } } } } countries = newAdmins.ToArray(); lastCountryLookupCount = -1; } /// /// Given a country name returns the Country object. /// /// The country. /// Country name. public Country GetCountry(string countryName) { int countryIndex = GetCountryIndex(countryName); return GetCountry(countryIndex); } /// /// Given a country index returns the Country object /// /// The country. /// Country index. public Country GetCountry(int countryIndex) { if (countryIndex >= 0 && countryIndex < countries.Length) { return countries[countryIndex]; } return null; } /// /// Returns the index of a country in the countries collection by its name. /// public int GetCountryIndex(string countryName) { int countryIndex; if (countryLookup != null && _countryLookup.TryGetValue(countryName, out countryIndex)) return countryIndex; else return -1; } /// /// Returns the country capital city object. /// /// The country capital. /// Country name. public City GetCountryCapital(string countryName) { int cityIndex = GetCountryCapitaIndex(countryName); if (cityIndex < 0) return null; return _cities[cityIndex]; } /// /// Returns the index of the capital city of a country /// /// The country capita index. /// Country name. public int GetCountryCapitaIndex(string countryName) { int countryIndex = GetCountryIndex(countryName); if (countryIndex < 0) return -1; return _countries[countryIndex].cityCapitalIndex; } /// /// Returns the index of a country in the countries by its FIPS 10 4 code. /// public int GetCountryIndexByFIPS10_4(string fips) { if (string.IsNullOrEmpty(fips)) return -1; for (int k = 0; k < _countries.Length; k++) { if (_countries[k].fips10_4.Equals(fips)) { return k; } } return -1; } /// /// Returns the index of a country in the countries by its ISO A-2 code. /// public int GetCountryIndexByISO_A2(string iso_a2) { if (string.IsNullOrEmpty(iso_a2)) return -1; for (int k = 0; k < _countries.Length; k++) { if (_countries[k].iso_a2.Equals(iso_a2)) { return k; } } return -1; } /// /// Returns the index of a country in the countries by its ISO A-3 code. /// public int GetCountryIndexByISO_A3(string iso_a3) { if (string.IsNullOrEmpty(iso_a3)) return -1; for (int k = 0; k < _countries.Length; k++) { if (_countries[k].iso_a3.Equals(iso_a3)) { return k; } } return -1; } /// /// Returns the index of a country in the countries by its ISO N-3 code. /// public int GetCountryIndexByISO_N3(string iso_n3) { if (string.IsNullOrEmpty(iso_n3)) return -1; for (int k = 0; k < _countries.Length; k++) { if (_countries[k].iso_n3.Equals(iso_n3)) { return k; } } return -1; } /// /// Returns the index of a country in the countries collection by its reference. /// public int GetCountryIndex(Country country) { int countryIndex; if (countryLookup.TryGetValue(country.name, out countryIndex)) return countryIndex; else return -1; } /// /// Gets the index of the country that contains the provided map coordinates. This will ignore hidden countries. /// public int GetCountryIndex(Vector3 spherePosition) { int countryIndex, countryRegionIndex; if (GetCountryUnderSpherePosition(spherePosition, out countryIndex, out countryRegionIndex)) { return countryIndex; } return -1; } /// /// Gets the country that contains a given map coordinate or the country whose center is nearest to that coordinate. /// public int GetCountryNearPoint(Vector3 spherePosition) { int countryIndex = GetCountryIndex(spherePosition); if (countryIndex >= 0) return countryIndex; float minDist = float.MaxValue; int countryCount = countries.Length; for (int k = 0; k < countryCount; k++) { Country country = countries[k]; float dist = FastVector.SqrDistanceByValue(country.localPosition, spherePosition); // Vector3.SqrMagnitude (country.sphereCenter - spherePosition); if (dist < minDist) { minDist = dist; countryIndex = k; } } return countryIndex; } /// /// Returns a list of countries whose attributes matches predicate /// public void GetCountries(AttribPredicate predicate, List results) { if (results == null) return; for (int k = 0; k < _countries.Length; k++) { Country country = _countries[k]; if (country.hasAttributes && predicate(country.attrib)) results.Add(country); } } /// /// Gets XML attributes of all countries in jSON format. /// public string GetCountriesAttributes(bool prettyPrint = true) { if (countries == null) return null; return GetCountriesAttributes(new List(_countries), prettyPrint); } /// /// Gets XML attributes of provided countries in jSON format. /// public string GetCountriesAttributes(List countries, bool prettyPrint = true) { JSONObject composed = new JSONObject(); for (int k = 0; k < countries.Count; k++) { Country country = countries[k]; if (country.hasAttributes && country.attrib.keys != null) composed.AddField(country.name, country.attrib); } return composed.Print(prettyPrint); } /// /// Sets countries attributes from a jSON formatted string. /// public void SetCountriesAttributes(string jSON) { JSONObject composed = new JSONObject(jSON); if (composed.keys == null) return; int keyCount = composed.keys.Count; for (int k = 0; k < keyCount; k++) { string countryName = composed.keys[k]; int countryIndex = GetCountryIndex(countryName); if (countryIndex >= 0 && countryIndex < _countries.Length) { _countries[countryIndex].attrib = composed[k]; } } } /// /// Used by Editor. Returns the country index by screen position defined by a ray in the Scene View. /// public bool GetCountryIndex(Ray ray, out int countryIndex, out int regionIndex) { Vector3 hitPos; if (GetGlobeIntersection(ray, out hitPos)) { Vector3 localHit = transform.InverseTransformPoint(hitPos); if (GetCountryUnderMouse(localHit, out countryIndex, out regionIndex)) { return true; } } countryIndex = -1; regionIndex = -1; return false; } /// /// Returns all neighbour countries /// public List CountryNeighbours(int countryIndex) { List countryNeighbours = new List(); // Get country object Country country = countries[countryIndex]; // Iterate for all regions (a country can have several separated regions) for (int countryRegionIndex = 0; countryRegionIndex < country.regions.Count; countryRegionIndex++) { Region countryRegion = country.regions[countryRegionIndex]; // Get the neighbours for this region for (int neighbourIndex = 0; neighbourIndex < countryRegion.neighbours.Count; neighbourIndex++) { Region neighbour = countryRegion.neighbours[neighbourIndex]; Country neighbourCountry = (Country)neighbour.entity; if (!countryNeighbours.Contains(neighbourCountry)) { countryNeighbours.Add(neighbourCountry); } } } return countryNeighbours; } /// /// Get neighbours of the main region of a country /// public List CountryNeighboursOfMainRegion(int countryIndex) { Country country = countries[countryIndex]; return CountryNeighboursOfRegion(countryIndex, country.mainRegionIndex); } /// /// Get neighbours of a given region of a country /// public List CountryNeighboursOfRegion(int countryIndex, int regionIndex) { List countryNeighbours = new List(); // Get main region Country country = countries[countryIndex]; Region countryRegion = country.regions[regionIndex]; // Get the neighbours for this region for (int neighbourIndex = 0; neighbourIndex < countryRegion.neighbours.Count; neighbourIndex++) { Region neighbour = countryRegion.neighbours[neighbourIndex]; Country neighbourCountry = (Country)neighbour.entity; if (!countryNeighbours.Contains(neighbourCountry)) { countryNeighbours.Add(neighbourCountry); } } return countryNeighbours; } /// /// Get neighbours of the currently selected region /// public List CountryNeighboursOfCurrentRegion() { List countryNeighbours = new List(); // Get main region Region selectedRegion = countryRegionHighlighted; if (selectedRegion == null) return countryNeighbours; // Get the neighbours for this region for (int neighbourIndex = 0; neighbourIndex < selectedRegion.neighbours.Count; neighbourIndex++) { Region neighbour = selectedRegion.neighbours[neighbourIndex]; Country neighbourCountry = (Country)neighbour.entity; if (!countryNeighbours.Contains(neighbourCountry)) { countryNeighbours.Add(neighbourCountry); } } return countryNeighbours; } public bool GetCountryUnderSpherePosition(Vector3 spherePoint, out int countryIndex, out int countryRegionIndex) { return GetCountryUnderMouse(spherePoint, out countryIndex, out countryRegionIndex); } /// /// Starts navigation to target country. Returns false if country is not found. /// public CallbackHandler FlyToCountry(Country country) { int countryIndex = GetCountryIndex(country); if (countryIndex >= 0) { return FlyToCountry(countryIndex); } return CallbackHandler.Null; } /// /// Starts navigation to target country. Returns false if country is not found. /// public CallbackHandler FlyToCountry(string name, float duration = -1, float zoomLevel = 0) { int countryIndex = GetCountryIndex(name); if (countryIndex >= 0) { if (duration < 0) duration = _navigationTime; return FlyToCountry(countryIndex, duration, zoomLevel); } return CallbackHandler.Null; } /// /// Starts navigation to target country by index in the countries collection. Returns false if country is not found. /// public CallbackHandler FlyToCountry(int countryIndex) { return FlyToCountry(countryIndex, _navigationTime); } /// /// Starts navigating to target country by index in the countries collection with specified duration, ignoring NavigationTime property. /// Set duration to zero to go instantly. /// public CallbackHandler FlyToCountry(int countryIndex, float duration) { if (countryIndex < 0 || countryIndex >= countries.Length) return CallbackHandler.Null; return FlyToLocation(countries[countryIndex].localPosition, duration, 0, _navigationBounceIntensity); } /// /// Starts navigating to target country by index in the countries collection with specified duration, ignoring NavigationTime property. /// Set duration to zero to go instantly. /// Set zoomLevel to a value from 0 to 1 for the destination zoom level. A value of 0 will keep current zoom level. /// public CallbackHandler FlyToCountry(int countryIndex, float duration, float zoomLevel) { if (countryIndex < 0 || countryIndex >= countries.Length) return CallbackHandler.Null; return FlyToLocation(countries[countryIndex].localPosition, duration, zoomLevel, _navigationBounceIntensity); } /// /// Starts navigating to target country by index in the countries collection with specified duration, ignoring NavigationTime property. /// Set duration to zero to go instantly. /// Set zoomLevel to a value from 0 to 1 for the destination zoom level. A value of 0 will keep current zoom level. /// Set bounceIntensity to a value from 0 to 1 for a bouncing effect between current position and destination /// public CallbackHandler FlyToCountry(int countryIndex, float duration, float zoomLevel, float bounceIntensity) { if (countryIndex < 0 || countryIndex >= countries.Length) return CallbackHandler.Null; return FlyToLocation(countries[countryIndex].localPosition, duration, zoomLevel, bounceIntensity); } /// /// Colorize all regions of specified country by name. Returns false if not found. /// public bool ToggleCountrySurface(string name, bool visible, Color color = new Color(), bool drawOutline = false, Color outlineColor = new Color()) { int countryIndex = GetCountryIndex(name); if (countryIndex >= 0) { ToggleCountrySurface(countryIndex, visible, color, null, drawOutline, outlineColor); return true; } return false; } /// /// Colorize all regions of specified country by index in the countries collection. /// public void ToggleCountrySurface(int countryIndex, bool visible, Color color = new Color(), bool drawOutline = false, Color outlineColor = new Color()) { ToggleCountrySurface(countryIndex, visible, color, null, drawOutline, outlineColor); } /// /// Colorize all regions of specified country and assings a texture. /// public void ToggleCountrySurface(string name, bool visible, Color color, Texture2D texture, bool drawOutline = false, Color outlineColor = new Color()) { int countryIndex = GetCountryIndex(name); if (countryIndex < 0 || countryIndex >= countries.Length) return; for (int r = 0; r < countries[countryIndex].regions.Count; r++) { ToggleCountryRegionSurface(countryIndex, r, visible, color, texture, Misc.Vector2one, Misc.Vector2zero, 0, drawOutline, outlineColor); } } /// /// Colorize all regions of specified country and assings a texture. /// public void ToggleCountrySurface(int countryIndex, bool visible, Color color, Texture2D texture, bool drawOutline = false, Color outlineColor = new Color()) { if (!visible) { HideCountrySurface(countryIndex); return; } if (countryIndex < 0 || countryIndex >= countries.Length) return; int count = countries[countryIndex].regions.Count; for (int r = 0; r < count; r++) { ToggleCountryRegionSurface(countryIndex, r, visible, color, texture, Misc.Vector2one, Misc.Vector2zero, 0, drawOutline, outlineColor); } } /// /// Adds a country outline /// public void DrawCountryOutline(string countryName, Color color) { ToggleCountryOutline(countryName, true, color); } /// /// Adds a country outline /// public void DrawCountryOutline(int countryIndex, Color color) { ToggleCountryOutline(countryIndex, true, color); } /// /// Toggles on/off a country outline /// public void ToggleCountryOutline(string countryName, bool visible, Color color = default(Color)) { int countryIndex = GetCountryIndex(countryName); ToggleCountryOutline(countryIndex, visible, color); } /// /// Toggles on/off a country outline /// public void ToggleCountryOutline(int countryIndex, bool visible, Color color = default(Color)) { if (countryIndex < 0 || countryIndex >= countries.Length) return; Country country = countries[countryIndex]; int regionsCount = country.regions.Count; for (int k = 0; k < regionsCount; k++) { ToggleRegionOutline(country.regions[k], visible, color); } } /// /// Toggles on/off a country region outline /// public void ToggleCountryRegionOutline(int countryIndex, int regionIndex, bool visible, Color color = default(Color)) { if (countryIndex < 0 || countryIndex >= countries.Length) return; Country country = countries[countryIndex]; int regionsCount = country.regions.Count; if (regionIndex < 0 || regionIndex >= regionsCount) return; ToggleRegionOutline(country.regions[regionIndex], visible, color); } /// /// Uncolorize/hide specified country by index in the countries collection. /// public void HideCountrySurface(int countryIndex) { if (countryIndex < 0 || countryIndex >= countries.Length) return; for (int r = 0; r < countries[countryIndex].regions.Count; r++) { HideCountryRegionSurface(countryIndex, r); } } /// /// Colorize main region of a country by index in the countries collection. /// public GameObject ToggleCountryMainRegionSurface(int countryIndex, bool visible, Color color) { return ToggleCountryMainRegionSurface(countryIndex, visible, color, _showOutline, _outlineColor); } /// /// Add texture to main region of a country by index in the countries collection. /// public GameObject ToggleCountryMainRegionSurface(int countryIndex, bool visible, Texture2D texture) { return ToggleCountryMainRegionSurface(countryIndex, visible, Color.white, texture, Misc.Vector2one, Misc.Vector2zero, 0); } /// /// Colorize main region of a country by index in the countries collection. /// public GameObject ToggleCountryMainRegionSurface(int countryIndex, bool visible, Color color, bool drawOutline, Color outlineColor) { return ToggleCountryRegionSurface(countryIndex, countries[countryIndex].mainRegionIndex, visible, color, null, Misc.Vector3one, Misc.Vector3zero, 0, drawOutline, outlineColor); } /// /// Colorize main region of a country by index in the countries collection. /// /// Optional texture or null to colorize with single color public GameObject ToggleCountryMainRegionSurface(int countryIndex, bool visible, Color color, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation) { return ToggleCountryMainRegionSurface(countryIndex, visible, color, texture, textureScale, textureOffset, textureRotation, _showOutline, _outlineColor); } /// /// Colorize main region of a country by index in the countries collection. /// /// Optional texture or null to colorize with single color public GameObject ToggleCountryMainRegionSurface(int countryIndex, bool visible, Color color, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool drawOutline, Color outlineColor) { return ToggleCountryRegionSurface(countryIndex, countries[countryIndex].mainRegionIndex, visible, color, texture, textureScale, textureOffset, textureRotation, drawOutline, outlineColor); } /// /// Colorize specified region of a country by index in the countries collection. /// /// Optional texture or null to colorize with single color public GameObject ToggleCountryRegionSurface(int countryIndex, int regionIndex, bool visible, Color color) { return ToggleCountryRegionSurface(countryIndex, regionIndex, visible, color, null, Misc.Vector3one, Misc.Vector3zero, 0); } /// /// Colorize/texture specified region of a country by indexes. /// public GameObject ToggleCountryRegionSurface(int countryIndex, int regionIndex, bool visible, Color color, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation) { return ToggleCountryRegionSurface(countryIndex, regionIndex, visible, color, texture, textureScale, textureOffset, textureRotation, _showOutline, _outlineColor); } /// /// Colorize/texture specified region of a country by indexes. /// public GameObject ToggleCountryRegionSurface(int countryIndex, int regionIndex, bool visible, Color color, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool drawOutline, Color outlineColor) { if (!visible) { HideCountryRegionSurface(countryIndex, regionIndex); return null; } Region region = countries[countryIndex].regions[regionIndex]; int cacheIndex = GetCacheIndexForCountryRegion(countryIndex, regionIndex); GameObject surf; // Checks if current cached surface contains a material with a texture, if it exists but it has not texture, destroy it to recreate with uv mappings surfaces.TryGetValue(cacheIndex, out surf); // Should the surface be recreated? Material surfMaterial; if (surf != null) { surfMaterial = surf.GetComponent().sharedMaterial; if (texture != null && (region.customMaterial == null || textureScale != region.customTextureScale || textureOffset != region.customTextureOffset || textureRotation != region.customTextureRotation || !region.customMaterial.name.Equals(countryTexturizedMat.name))) { surfaces.Remove(cacheIndex); DestroyImmediate(surf); surf = null; } } // If it exists, activate and check proper material, if not create surface bool isHighlighted = countryHighlightedIndex == countryIndex && (countryRegionHighlightedIndex == regionIndex || _highlightAllCountryRegions) && _enableCountryHighlight; if (surf != null) { bool needMaterial = _highlightAllCountryRegions; if (!surf.activeSelf) { surf.SetActive(true); needMaterial = true; UpdateSurfaceCount(); } else { // Check if material is ok surfMaterial = surf.GetComponent().sharedMaterial; if ((texture == null && !surfMaterial.name.Equals(countryColoredMat.name)) || (texture != null && !surfMaterial.name.Equals(countryTexturizedMat.name)) || (surfMaterial.color != color && !isHighlighted) || (texture != null && region.customMaterial.mainTexture != texture)) needMaterial = true; } if (needMaterial) { Material goodMaterial = GetCountryColoredTexturedMaterial(color, texture); region.customMaterial = goodMaterial; region.customTextureOffset = textureOffset; region.customTextureRotation = textureRotation; region.customTextureScale = textureScale; ApplyMaterialToSurface(surf, goodMaterial); } if (drawOutline) { ToggleRegionOutline(region, drawOutline, outlineColor); } } else { surfMaterial = GetCountryColoredTexturedMaterial(color, texture); surf = GenerateCountryRegionSurface(countryIndex, regionIndex, surfMaterial, textureScale, textureOffset, textureRotation, drawOutline, outlineColor, false); } // If it was highlighted, highlight it again if (region.customMaterial != null && isHighlighted && region.customMaterial.color != hudMatCountry.color) { Material clonedMat = Instantiate(region.customMaterial); clonedMat.name = region.customMaterial.name; clonedMat.color = hudMatCountry.color; surf.GetComponent().sharedMaterial = clonedMat; countryRegionHighlightedObj = surf; } return surf; } /// /// Uncolorize/hide specified country by index in the countries collection. /// public void HideCountryRegionSurface(int countryIndex, int regionIndex) { int cacheIndex = GetCacheIndexForCountryRegion(countryIndex, regionIndex); GameObject surf = null; if (surfaces.TryGetValue(cacheIndex, out surf)) { if (surf != null) surf.SetActive(false); else surfaces.Remove(cacheIndex); } UpdateSurfaceCount(); countries[countryIndex].regions[regionIndex].customMaterial = null; } /// /// Highlights the country region specified. /// Internally used by the Editor component, but you can use it as well to temporarily mark a country region. /// /// Pass true only if you're sure you want to force refresh the geometry of the highlight (for instance, if the frontiers data has changed). If you're unsure, pass false. public GameObject ToggleCountryRegionSurfaceHighlight(int countryIndex, int regionIndex, Color color, bool drawOutline) { GameObject surf; Material mat = Instantiate(hudMatCountry); mat.color = color; mat.renderQueue--; int cacheIndex = GetCacheIndexForCountryRegion(countryIndex, regionIndex); bool existsInCache = surfaces.ContainsKey(cacheIndex); if (existsInCache) { surf = surfaces[cacheIndex]; if (surf == null) { surfaces.Remove(cacheIndex); } else { surf.SetActive(true); surf.GetComponent().sharedMaterial = mat; } } else { surf = GenerateCountryRegionSurface(countryIndex, regionIndex, mat, Misc.Vector2one, Misc.Vector2zero, 0, drawOutline, _outlineColor, true); } return surf; } /// /// Hides all colorized regions of all countries. /// public void HideCountrySurfaces() { for (int c = 0; c < countries.Length; c++) { HideCountrySurface(c); } } /// /// Flashes specified country by index in the countries collection. /// public void BlinkCountry(int countryIndex, Color color1, Color color2, float duration, float blinkingSpeed, bool smoothBlink = false) { if (countryIndex < 0 || countryIndex >= countries.Length) return; int mainRegionIndex = countries[countryIndex].mainRegionIndex; BlinkCountry(countryIndex, mainRegionIndex, color1, color2, duration, blinkingSpeed, smoothBlink); } /// /// Flashes specified country's region. /// public void BlinkCountry(int countryIndex, int regionIndex, Color color1, Color color2, float duration, float blinkingSpeed, bool smoothBlink = false) { BlinkCountry(countryIndex, regionIndex, color1, color2, duration, blinkingSpeed, _showOutline, _outlineColor, smoothBlink); } /// /// Flashes specified country's region. /// public void BlinkCountry(int countryIndex, int regionIndex, Color color1, Color color2, float duration, float blinkingSpeed, bool drawOutline, Color outlineColor, bool smoothBlink = false) { int cacheIndex = GetCacheIndexForCountryRegion(countryIndex, regionIndex); GameObject surf; bool disableAtEnd; if (surfaces.ContainsKey(cacheIndex)) { surf = surfaces[cacheIndex]; disableAtEnd = !surf.activeSelf; } else { surf = GenerateCountryRegionSurface(countryIndex, regionIndex, hudMatCountry, drawOutline, outlineColor, true); disableAtEnd = true; } surf.SetActive(true); SurfaceBlinker sb = surf.AddComponent(); sb.blinkMaterial = hudMatCountry; sb.color1 = color1; sb.color2 = color2; sb.duration = duration; sb.speed = blinkingSpeed; sb.disableAtEnd = disableAtEnd; sb.customizableSurface = countries[countryIndex].regions[regionIndex]; sb.smoothBlink = smoothBlink; } /// /// Returns proposedName if it's unique in the country collection. Otherwise it adds a suffix to the name to make it unique. /// /// The country unique name. public string GetCountryUniqueName(string proposedName) { string n = proposedName; int iteration = 2; while (countryLookup.ContainsKey(proposedName)) { proposedName = n + " " + iteration++; } return proposedName; } /// /// Returns an array of country names. The returning list can be grouped by continent. /// public string[] GetCountryNames(bool groupByContinent) { return GetCountryNames(groupByContinent, true); } /// /// Returns an array of country names. The returning list can be grouped by continent. /// public string[] GetCountryNames(bool groupByContinent, bool includeCountryIndex) { List c = new List(); if (countries == null) return c.ToArray(); string previousContinent = ""; for (int k = 0; k < countries.Length; k++) { Country country = countries[k]; if (groupByContinent) { if (!country.continent.Equals(previousContinent)) { c.Add(country.continent); previousContinent = country.continent; } if (includeCountryIndex) { c.Add(country.continent + "|" + country.name + " (" + k + ")"); } else { c.Add(country.continent + "|" + country.name); } } else { if (includeCountryIndex) { c.Add(country.name + " (" + k + ")"); } else { c.Add(country.name); } } } c.Sort(); if (groupByContinent) { int k = -1; while (++k < c.Count) { int i = c[k].IndexOf('|'); if (i > 0) { c[k] = " " + c[k].Substring(i + 1); } } } return c.ToArray(); } public string[] GetCountryNeighboursNames(int countryIndex, bool includeCountryIndex) { if (countryIndex < 0 || countryIndex >= countries.Length) return null; List c = new List(50); Region region = countries[countryIndex].mainRegion; int nc = region.neighbours.Count; if (nc == 0) return c.ToArray(); for (int k = 0; k < nc; k++) { Region nr = region.neighbours[k]; Country oc = (Country)nr.entity; if (includeCountryIndex) { int cIndex = GetCountryIndex(oc); c.Add(" " + oc.name + " (" + cIndex + ")"); } else { c.Add(" " + oc.name); } } c.Sort(); c.Insert(0, "Neighbours of " + countries[countryIndex].name); return c.ToArray(); } /// /// Returns the colored surface (game object) of a country. If it has not been colored yet, it will return null. /// public GameObject GetCountryRegionSurfaceGameObject(int countryIndex, int regionIndex) { int cacheIndex = GetCacheIndexForCountryRegion(countryIndex, regionIndex); GameObject surf = null; surfaces.TryGetValue(cacheIndex, out surf); return surf; } /// /// Returns the zoom level which shows the country main region in full screen /// /// The country region zoom level. /// Country index. public float GetCountryMainRegionZoomExtents(int countryIndex) { if (countryIndex < 0 || countryIndex >= _countries.Length) return 0; Country country = _countries[countryIndex]; return GetCountryRegionZoomExtents(countryIndex, country.mainRegionIndex); } /// /// Returns the zoom level which shows the country region in full screen /// /// The country region zoom level. /// Country index. /// Region index. public float GetCountryRegionZoomExtents(int countryIndex, int regionIndex) { if (countryIndex < 0 || countryIndex >= _countries.Length) return 0; Country country = _countries[countryIndex]; if (regionIndex < 0 || regionIndex >= country.regions.Count) return 0; return GetRegionZoomExtents(country.regions[regionIndex]); } /// /// Returns a list of countries that are visible (front facing camera) /// public List GetVisibleCountries() { List vc = new List(30); Camera cam = mainCamera; for (int k = 0; k < countries.Length; k++) { Country country = countries[k]; if (country.hidden) continue; // Check if country is facing camera Vector3 center = transform.TransformPoint(country.localPosition); Vector3 dir = center - transform.position; float d = Vector3.Dot(cam.transform.forward, dir); if (d < -0.2f) { // Check if center of country is inside viewport Vector3 vpos = cam.WorldToViewportPoint(center); float viewportMinX = cam.rect.xMin; float viewportMaxX = cam.rect.xMax; float viewportMinY = cam.rect.yMin; float viewportMaxY = cam.rect.yMax; if (vpos.x >= viewportMinX && vpos.x <= viewportMaxX && vpos.y >= viewportMinY && vpos.y <= viewportMaxY) { vc.Add(country); } else { // Check if some frontier point is inside viewport Vector3[] frontier = country.regions[country.mainRegionIndex].spherePoints; int step = 1 + frontier.Length / 25; for (int p = 0; p < frontier.Length; p += step) { Vector3 pos = transform.TransformPoint(frontier[p]); vpos = cam.WorldToViewportPoint(pos); if (vpos.x >= viewportMinX && vpos.x <= viewportMaxX && vpos.y >= viewportMinY && vpos.y <= viewportMaxY) { vc.Add(country); break; } } } } } return vc; } /// /// Returns a list of countries that are visible and overlaps the rectangle defined by two given sphere points /// public List GetVisibleCountries(Vector3 rectTopLeft, Vector3 rectBottomRight) { Vector2 latlon0, latlon1; latlon0 = Conversion.GetBillboardPosFromSpherePoint(rectTopLeft); latlon1 = Conversion.GetBillboardPosFromSpherePoint(rectBottomRight); Rect rect = new Rect(latlon0.x, latlon1.y, latlon1.x - latlon0.x, latlon0.y - latlon1.y); List selectedCountries = new List(); for (int k = 0; k < countries.Length; k++) { Country country = countries[k]; if (country.hidden) continue; if (selectedCountries.Contains(country)) continue; // Check if any of country's regions is inside rect int crc = country.regions.Count; for (int cr = 0; cr < crc; cr++) { Region region = country.regions[cr]; if (rect.Overlaps(region.rect2Dbillboard)) { selectedCountries.Add(country); break; } } } return selectedCountries; } /// /// Makes countryIndex absorb another country providing any of its regions. All regions are transfered to target country. /// This function is quite slow with high definition frontiers. /// /// Country index of the conquering country. /// Source region of the loosing country. public bool CountryTransferCountryRegion(int countryIndex, Region sourceCountryRegion, bool redraw = true) { int sourceCountryIndex = GetCountryIndex((Country)sourceCountryRegion.entity); if (countryIndex < 0 || sourceCountryIndex < 0 || countryIndex == sourceCountryIndex) return false; if (_provinces == null && !_showProvinces) { ReadProvincesPackedString(); // Forces loading of provinces } // Transfer all provinces records to target country Country sourceCountry = countries[sourceCountryIndex]; Country targetCountry = countries[countryIndex]; if (targetCountry.provinces != null && sourceCountry.provinces != null) { List destProvinces = new List(targetCountry.provinces); for (int k = 0; k < sourceCountry.provinces.Length; k++) { Province province = sourceCountry.provinces[k]; province.countryIndex = countryIndex; destProvinces.Add(province); } destProvinces.Sort(ProvinceSizeComparer); targetCountry.provinces = destProvinces.ToArray(); } // Transfer cities int cityCount = cities.Count; for (int k = 0; k < cityCount; k++) { if (cities[k].countryIndex == sourceCountryIndex) cities[k].countryIndex = countryIndex; } // Transfer mount points int mountPointCount = mountPoints.Count; for (int k = 0; k < mountPointCount; k++) { if (mountPoints[k].countryIndex == sourceCountryIndex) mountPoints[k].countryIndex = countryIndex; } // Add main region of the source country to target if they are joint Region targetRegion = targetCountry.regions[targetCountry.mainRegionIndex]; // Add region to target country's polygon - only if the country is touching or crossing target country frontier if (sourceCountryRegion.Intersects(targetRegion)) { RegionMagnet(sourceCountryRegion, targetRegion); Clipper clipper = new Clipper(); clipper.AddPaths(targetCountry.regions, PolyType.ptSubject); clipper.AddPath(sourceCountryRegion, PolyType.ptClip); clipper.Execute(ClipType.ctUnion, targetCountry); } else { // Add new region to country sourceCountryRegion.entity = targetRegion.entity; sourceCountryRegion.regionIndex = targetCountry.regions.Count; targetCountry.regions.Add(sourceCountryRegion); } // Transfer additional regions if (sourceCountry.regions.Count > 1) { List targetRegions = new List(targetCountry.regions); for (int k = 0; k < sourceCountry.regions.Count; k++) { Region otherRegion = sourceCountry.regions[k]; if (otherRegion != sourceCountryRegion) { targetRegions.Add(sourceCountry.regions[k]); } } targetCountry.regions = targetRegions; } // Fusion any adjacent regions that results from merge operation MergeAdjacentRegions(targetCountry); RegionSanitize(targetCountry.regions, false); // Finish operation internal_CountryDelete(sourceCountryIndex, false); if (countryIndex > sourceCountryIndex) countryIndex--; if (redraw) { RefreshCountryDefinition(countryIndex, null); Redraw(); } else { RefreshCountryGeometry(targetCountry); } return true; } /// /// Treats source country as a province that joins target country /// /// /// /// public bool CountryTransferAsProvince(int targetCountryIndex, int sourceCountryIndex, bool redraw = true) { Country sourceCountry = countries[sourceCountryIndex]; Country targetCountry = countries[targetCountryIndex]; // Add source country regions to target country targetCountry.regions.AddRange(sourceCountry.regions); // Create new province string provinceName = sourceCountry.name; Province newProvince = new Province(sourceCountry.name, targetCountryIndex); newProvince.regions = sourceCountry.regions; newProvince.mainRegionIndex = sourceCountry.mainRegionIndex; newProvince.latlonCenter = sourceCountry.latlonCenter; // Add province ProvinceAdd(newProvince); // Move all cities to target country int cityCount = cities.Count; for (int k = 0; k < cityCount; k++) { if (cities[k].countryIndex == sourceCountryIndex) { cities[k].countryIndex = targetCountryIndex; cities[k].province = provinceName; } } // Move all mount points to target country int provinceIndex = GetProvinceIndex(newProvince); int mpCount = mountPoints.Count; for (int k = 0; k < mpCount; k++) { if (mountPoints[k].countryIndex == sourceCountryIndex) { mountPoints[k].countryIndex = targetCountryIndex; mountPoints[k].provinceIndex = provinceIndex; } } // Delete source country CountryDelete(sourceCountryIndex, true, false); // Update geometry bounds for target country if (redraw) { targetCountryIndex = GetCountryIndex(targetCountry); RefreshCountryDefinition(targetCountryIndex, null); Redraw(); } else { RefreshCountryGeometry(targetCountry); } return true; } /// /// Changes province's owner to specified country and modifies frontiers/borders. /// Note: provinceRegion parameter usually is the province main region - although it does not matter since all regions will transfer as well. /// public bool CountryTransferProvinceRegion(int targetCountryIndex, Region provinceRegion, bool redraw = true) { int provinceIndex = GetProvinceIndex((Province)provinceRegion.entity); if (provinceIndex < 0 || targetCountryIndex < 0 || targetCountryIndex >= countries.Length) return false; // Province must belong to another country Province province = provinces[provinceIndex]; int sourceCountryIndex = province.countryIndex; if (sourceCountryIndex == targetCountryIndex) return false; // Remove province form source country Country sourceCountry = countries[sourceCountryIndex]; if (sourceCountry.provinces != null) { List sourceProvinces = new List(sourceCountry.provinces); if (sourceProvinces.Contains(province)) { sourceProvinces.Remove(province); sourceCountry.provinces = sourceProvinces.ToArray(); } } // Adds province to target country Country targetCountry = countries[targetCountryIndex]; if (targetCountry.provinces != null) { List destProvinces = new List(targetCountry.provinces); destProvinces.Add(province); destProvinces.Sort(ProvinceSizeComparer); targetCountry.provinces = destProvinces.ToArray(); } else { List destProvinces = new List(); destProvinces.Add(province); targetCountry.provinces = destProvinces.ToArray(); } Clipper clipper = new Clipper(); clipper.AddPaths(sourceCountry.regions, PolyType.ptSubject); clipper.AddPath(provinceRegion, PolyType.ptClip); clipper.Execute(ClipType.ctDifference, sourceCountry); // Remove invalid regions from source country if (sourceCountry.regions != null) { for (int k = 0; k < sourceCountry.regions.Count; k++) { Region otherSourceRegion = sourceCountry.regions[k]; if (!otherSourceRegion.sanitized && otherSourceRegion.latlon.Length < 5) { sourceCountry.regions.RemoveAt(k); k--; } } } clipper = new Clipper(); clipper.AddPaths(targetCountry.regions, PolyType.ptSubject); clipper.AddPath(provinceRegion, PolyType.ptClip); clipper.Execute(ClipType.ctUnion, targetCountry); // Fusion any adjacent regions that results from merge operation MergeAdjacentRegions(targetCountry); // Remove invalid regions from source country if (sourceCountry.regions != null) { for (int k = 0; k < sourceCountry.regions.Count; k++) { Region otherSourceRegion = sourceCountry.regions[k]; if (!otherSourceRegion.sanitized && otherSourceRegion.latlon.Length < 5) { sourceCountry.regions.RemoveAt(k); k--; } } } // Ensure all provinces have a land region at country level if (sourceCountry.provinces != null) { int crc = sourceCountry.regions.Count; for (int p = 0; p < sourceCountry.provinces.Length; p++) { Province sourceProv = sourceCountry.provinces[p]; int sprc = sourceProv.regions.Count; for (int pr = 0; pr < sprc; pr++) { Region sourceProvRegion = sourceProv.regions[pr]; bool covered = false; for (int cr = 0; cr < crc; cr++) { Region sourceCountryRegion = sourceCountry.regions[cr]; if (sourceProvRegion.Intersects(sourceCountryRegion)) { covered = true; break; } } if (!covered) { Region newCountryRegion = new Region(sourceCountry, sourceCountry.regions.Count); newCountryRegion.UpdatePointsAndRect(sourceProvRegion.latlon); sourceCountry.regions.Add(newCountryRegion); } } } } // Update cities int cityCount = cities.Count; for (int k = 0; k < cityCount; k++) { City city = cities[k]; if (city.countryIndex == sourceCountryIndex && city.province.Equals(province.name)) { city.countryIndex = targetCountryIndex; } } // Update mount points int mountPointsCount = mountPoints.Count; for (int k = 0; k < mountPointsCount; k++) { MountPoint mp = mountPoints[k]; if (mp.countryIndex == sourceCountryIndex && mp.provinceIndex == provinceIndex) { mp.countryIndex = targetCountryIndex; } } // Update source country definition if (sourceCountry.regions.Count == 0) { internal_CountryDelete(sourceCountryIndex, false); if (targetCountryIndex > sourceCountryIndex) targetCountryIndex--; } else { RegionSanitize(sourceCountry.regions, false); RefreshCountryDefinition(sourceCountryIndex, null); } // Update target country definition RegionSanitize(targetCountry.regions, false); RefreshCountryDefinition(targetCountryIndex, null); province.countryIndex = targetCountryIndex; if (redraw) { Redraw(); } return true; } /// /// Makes countryIndex absorb an hexagonal portion of the map. If that portion belong to another country, it will be substracted from that country as well. /// This function is quite slow with high definition frontiers. /// /// Country index of the conquering country. /// Index of the cell to add to the country. public bool CountryTransferCell(int countryIndex, int cellIndex, bool redraw = true) { if (countryIndex < 0 || cellIndex < 0 || cells == null || cellIndex >= cells.Length) return false; // Start process Country country = countries[countryIndex]; Cell cell = cells[cellIndex]; // Create a region for the cell Region sourceRegion = new Region(country, country.regions.Count); // Convert cell points to latlon coordinates sourceRegion.UpdatePointsAndRect(cell.latlon); // Transfer cities List citiesInCell = GetCities(sourceRegion); int cityCount = citiesInCell.Count; for (int k = 0; k < cityCount; k++) { City city = citiesInCell[k]; if (city.countryIndex != countryIndex) { city.countryIndex = countryIndex; city.province = ""; // clear province since it does not apply anymore } } // Transfer mount points List mountPointsInCell = new List(); int mountPointCount = GetMountPoints(sourceRegion, mountPointsInCell); for (int k = 0; k < mountPointCount; k++) { MountPoint mp = mountPointsInCell[k]; if (mp.countryIndex != countryIndex) { mp.countryIndex = countryIndex; mp.provinceIndex = -1; // same as cities - province cleared in case it's informed since it does not apply anymore } } // Add region to target country's polygon - only if the country is touching or crossing target country frontier Region targetRegion = country.mainRegion; RegionMagnet(sourceRegion, targetRegion); Clipper clipper = new Clipper(); clipper.AddPath(sourceRegion, PolyType.ptClip); clipper.AddPaths(country.regions, PolyType.ptSubject); clipper.Execute(ClipType.ctUnion, country); // Finish operation with the country RegionSanitize(country.regions, true); RefreshCountryGeometry(country); // Substract cell region from any other country for (int k = 0; k < _countries.Length; k++) { Country otherCountry = _countries[k]; if (otherCountry == country || !otherCountry.Overlaps(sourceRegion)) continue; clipper = new Clipper(); clipper.AddPath(sourceRegion, PolyType.ptClip); clipper.AddPaths(otherCountry.regions, PolyType.ptSubject); clipper.Execute(ClipType.ctDifference, otherCountry); RegionSanitize(otherCountry.regions, true); if (otherCountry.regions.Count == 0) { int otherCountryIndex = GetCountryIndex(otherCountry); CountryDelete(otherCountryIndex, true, false); if (k >= otherCountryIndex) k--; } else { RefreshCountryGeometry(otherCountry); } } OptimizeFrontiers(); if (redraw) Redraw(); return true; } /// /// Sets the provinces for an existing country. The country regions will be updated to reflect the regions of the new provinces. /// /// If true, adjacent regions will be merged (default). /// public bool CountrySetProvinces(int countryIndex, List provinces, bool mergeRegions = true, bool updateCities = true, bool updateMountPoints = true) { if (countryIndex < 0 || countryIndex >= countries.Length || provinces == null) return false; Country country = countries[countryIndex]; country.regions.Clear(); List newProvinces = new List(); foreach (Province province in provinces) { if (province == null) continue; if (province.regions == null) { ReadProvincePackedString(province); if (province.regions == null) continue; } int r = 0; foreach (Region region in province.regions) { if (region == null) continue; Region newRegion = region.Clone(); newRegion.entity = country; newRegion.regionIndex = r++; country.regions.Add(newRegion); } newProvinces.Add(province); } country.provinces = newProvinces.ToArray(); if (mergeRegions) { MergeAdjacentRegions(country); } int provincesCount = country.provinces.Length; if (updateCities) { int citiesCount = cities.Count; for (int p = 0; p < provincesCount; p++) { Province province = country.provinces[p]; for (int k = 0; k < citiesCount; k++) { City city = _cities[k]; if (city.countryIndex == province.countryIndex && city.province.Equals(province.name)) { city.countryIndex = countryIndex; } } } } if (updateMountPoints) { int mpCount = mountPoints.Count; for (int p = 0; p < provincesCount; p++) { Province province = country.provinces[p]; int provinceIndex = GetProvinceIndex(province); for (int k = 0; k < mpCount; k++) { MountPoint mp = mountPoints[k]; if (mp.countryIndex == province.countryIndex && mp.provinceIndex == provinceIndex) { mp.countryIndex = countryIndex; } } } } // Update provinces country index for (int p = 0; p < provincesCount; p++) { Province province = country.provinces[p]; province.countryIndex = countryIndex; } lastCountryLookupCount = -1; RefreshCountryGeometry(country); return true; } /// /// Add provinces to an existing country. The country regions will be updated to reflect the regions of the new provinces. /// Please note that if the provinces currently belong to another country, that other country won't be updated. Call CountrySetProvinces or CountryRemoveProvinces on the other country as well. /// /// If true, adjacent regions will be merged (default). /// public bool CountryAddProvinces(int countryIndex, List provinces, bool mergeRegions = true, bool updateCities = true, bool updateMountPoints = true) { if (countryIndex < 0 || countryIndex >= countries.Length || provinces == null) return false; Country country = countries[countryIndex]; List newProvinces = new List(); if (country.provinces != null) { newProvinces.AddRange(country.provinces); } // ensure there's at least one new province foreach (Province province in provinces) { if (province == null) continue; bool alreadyExists = false; foreach (Province existingProvince in country.provinces) { if (existingProvince == province) { alreadyExists = true; break; } } if (alreadyExists) continue; if (province.regions == null) { ReadProvincePackedString(province); if (province.regions == null) continue; } int r = 0; foreach (Region region in province.regions) { if (region == null) continue; Region newRegion = region.Clone(); newRegion.entity = country; newRegion.regionIndex = r++; country.regions.Add(newRegion); } newProvinces.Add(province); if (updateCities) { int citiesCount = cities.Count; for (int k = 0; k < citiesCount; k++) { City city = _cities[k]; if (city.countryIndex == province.countryIndex && city.province.Equals(province.name)) { city.countryIndex = countryIndex; } } } if (updateMountPoints) { int mpCount = mountPoints.Count; int provinceIndex = GetProvinceIndex(province); for (int k = 0; k < mpCount; k++) { MountPoint mp = mountPoints[k]; if (mp.countryIndex == province.countryIndex && mp.provinceIndex == provinceIndex) { mp.countryIndex = countryIndex; } } } } country.provinces = newProvinces.ToArray(); if (mergeRegions) { MergeAdjacentRegions(country); } // Update provinces country index for (int p = 0; p < country.provinces.Length; p++) { Province province = country.provinces[p]; province.countryIndex = countryIndex; } lastCountryLookupCount = -1; RefreshCountryGeometry(country); return true; } /// /// Remove provinces from an existing country. The country regions will be updated to reflect the regions of the new provinces. /// /// If true, adjacent regions will be merged (default). /// public bool CountryRemoveProvinces(int countryIndex, List provinces, bool mergeRegions = true) { if (countryIndex < 0 || countryIndex >= countries.Length || provinces == null) return false; Country country = countries[countryIndex]; List newProvinces = new List(); foreach (Province province in country.provinces) { if (province == null || provinces.Contains(province)) continue; newProvinces.Add(province); } return CountrySetProvinces(countryIndex, newProvinces, mergeRegions, false); } #endregion } }