// 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;
namespace WPM {
public delegate void CityEvent(int cityIndex);
/* Public WPM Class */
public partial class WorldMapGlobe : MonoBehaviour {
public event CityEvent OnCityEnter;
public event CityEvent OnCityExit;
public event CityEvent OnCityPointerDown;
public event CityEvent OnCityPointerUp;
public event CityEvent OnCityClick;
public const int CITY_CLASS_FILTER_REGION_CAPITAL_CITY = 2;
public const int CITY_CLASS_FILTER_COUNTRY_CAPITAL_CITY = 4;
///
/// Complete list of cities with their names and country names.
///
public List cities {
get {
if (_cities == null)
ReadCitiesPackedString();
return _cities;
}
set {
_cities = value;
lastCityLookupCount = -1;
}
}
City _cityHighlighted;
///
/// Returns City under mouse position or null if none.
///
public City cityHighlighted { get { return _cityHighlighted; } }
int _cityHighlightedIndex = -1;
///
/// Returns City index mouse position or null if none.
///
public int cityHighlightedIndex { get { return _cityHighlightedIndex; } }
int _cityLastClicked = -1;
///
/// Returns the last clicked city index.
///
public int cityLastClicked { get { return _cityLastClicked; } }
[SerializeField]
bool
_showCities = true;
///
/// Toggle cities visibility.
///
public bool showCities {
get {
return _showCities;
}
set {
if (_showCities != value) {
_showCities = value;
isDirty = true;
if (citiesLayer != null) {
citiesLayer.SetActive(_showCities);
} else if (_showCities) {
DrawCities();
}
}
}
}
[NonSerialized]
int
_numCitiesDrawn = 0;
///
/// Gets the number cities drawn.
///
public int numCitiesDrawn { get { return _numCitiesDrawn; } }
[SerializeField]
Color
_citiesColor = Color.white;
///
/// Global color for cities.
///
public Color citiesColor {
get {
if (citiesNormalMat != null) {
return citiesNormalMat.color;
} else {
return _citiesColor;
}
}
set {
if (value != _citiesColor) {
_citiesColor = value;
isDirty = true;
if (citiesNormalMat != null && _citiesColor != citiesNormalMat.color) {
citiesNormalMat.color = _citiesColor;
}
}
}
}
[SerializeField]
Color
_citiesRegionCapitalColor = Color.cyan;
///
/// Global color for region capitals.
///
public Color citiesRegionCapitalColor {
get {
if (citiesRegionCapitalMat != null) {
return citiesRegionCapitalMat.color;
} else {
return _citiesRegionCapitalColor;
}
}
set {
if (value != _citiesRegionCapitalColor) {
_citiesRegionCapitalColor = value;
isDirty = true;
if (citiesRegionCapitalMat != null && _citiesRegionCapitalColor != citiesRegionCapitalMat.color) {
citiesRegionCapitalMat.color = _citiesRegionCapitalColor;
}
}
}
}
[SerializeField]
Color
_citiesCountryCapitalColor = Color.yellow;
///
/// Global color for country capitals.
///
public Color citiesCountryCapitalColor {
get {
if (citiesCountryCapitalMat != null) {
return citiesCountryCapitalMat.color;
} else {
return _citiesCountryCapitalColor;
}
}
set {
if (value != _citiesCountryCapitalColor) {
_citiesCountryCapitalColor = value;
isDirty = true;
if (citiesCountryCapitalMat != null && _citiesCountryCapitalColor != citiesCountryCapitalMat.color) {
citiesCountryCapitalMat.color = _citiesCountryCapitalColor;
}
}
}
}
[SerializeField]
float _cityIconSize = 0.2f;
///
/// The size of the cities icon (dot).
///
public float cityIconSize {
get {
return _cityIconSize;
}
set {
if (value != _cityIconSize) {
_cityIconSize = value;
isDirty = true;
ScaleCities();
ScaleMountPoints();
}
}
}
[Range(0, 17000)]
[SerializeField]
int
_minPopulation = 1500;
public int minPopulation {
get {
return _minPopulation;
}
set {
if (value != _minPopulation) {
_minPopulation = value;
isDirty = true;
DrawCities();
}
}
}
[SerializeField]
int _cityClassAlwaysShow;
///
/// Flags for specifying the class of cities to always show irrespective of other filters like minimum population. Can assign a combination of bit flags defined by CITY_CLASS_FILTER* constants.
///
public int cityClassAlwaysShow {
get { return _cityClassAlwaysShow; }
set {
if (_cityClassAlwaysShow != value) {
_cityClassAlwaysShow = value;
isDirty = true;
DrawCities();
}
}
}
[SerializeField]
bool
_combineCityMeshes = true;
///
/// Optimize cities meshes.
///
public bool combineCityMeshes {
get {
return _combineCityMeshes;
}
set {
if (_combineCityMeshes != value) {
_combineCityMeshes = value;
isDirty = true;
DrawCities();
}
}
}
string _cityAttributeFile = CITY_ATTRIB_DEFAULT_FILENAME;
public string cityAttributeFile {
get { return _cityAttributeFile; }
set {
if (value != _cityAttributeFile) {
_cityAttributeFile = value;
if (_cityAttributeFile == null)
_cityAttributeFile = CITY_ATTRIB_DEFAULT_FILENAME;
isDirty = true;
ReloadCitiesAttributes();
}
}
}
#region Public API area
///
/// Starts navigation to target city. Returns false if not found.
///
public CallbackHandler FlyToCity(string cityName) {
int cityIndex = GetCityIndex(cityName);
return FlyToCity(cityIndex);
}
///
/// Starts navigation to target city. Returns false if not found.
///
public CallbackHandler FlyToCity(string countryName, string cityName) {
int cityIndex = GetCityIndexInCountry(countryName, cityName);
return FlyToCity(cityIndex);
}
///
/// Starts navigation to target city by index in the cities collection. Returns false if not found.
///
public CallbackHandler FlyToCity(int cityIndex) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return CallbackHandler.Null;
return FlyToCity(cities[cityIndex]);
}
///
/// Starts navigation to target city by index in the cities collection and duration. Returns false if not found.
///
public CallbackHandler FlyToCity(int cityIndex, float duration) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return CallbackHandler.Null;
return FlyToLocation(cities[cityIndex].localPosition, duration, 0, _navigationBounceIntensity);
}
///
/// Starts navigation to target city. Returns false if not found.
///
public CallbackHandler FlyToCity(City city) {
return FlyToCity(city, _navigationTime);
}
///
/// Starts navigation to target city with duration (seconds). Returns false if not found.
///
public CallbackHandler FlyToCity(City city, float duration) {
return FlyToLocation(city.localPosition, duration, 0, _navigationBounceIntensity);
}
///
/// Starts navigating to target city by index in the cities 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 FlyToCity(int cityIndex, float duration, float zoomLevel) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return CallbackHandler.Null;
return FlyToLocation(_cities[cityIndex].localPosition, duration, zoomLevel, _navigationBounceIntensity);
}
///
/// Starts navigating to target city by index in the cities collection with specified duration, ignoring NavigationTime property.
///
/// City index.
/// Set duration to zero to go instantly.
/// Set zoomLevel to a value from 0 to 1 for the destination zoom level.
/// Set bounceIntensity to a value from 0 to 1 for a bouncing effect between current position and destination.
public CallbackHandler FlyToCity(int cityIndex, float duration, float zoomLevel, float bounceIntensity) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return CallbackHandler.Null;
return FlyToLocation(_cities[cityIndex].localPosition, duration, zoomLevel, bounceIntensity);
}
///
/// Returns an array with the city names.
///
public string[] GetCityNames() {
return GetCityNames(true);
}
///
/// Returns an array with the city names.
///
public string[] GetCityNames(bool includeCityIndex) {
List c = new List(cities.Count);
for (int k = 0; k < cities.Count; k++) {
if (includeCityIndex) {
c.Add(cities[k].name + " (" + k + ")");
} else {
c.Add(cities[k].name);
}
}
c.Sort();
return c.ToArray();
}
///
/// Returns an array with the city names.
///
public string[] GetCityNames(int countryIndex, bool includeCityIndex) {
List c = new List(cities.Count);
for (int k = 0; k < cities.Count; k++) {
if (cities[k].countryIndex == countryIndex) {
if (includeCityIndex) {
c.Add(cities[k].name + " (" + k + ")");
} else {
c.Add(cities[k].name);
}
}
}
c.Sort();
return c.ToArray();
}
///
/// Given a country name and a city name, returns the City object
///
/// The city.
/// Country name.
/// City name.
public City GetCity(string countryName, string cityName) {
int countryIndex = GetCountryIndex(countryName);
return GetCity(countryIndex, cityName);
}
///
/// Given a country name, province name and a city name, returns the City object
///
/// The city.
/// Country name.
/// Province name.
/// City name.
public City GetCity(string countryName, string provinceName, string cityName) {
int cityIndex = GetCityIndex(countryName, provinceName, cityName);
if (cityIndex < 0)
return null;
return cities[cityIndex];
}
///
/// Gets a city object by its index
///
public City GetCity(int cityIndex) {
if (cityIndex < 0 || cityIndex >= cities.Count) {
return null;
}
return _cities[cityIndex];
}
///
/// Given a country index and a city name returns the City object
///
/// The city.
/// Country index.
/// City name.
public City GetCity(int countryIndex, string cityName) {
int cityIndex = GetCityIndexInCountry(countryIndex, cityName);
if (cityIndex < 0 || cityIndex >= cities.Count) {
return null;
}
return _cities[cityIndex];
}
///
/// Returns the index of a random visible city.
///
public int GetCityIndex() {
if (cities == null)
return -1;
int z = UnityEngine.Random.Range(0, cities.Count);
int cityCount = cities.Count;
for (int k = z; k < cityCount; k++) {
if (cities[k].isShown)
return k;
}
for (int k = 0; k < z; k++) {
if (cities[k].isShown)
return k;
}
return -1;
}
///
/// Returns the index of the city by its name in the cities collection.
///
public int GetCityIndex(string cityName) {
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
if (cityName.Equals(cities[k].name)) {
return k;
}
}
return -1;
}
///
/// Returns the index of the city by its name in the cities collection of a given country and province.
///
public int GetCityIndex(string countryName, string provinceName, string cityName) {
int countryIndex = GetCountryIndex(countryName);
return GetCityIndex(countryIndex, provinceName, cityName);
}
///
/// Returns the index of the city by its name in the cities collection of a given country and province.
///
public int GetCityIndex(int countryIndex, string provinceName, string cityName) {
if (countryIndex < 0 || countryIndex >= countries.Length)
return -1;
int provinceIndex = GetProvinceIndex(countryIndex, provinceName);
if (provinceIndex < 0)
return -1;
return GetCityIndexInProvince(provinceIndex, cityName);
}
///
/// Returns the index of the city by its name in the cities collection of a given province.
///
public int GetCityIndexInProvince(int provinceIndex, string cityName) {
if (provinceIndex < 0 || provinceIndex >= provinces.Length)
return -1;
string provinceName = _provinces[provinceIndex].name;
int countryIndex = _provinces[provinceIndex].countryIndex;
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
City city = _cities[k];
if (city.countryIndex == countryIndex && city.province.Equals(provinceName) && cityName.Equals(cities[k].name)) {
return k;
}
}
return -1;
}
///
/// Returns the index of the city by its name in the cities collection of a given province.
///
public int GetCityIndexInProvince(string provinceName, string cityName) {
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
City city = _cities[k];
if (city.province.Equals(provinceName) && cityName.Equals(cities[k].name)) {
return k;
}
}
return -1;
}
///
/// Returns the index of the city by its name in the cities collection of a given country.
///
public int GetCityIndexInCountry(int countryIndex, string cityName) {
if (countryIndex < 0 || countryIndex >= countries.Length)
return -1;
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
City city = _cities[k];
if (city.countryIndex == countryIndex && cityName.Equals(cities[k].name)) {
return k;
}
}
return -1;
}
///
/// Returns the index of a city in the cities collection by its reference.
///
public int GetCityIndex(City city) {
return GetCityIndex(city, true);
}
///
/// Returns the index of a city in the cities collection by its reference.
///
public int GetCityIndex(City city, bool includeNotVisible) {
if (includeNotVisible)
return cities.IndexOf(city);
int cityIndex;
if (cityLookup.TryGetValue(city, out cityIndex))
return cityIndex;
else
return -1;
}
///
/// Returns the index of a city in the global countries collection. Note that country name needs to be supplied due to repeated city names.
///
public int GetCityIndexInCountry(string countryName, string cityName) {
int countryIndex = GetCountryIndex(countryName);
if (countryIndex < 0 || countryIndex >= countries.Length)
return -1;
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
City city = _cities[k];
if (city.countryIndex == countryIndex && cityName.Equals(cities[k].name)) {
return k;
}
}
return -1;
}
///
/// Returns the city index by screen position.
///
public bool GetCityIndex(Ray ray, out int cityIndex, int countryIndex = -1) {
Vector3 hitPos;
if (GetGlobeIntersection(ray, out hitPos)) {
Vector3 localHit = transform.InverseTransformPoint(hitPos);
int c = GetCityNearPoint(localHit, countryIndex);
if (c >= 0) {
cityIndex = c;
return true;
}
}
cityIndex = -1;
return false;
}
///
/// Returns the index of the nearest city to a location (lat/lon).
///
public int GetCityIndex(float lat, float lon) {
Vector3 spherePosition = Conversion.GetSpherePointFromLatLon(lat, lon);
return GetCityIndex(spherePosition);
}
///
/// Returns the nearest city to a point specified in sphere coordinates.
///
/// The city near point.
/// Local point in sphere coordinates.
/// Optional city index which will be excluded. Useful for getting the nearest city to a given one.
public int GetCityIndex(Vector2 latlon, int cityIndexToExclude = -1) {
if (visibleCities == null)
return -1;
int nearest = -1;
float minDist = float.MaxValue;
int cityCount = cities.Count;
for (int c = 0; c < cityCount; c++) {
if (c == cityIndexToExclude)
continue;
City city = cities[c];
if (!city.isShown)
continue;
Vector2 cityLoc = city.latlon;
float dist = FastVector.SqrDistance(ref cityLoc, ref latlon);
if (dist < minDist) {
minDist = dist;
nearest = c;
}
}
return nearest;
}
///
/// Returns the nearest city to a point specified in sphere coordinates.
///
/// The city near point.
/// Local point in sphere coordinates.
/// Optional list with city index which will be excluded. Useful for getting the nearest city to a given one.
public int GetCityIndex(Vector2 latlon, List excludeCitiesList) {
if (visibleCities == null)
return -1;
int nearest = -1;
float minDist = float.MaxValue;
int cityCount = cities.Count;
for (int c = 0; c < cityCount; c++) {
City city = cities[c];
if (!city.isShown)
continue;
float dist = FastVector.SqrDistanceByValue(city.latlon, latlon);
if (dist < minDist) {
if (!excludeCitiesList.Contains(c)) {
minDist = dist;
nearest = c;
}
}
}
return nearest;
}
///
/// Gets the name of the city country.
///
public string GetCityCountryName(int cityIndex) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return "";
int countryIndex = _cities[cityIndex].countryIndex;
Country country = GetCountry(countryIndex);
if (country != null)
return country.name;
else
return "";
}
///
/// Convenient method that returns the name of the city plus the province and country names
///
/// The city full name.
/// City index.
public string GetCityFullName(City city) {
if (city == null)
return null;
sb.Length = 0;
sb.Append(city.name);
sb.Append(" (");
if (!string.IsNullOrEmpty(city.province) && !city.province.Equals(city.name)) {
sb.Append(city.province);
sb.Append(", ");
}
sb.Append(countries[city.countryIndex].name);
sb.Append(")");
return sb.ToString();
}
///
/// Convenient method that returns the name of the city plus the province and country names
///
/// The city full name.
/// City index.
public string GetCityFullName(int cityIndex) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return null;
return GetCityFullName(cities[cityIndex]);
}
///
/// Gets the name of the city province.
///
public string GetCityProvinceName(int cityIndex) {
if (cityIndex < 0 || cityIndex >= cities.Count)
return "";
return _cities[cityIndex].province;
}
///
/// Gets a random city from a given country
///
/// The city.
/// Country object.
public City GetCityRandom(Country country, bool onlyVisible = false) {
int cityCount = cities.Count;
int countryIndex = GetCountryIndex(country);
List cc = new List(100);
for (int k = 0; k < cityCount; k++) {
if ((!onlyVisible || _cities[k].isShown) && _cities[k].countryIndex == countryIndex) {
cc.Add(_cities[k]);
}
}
int count = cc.Count;
if (count == 0)
return null;
return cc[UnityEngine.Random.Range(0, count)];
}
///
/// Gets a random city from a given province
///
/// The city.
/// Province object.
public City GetCityRandom(Province province, bool onlyVisible = false) {
int cityCount = cities.Count;
int countryIndex = province.countryIndex;
List cc = new List(100);
for (int k = 0; k < cityCount; k++) {
if ((!onlyVisible || _cities[k].isShown) && _cities[k].countryIndex == countryIndex && _cities[k].province.Equals(province.name)) {
cc.Add(_cities[k]);
}
}
int count = cc.Count;
if (count == 0)
return null;
return cc[UnityEngine.Random.Range(0, count)];
}
///
/// Gets a random visible city
///
/// The city.
public City GetCityRandom(bool onlyVisible = true) {
if (onlyVisible) {
int count = visibleCities.Length;
if (count == 0)
return null;
return visibleCities[UnityEngine.Random.Range(0, count)];
} else {
int count = cities.Count;
if (count == 0)
return null;
return _cities[UnityEngine.Random.Range(0, count)];
}
}
///
/// Gets a random city index from a given country
///
/// The city random.
/// Country object.
public int GetCityIndexRandom(Country country, bool onlyVisible = true) {
City city = GetCityRandom(country, onlyVisible);
if (city == null)
return -1;
return GetCityIndex(city);
}
///
/// Gets a random city index from a given province
///
/// The city random.
/// Province object.
public int GetCityIndexRandom(Province province, bool onlyVisible = true) {
City city = GetCityRandom(province, onlyVisible);
if (city == null)
return -1;
return GetCityIndex(city);
}
///
/// Gets the name of the country of the city.
///
public string GetCityCountryName(City city) {
Country country = GetCountry(city.countryIndex);
if (country != null)
return country.name;
else
return "";
}
///
/// Gets the name of the province of the city.
///
public string GetCityProvinceName(City city) {
return city.province;
}
///
/// Returns a list of cities whose attributes matches predicate
///
public void GetCities(AttribPredicate predicate, List results) {
if (results == null) return;
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
City city = _cities[k];
if (city.hasAttributes && predicate(city.attrib))
results.Add(city);
}
}
///
/// Gets XML attributes of all cities in jSON format.
///
public string GetCitiesAttributes(bool prettyPrint = true) {
if (cities == null) return null;
return GetCitiesAttributes(new List(cities), prettyPrint);
}
///
/// Gets XML attributes of provided cities in jSON format.
///
public string GetCitiesAttributes(List cities, bool prettyPrint = true) {
JSONObject composed = new JSONObject();
int cityCount = cities.Count;
for (int k = 0; k < cityCount; k++) {
City city = _cities[k];
if (city.hasAttributes && city.attrib.keys != null) {
composed.AddField(k.ToString(), city.attrib);
}
}
return composed.Print(prettyPrint);
}
///
/// Sets cities attributes from a jSON formatted string.
///
public void SetCitiesAttributes(string jSON) {
JSONObject composed = new JSONObject(jSON);
if (composed.keys == null)
return;
int keyCount = composed.keys.Count;
for (int k = 0; k < keyCount; k++) {
int cityIndex = int.Parse(composed.keys[k]);
if (cityIndex >= 0) {
cities[cityIndex].attrib = composed[k];
}
}
}
///
/// Clears any city highlighted (color changed) and resets them to default city color
///
public void HideCityHighlights() {
if (citiesLayer == null)
return;
Renderer[] rr = citiesLayer.GetComponentsInChildren(true);
for (int k = 0; k < rr.Length; k++) {
string matName = rr[k].sharedMaterial.name;
if (matName.Equals("Cities")) {
rr[k].sharedMaterial = citiesNormalMat;
} else if (matName.Equals("CitiesCapitalRegion")) {
rr[k].sharedMaterial = citiesRegionCapitalMat;
} else if (matName.Equals("CitiesCapitalCountry")) {
rr[k].sharedMaterial = citiesCountryCapitalMat;
}
}
}
///
/// Toggles the city highlight.
///
/// City index.
/// Color.
/// If set to true the color of the city will be changed. If set to false the color of the city will be reseted to default color
public void ToggleCityHighlight(int cityIndex, Color color, bool highlighted) {
if (citiesLayer == null)
return;
GameObject cityObj = cities[cityIndex].gameObject;
if (cityObj == null)
return;
Renderer rr = cityObj.GetComponent();
if (rr == null)
return;
Material mat;
if (highlighted) {
mat = Instantiate(rr.sharedMaterial);
mat.name = rr.sharedMaterial.name;
mat.color = color;
rr.sharedMaterial = mat;
} else {
switch (cities[cityIndex].cityClass) {
case CITY_CLASS.COUNTRY_CAPITAL:
mat = citiesCountryCapitalMat;
break;
case CITY_CLASS.REGION_CAPITAL:
mat = citiesRegionCapitalMat;
break;
default:
mat = citiesNormalMat;
break;
}
rr.sharedMaterial = mat;
}
}
///
/// Flashes specified city by index in the global city collection.
///
public void BlinkCity(int cityIndex, Color color1, Color color2, float duration, float blinkingSpeed) {
if (citiesLayer == null)
return;
// string cobj = GetCityHierarchyName(cityIndex);
// Transform t = transform.Find (cobj);
GameObject cityObj = cities[cityIndex].gameObject;
if (cityObj == null)
return;
CityBlinker sb = cityObj.AddComponent();
sb.blinkMaterial = cityObj.GetComponent().sharedMaterial;
sb.color1 = color1;
sb.color2 = color2;
sb.duration = duration;
sb.speed = blinkingSpeed;
}
///
/// Deletes all cities of current selected country's continent
///
public void CitiesDeleteFromContinent(string continentName) {
HideCityHighlights();
int k = -1;
while (++k < cities.Count) {
int cindex = cities[k].countryIndex;
if (cindex >= 0) {
string cityContinent = countries[cindex].continent;
if (cityContinent.Equals(continentName)) {
cities.RemoveAt(k);
k--;
}
}
}
}
///
/// Returns a list of provinces that are visible (front facing camera)
///
public List GetVisibleCities() {
List vc = new List(30);
if (cities == null)
return null;
Camera cam = mainCamera;
float viewportMinX = cam.rect.xMin;
float viewportMaxX = cam.rect.xMax;
float viewportMinY = cam.rect.yMin;
float viewportMaxY = cam.rect.yMax;
for (int k = 0; k < visibleCities.Length; k++) {
City city = visibleCities[k];
// Check if city is facing camera
Vector3 center = transform.TransformPoint(city.localPosition);
Vector3 dir = center - transform.position;
float d = Vector3.Dot(cam.transform.forward, dir);
if (d < -0.2f) {
// Check if city is inside viewport
Vector3 vpos = cam.WorldToViewportPoint(center);
if (vpos.x >= viewportMinX && vpos.x <= viewportMaxX && vpos.y >= viewportMinY && vpos.y <= viewportMaxY) {
vc.Add(city);
}
}
}
return vc;
}
///
/// Returns a list of cities that are visible and located inside the rectangle defined by two given sphere points
///
public List GetVisibleCities(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 selectedCities = new List();
int cityCount = visibleCities.Length;
for (int k = 0; k < cityCount; k++) {
City city = visibleCities[k];
Vector2 bpos = Conversion.GetBillboardPosFromSpherePoint(city.localPosition);
if (rect.Contains(bpos)) {
selectedCities.Add(city);
}
}
return selectedCities;
}
#endregion
}
}