using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using WPM.Poly2Tri; namespace WPM { public partial class Region { Vector2[] _latlon; /// /// Region border in lat/lon coordinates. /// public Vector2[] latlon { get { return _latlon; } set { _latlon = value; UpdateSpherePointsFromLatLon(); } } Vector3[] _spherePoints; /// /// Region border in spherical coordinates. These values are computed upon calling Region.ComputeSphereCoordiantes() /// public Vector3[] spherePoints { get { return _spherePoints; } set { _spherePoints = value; UpdateLatLonFromSpherePoints(); } } Vector2 _latlonCenter; /// /// Center of this region /// public Vector2 latlonCenter { get { return _latlonCenter; } set { _latlonCenter = value; _sphereCenter = Conversion.GetSpherePointFromLatLon(_latlonCenter); } } Vector3 _sphereCenter; public Vector3 sphereCenter { get { return _sphereCenter; } } Rect _latlonRect2D; /// /// 2D rect enclosing all points /// public Rect latlonRect2D { get { return _latlonRect2D; } set { _latlonRect2D = value; _rect2Dbillboard = Conversion.GetBillboardRectFromLatLonRect(_latlonRect2D); } } Rect _rect2Dbillboard; public Rect rect2Dbillboard { get { return _rect2Dbillboard; } } /// /// Equals to rect2D.width * rect2D.height. Precomputed for performance purposes in comparison functions /// public float rect2DArea; public Material customMaterial; public Vector2 customTextureScale, customTextureOffset; public float customTextureRotation; public bool customOutline; public Color customOutlineColor; public List neighbours { get; set; } public IAdminEntity entity { get; set; } // country or province index public int regionIndex { get; set; } /// /// Some operations require to sanitize the region point list. This flag determines if the point list changed and should pass a sanitize call. /// public bool sanitized; /// /// Reference to the surface gameobject when colored/textured /// public GameObject surfaceGameObject; public Region(IAdminEntity entity, int regionIndex) { this.entity = entity; this.regionIndex = regionIndex; this.sanitized = true; neighbours = new List(); } public Region Clone() { Region c = new Region(entity, regionIndex); c._latlonCenter = this._latlonCenter; c._latlonRect2D = this._latlonRect2D; c._rect2Dbillboard = this._rect2Dbillboard; c._sphereCenter = this._sphereCenter; c.customMaterial = this.customMaterial; c.customTextureScale = this.customTextureScale; c.customTextureOffset = this.customTextureOffset; c.customTextureRotation = this.customTextureRotation; c.customOutline = this.customOutline; c.customOutlineColor = customOutlineColor; c._spherePoints = new Vector3[_spherePoints.Length]; Array.Copy(_spherePoints, c._spherePoints, _spherePoints.Length); c._latlon = new Vector2[_latlon.Length]; Array.Copy(_latlon, c._latlon, _latlon.Length); return c; } public void Clear() { _latlon = new Vector2[0]; _spherePoints = new Vector3[0]; _latlonRect2D = new Rect(0, 0, 0, 0); _rect2Dbillboard = _latlonRect2D; } public void UpdateSpherePointsFromLatLon() { if (latlon == null) return; int pointCount = latlon.Length; if (spherePoints == null || spherePoints.Length != pointCount) { _spherePoints = new Vector3[pointCount]; } for (int k = 0; k < pointCount; k++) { _spherePoints[k] = Conversion.GetSpherePointFromLatLon(_latlon[k]); } } public void UpdateLatLonFromSpherePoints() { if (spherePoints == null) return; int pointCount = spherePoints.Length; if (_latlon == null || _latlon.Length != pointCount) { _latlon = new Vector2[pointCount]; } for (int k = 0; k < pointCount; k++) { _latlon[k] = Conversion.GetLatLonFromUnitSpherePoint(_spherePoints[k]); } } public bool Contains(float lat, float lon) { return Contains(new Vector2(lat, lon)); } /// /// p = (lat/lon) /// public bool Contains(Vector2 p) { if (!_latlonRect2D.Contains(p)) { // check world-edge-crossing country Vector2 wrappedPos = p; wrappedPos.y += 360; if (!_latlonRect2D.Contains(wrappedPos)) { return false; } } int numPoints = _latlon.Length; int j = numPoints - 1; bool inside = false; for (int i = 0; i < numPoints; j = i++) { if (((_latlon[i].y <= p.y && p.y < _latlon[j].y) || (_latlon[j].y <= p.y && p.y < _latlon[i].y)) && (p.x < (_latlon[j].x - _latlon[i].x) * (p.y - _latlon[i].y) / (_latlon[j].y - _latlon[i].y) + _latlon[i].x)) inside = !inside; } return inside; } public bool Contains(Region other) { if (!_latlonRect2D.Overlaps(other._latlonRect2D)) return false; int numPoints = other._latlon.Length; for (int i = 0; i < numPoints; i++) { if (!Contains(other._latlon[i])) return false; } return true; } public bool Intersects(Region other) { Rect otherRect = other._latlonRect2D; if (otherRect.xMin > _latlonRect2D.xMax) return false; if (otherRect.xMax < _latlonRect2D.xMin) return false; if (otherRect.yMin > _latlonRect2D.yMax) return false; if (otherRect.yMax < _latlonRect2D.yMin) return false; int pointCount = _latlon.Length; int otherPointCount = other._latlon.Length; for (int k = 0; k < otherPointCount; k++) { int j = pointCount - 1; bool inside = false; Vector2 p = other._latlon[k]; for (int i = 0; i < pointCount; j = i++) { if (((_latlon[i].y <= p.y && p.y < _latlon[j].y) || (_latlon[j].y <= p.y && p.y < _latlon[i].y)) && (p.x < (_latlon[j].x - _latlon[i].x) * (p.y - _latlon[i].y) / (_latlon[j].y - _latlon[i].y) + _latlon[i].x)) inside = !inside; } if (inside) return true; } for (int k = 0; k < pointCount; k++) { int j = otherPointCount - 1; bool inside = false; Vector2 p = _latlon[k]; for (int i = 0; i < otherPointCount; j = i++) { if (((other._latlon[i].y <= p.y && p.y < other._latlon[j].y) || (other._latlon[j].y <= p.y && p.y < other._latlon[i].y)) && (p.x < (other._latlon[j].x - other._latlon[i].x) * (p.y - other._latlon[i].y) / (other._latlon[j].y - other._latlon[i].y) + other._latlon[i].x)) inside = !inside; } if (inside) return true; } return false; } /// /// Updates the region rect2D. Needed if points is updated manually. /// public void UpdatePointsAndRect(List newPoints) { sanitized = false; latlon = newPoints.ToArray(); UpdateRect(); UpdateSpherePointsFromLatLon(); } /// /// Updates the region rect2D. Needed if points is updated manually. /// public void UpdatePointsAndRect(Vector2[] newPoints) { sanitized = false; latlon = newPoints; UpdateRect(); UpdateSpherePointsFromLatLon(); } /// /// Updates the region rect2D. Needed if points is updated manually. /// public void UpdateRect() { Vector2 min = new Vector2(1000f, 1000f); Vector2 max = -min; int pointCount = _latlon.Length; for (int k = 0; k < pointCount; k++) { float x = _latlon[k].x; float y = _latlon[k].y; if (x < min.x) min.x = x; if (x > max.x) max.x = x; if (y < min.y) min.y = y; if (y > max.y) max.y = y; } latlonRect2D = new Rect(min.x, min.y, max.x - min.x, max.y - min.y); rect2DArea = latlonRect2D.width * latlonRect2D.height; latlonCenter = (min + max) * 0.5f; } /// /// If this region crossed the antimeridian, split it in two and add the second half to the regions list of the entity /// public void CheckWorldEdgesAndSplit() { // if the separation between two adjacent points is greater than 180, split the region in two if (_latlon == null || _latlon.Length < 6) return; Vector2 latlon0 = _latlon[0]; bool split = false; for (int k = 1; k < _latlon.Length; k++) { Vector2 latlon1 = _latlon[k]; float diff = latlon1.y - latlon0.y; if (diff > 270 || diff < -270) { split = true; break; } latlon0 = latlon1; } if (split) { List newRegionPoints = new List(); List firstRegionPoints = newRegionPoints; Vector2 prev = Misc.Vector2zero; int regionsNewStart = entity.regions.Count; for (int k = 0; k < _latlon.Length; k++) { if (prev.y * _latlon[k].y < 0) { // crosses edge float absPrevY = Mathf.Abs(prev.y); float edgeDx = 179.999f - absPrevY; float dx = (180f + _latlon[k].y) + (180f - absPrevY); float dy = _latlon[k].x - prev.x; float lat = prev.x + edgeDx * dy / dx; float otherEdge = prev.y > 0 ? 179.999f : -179.999f; float thisEdge = otherEdge * -1f; newRegionPoints.Add(new Vector2(lat, otherEdge)); if (newRegionPoints != firstRegionPoints) { // before adding a new region, check if any existing region contains last point. If so, add the points to that existing region bool isNewRegion = true; int regionsCount = entity.regions.Count; for (int r = regionsNewStart; r < regionsCount; r++) { if (entity.regions[r].Contains(prev)) { newRegionPoints.AddRange(entity.regions[r].latlon); entity.regions[r].UpdatePointsAndRect(newRegionPoints); isNewRegion = false; break; } } // check if this region surrounds another region by checking it that other region center is contained, in that case add to that region instead Region newRegion = new Region(entity, entity.regions.Count); newRegion.UpdatePointsAndRect(newRegionPoints); if (isNewRegion) { for (int r = regionsNewStart; r < regionsCount; r++) { if (newRegion.Contains(entity.regions[r]._latlonCenter)) { newRegionPoints.AddRange(entity.regions[r].latlon); entity.regions[r].UpdatePointsAndRect(newRegionPoints); isNewRegion = false; break; } } } if (isNewRegion) { // add a new region entity.regions.Add(newRegion); } } newRegionPoints = new List(); newRegionPoints.Add(new Vector2(lat, thisEdge)); } newRegionPoints.Add(_latlon[k]); prev = _latlon[k]; } // update this region which will be the West region firstRegionPoints.AddRange(newRegionPoints); UpdatePointsAndRect(firstRegionPoints); } } /// /// Takes into account antimeridian clamp /// public void UpdateLatlon(int index, Vector2 newLatlon) { Vector2 prevLatlon = _latlon[index]; if (prevLatlon.y < -150 && newLatlon.y > 150) { newLatlon.y = -179.99f; _spherePoints[index] = Conversion.GetSpherePointFromLatLon(newLatlon); } else if (prevLatlon.y > 150 && newLatlon.y < -150) { newLatlon.y = 179.99f; _spherePoints[index] = Conversion.GetSpherePointFromLatLon(newLatlon); } _latlon[index] = newLatlon; } } }