using System; using System.Collections.Generic; using System.IO; using UnityEngine; using LPoints = System.Collections.Generic.List; using Random = UnityEngine.Random; public class MapboxBuildingManager : MonoBehaviour { [NonSerialized] public List buildings = new List(); public string cachePath = "TileCache/Buildings/"; [NonSerialized] public GameObject container; [NonSerialized] public OnlineMapsTileSetControl control; public OnlineMaps map; public OnlineMapsBuildingMaterial[] materials; public OnlineMapsRange zoomRange = new OnlineMapsRange(19, OnlineMaps.MAXZOOM_EXT); public int zoom = 17; private OnlineMapsMapboxTile activeTile; private Vector2 lastSizeInScene; private List newTiles; private Dictionary tiles; private int ptlx, ptly, pbrx, pbry; private Shader defaultShader; private Material defaultWallMaterial; private Material defaultRoofMaterial; private List vertices; private List uvs; private List wallTriangles; private List roofTriangles; private List roofVertices; private bool hasErrors; private static List roofIndices; private float perimeter; private bool needUpdatePosition; private bool needUpdateScale; private bool needUpdateTiles; private Building CreateBuilding(OnlineMapsVectorTile tile, OnlineMapsVectorTile.Feature feature, LPoints g, ulong extent) { if (g.Count < 3) return null; Dictionary properties = feature.GetProperties(); if (GetProperty(properties, "extrude").ToString() == "false") return null; if (GetProperty(properties, "underground").ToString() == "true") return null; object heightObject = GetProperty(properties, "height"); object minHeightObject = GetProperty(properties, "min_height"); if (heightObject == null) return null; float height = (float)(double)heightObject; float minHeight = minHeightObject != null? (float)(double)minHeightObject : 0; double dExtent = extent; OnlineMapsProjection projection = map.projection; double minCX = double.MaxValue, minCY = double.MaxValue, maxCX = double.MinValue, maxCY = double.MinValue; Vector4 cp = new Vector4(float.MaxValue, float.MaxValue, float.MinValue, float.MinValue); List points = new List(); for (int i = 0; i < g.Count; i++) { var gg = g[i]; double px = gg.x / dExtent; double py = gg.y / dExtent; double lng, lat; projection.TileToCoordinates(tile.x + px, tile.y + py, tile.zoom, out lng, out lat); Vector3 p = control.GetWorldPosition(lng, lat); points.Add(p); if (p.x < cp.x) cp.x = p.x; if (p.z < cp.y) cp.y = p.z; if (p.x > cp.z) cp.z = p.x; if (p.z > cp.w) cp.w = p.z; if (lng < minCX) minCX = lng; if (lat < minCY) minCY = lat; if (lng > maxCX) maxCX = lng; if (lat > maxCY) maxCY = lat; } Vector3 centerPoint = new Vector3((cp.z + cp.x) / 2, 0, (cp.y + cp.w) / 2); for (int i = 0; i < points.Count; i++) points[i] -= centerPoint; string id = feature.id.ToString(); GameObject houseGO = new GameObject(id); houseGO.SetActive(false); houseGO.transform.parent = container.transform; houseGO.layer = container.gameObject.layer; MeshRenderer meshRenderer = houseGO.AddComponent(); MeshFilter meshFilter = houseGO.AddComponent(); houseGO.transform.localPosition = centerPoint; houseGO.transform.localRotation = Quaternion.Euler(Vector3.zero); houseGO.transform.localScale = Vector3.one; GetMaterial(out Material wallMaterial, out Material roofMaterial, out Vector2 scale); Mesh mesh = new Mesh { name = id }; meshFilter.sharedMesh = mesh; meshRenderer.sharedMaterials = new[] { wallMaterial, roofMaterial }; Building building = new Building(); building.gameObject = houseGO; building.initialSizeInScene = control.sizeInScene; building.initialZoom = map.floatZoom; building.centerCoordinates = control.GetCoordsByWorldPosition(centerPoint); building.boundsCoords = new Bounds(building.centerCoordinates, new Vector3((float)(maxCX - minCX), (float)(maxCY - minCY))); int wallVerticesCount = (points.Count + 1) * 2; int roofVerticesCount = points.Count; int verticesCount = wallVerticesCount + roofVerticesCount; int countTriangles = wallVerticesCount * 3; if (vertices == null) vertices = new List(verticesCount); else vertices.Clear(); if (uvs == null) uvs = new List(verticesCount); else uvs.Clear(); if (wallTriangles == null) wallTriangles = new List(countTriangles); else wallTriangles.Clear(); if (roofTriangles == null) roofTriangles = new List(); else roofTriangles.Clear(); double tlx, tly, brx, bry; map.buffer.GetCorners(out tlx, out tly, out brx, out bry); height *= OnlineMapsElevationManagerBase.GetBestElevationYScale(tlx, tly, brx, bry); CreateWall(points, height, wallMaterial, scale); CreateRoof(points, height); if (hasErrors) { OnlineMapsUtils.Destroy(houseGO); return null; } mesh.vertices = vertices.ToArray(); mesh.uv = uvs.ToArray(); mesh.subMeshCount = 2; mesh.SetTriangles(wallTriangles.ToArray(), 0); mesh.SetTriangles(roofTriangles.ToArray(), 1); mesh.RecalculateBounds(); mesh.RecalculateNormals(); return building; } private void GetMaterial(out Material wallMaterial, out Material roofMaterial, out Vector2 scale) { if (defaultShader == null) defaultShader = Shader.Find("Diffuse"); OnlineMapsBuildingMaterial material = null; if (materials != null && materials.Length != 0) { material = materials[Random.Range(0, materials.Length)]; } if (material != null) { if (material.wall != null) wallMaterial = Instantiate(material.wall) as Material; else wallMaterial = new Material(defaultShader); if (material.roof != null) roofMaterial = Instantiate(material.roof) as Material; else roofMaterial = new Material(defaultShader); scale = material.scale; } else { if (defaultWallMaterial == null) defaultWallMaterial = new Material(defaultShader); if (defaultRoofMaterial == null) defaultRoofMaterial = new Material(defaultShader); wallMaterial = Instantiate(defaultWallMaterial) as Material; roofMaterial = Instantiate(defaultRoofMaterial) as Material; scale = Vector2.one; } } private void CreateBuildings(OnlineMapsMapboxTile tile) { OnlineMapsVectorTile.Layer layer = tile.GetLayer("building"); ulong extent = layer.extent; List tileBuildings = new List(); for (int i = 0; i < layer.featureCount; i++) { OnlineMapsVectorTile.Feature feature = layer.GetFeature(i); BuildingGroup buildingGroup; try { buildingGroup = new BuildingGroup { id = feature.id }; List geometry = feature.Geometry(10); for (int j = 0; j < geometry.Count; j++) { Building building = CreateBuilding(tile, feature, geometry[j], extent); if (building == null) continue; buildingGroup.Add(building); } if (buildingGroup.buildings.Count > 0) { buildings.Add(buildingGroup); tileBuildings.Add(buildingGroup); } } catch (Exception e) { Debug.LogException(e); continue; } buildingGroup.AddTile(tile); } tile["buildings"] = tileBuildings; map.Redraw(); needUpdatePosition = true; } private void CreateRoof(List baseVertices, float baseHeight) { float[] roofPoints = new float[baseVertices.Count * 2]; if (roofVertices == null) roofVertices = new List(baseVertices.Count); else roofVertices.Clear(); try { int countVertices = CreateRoofVertices(baseVertices, roofVertices, roofPoints, baseHeight); CreateRoofTriangles(countVertices, roofPoints, ref roofTriangles); if (roofTriangles.Count == 0) { hasErrors = true; return; } Vector3 side1 = roofVertices[roofTriangles[1]] - roofVertices[roofTriangles[0]]; Vector3 side2 = roofVertices[roofTriangles[2]] - roofVertices[roofTriangles[0]]; Vector3 perp = Vector3.Cross(side1, side2); bool reversed = perp.y < 0; if (reversed) roofTriangles.Reverse(); float minX = float.MaxValue; float minZ = float.MaxValue; float maxX = float.MinValue; float maxZ = float.MinValue; for (int i = 0; i < roofVertices.Count; i++) { Vector3 v = roofVertices[i]; if (v.x < minX) minX = v.x; if (v.z < minZ) minZ = v.z; if (v.x > maxX) maxX = v.x; if (v.z > maxZ) maxZ = v.z; } float offX = maxX - minX; float offZ = maxZ - minZ; for (int i = 0; i < roofVertices.Count; i++) { Vector3 v = roofVertices[i]; uvs.Add(new Vector2((v.x - minX) / offX, (v.z - minZ) / offZ)); } int triangleOffset = vertices.Count; for (int i = 0; i < roofTriangles.Count; i++) roofTriangles[i] += triangleOffset; vertices.AddRange(roofVertices); } catch (Exception) { Debug.Log(roofTriangles.Count + " " + roofVertices.Count); hasErrors = true; throw; } } private static void CreateRoofTriangles(int countVertices, float[] roofPoints, ref List triangles) { if (roofIndices == null) roofIndices = new List(60); triangles.AddRange(OnlineMapsUtils.Triangulate(roofPoints, countVertices, roofIndices)); } private static int CreateRoofVertices(List baseVertices, List vertices, float[] roofPoints, float baseHeight) { float topPoint = baseHeight; int countVertices = 0; for (int i = 0; i < baseVertices.Count; i++) { Vector3 p = baseVertices[i]; float px = p.x; float pz = p.z; bool hasVertex = false; for (int j = 0; j < countVertices * 2; j += 2) { if (Math.Abs(roofPoints[j] - px) < float.Epsilon && Math.Abs(roofPoints[j + 1] - pz) < float.Epsilon) { hasVertex = true; break; } } if (!hasVertex) { int cv2 = countVertices * 2; roofPoints[cv2] = px; roofPoints[cv2 + 1] = pz; vertices.Add(new Vector3(px, topPoint, pz)); countVertices++; } } return countVertices; } private void CreateWall(List baseVertices, float baseHeight, Material material, Vector2 materialScale) { CreateWallMesh(baseVertices, baseHeight, false); Vector2 scale = material.mainTextureScale; scale.x *= perimeter / 100 * materialScale.x; scale.y *= baseHeight / 30 * materialScale.y; material.mainTextureScale = scale; } private void CreateWallMesh(List baseVertices, float baseHeight, bool inverted) { bool reversed = CreateWallVertices(baseHeight, baseVertices, vertices, uvs); if (inverted) reversed = !reversed; CreateWallTriangles(vertices, reversed); } private void CreateWallTriangles(List vertices, bool reversed) { int countVertices = vertices.Count; for (int i = 0; i < countVertices / 4; i++) { int p1 = i * 4; int p2 = p1 + 2; int p3 = p2 + 1; int p4 = p1 + 1; if (p2 >= countVertices) p2 -= countVertices; if (p3 >= countVertices) p3 -= countVertices; if (reversed) { wallTriangles.Add(p1); wallTriangles.Add(p4); wallTriangles.Add(p3); wallTriangles.Add(p1); wallTriangles.Add(p3); wallTriangles.Add(p2); } else { wallTriangles.Add(p2); wallTriangles.Add(p3); wallTriangles.Add(p1); wallTriangles.Add(p3); wallTriangles.Add(p4); wallTriangles.Add(p1); } } } private bool CreateWallVertices(float baseHeight, List baseVertices, List vertices, List uvs) { float topPoint = baseHeight; int baseVerticesCount = baseVertices.Count; Vector3 pp = Vector3.zero; Vector3 ptv = Vector3.zero; for (int i = 0; i <= baseVerticesCount; i++) { int j = i; if (j >= baseVerticesCount) j -= baseVerticesCount; Vector3 p = baseVertices[j]; Vector3 tv = new Vector3(p.x, topPoint, p.z); if (i > 0) { vertices.Add(pp); vertices.Add(ptv); vertices.Add(p); vertices.Add(tv); } pp = p; ptv = tv; } float currentDistance = 0; int countVertices = vertices.Count; int fourthVerticesCount = countVertices / 4; perimeter = 0; for (int i = 0; i < fourthVerticesCount; i++) { int i1 = i * 4; int i2 = i * 4 + 2; float magnitude = (vertices[i1] - vertices[i2]).magnitude; perimeter += magnitude; } float prevDistance = 0; for (int i = 0; i < fourthVerticesCount; i++) { int i1 = i * 4; int i2 = i * 4 + 2; float magnitude = (vertices[i1] - vertices[i2]).magnitude; float prevU = prevDistance / perimeter; currentDistance += magnitude; prevDistance = currentDistance; float curU = currentDistance / perimeter; uvs.Add(new Vector2(prevU, 0)); uvs.Add(new Vector2(prevU, 1)); uvs.Add(new Vector2(curU, 0)); uvs.Add(new Vector2(curU, 1)); } int southIndex = -1; float southZ = float.MaxValue; for (int i = 0; i < baseVerticesCount; i++) { if (baseVertices[i].z < southZ) { southZ = baseVertices[i].z; southIndex = i; } } int prevIndex = southIndex - 1; if (prevIndex < 0) prevIndex = baseVerticesCount - 1; int nextIndex = southIndex + 1; if (nextIndex >= baseVerticesCount) nextIndex = 0; float angle1 = OnlineMapsUtils.Angle2D(baseVertices[southIndex], baseVertices[nextIndex]); float angle2 = OnlineMapsUtils.Angle2D(baseVertices[southIndex], baseVertices[prevIndex]); return angle1 < angle2; } private OnlineMapsBuildingMaterial GetRandomMaterial() { if (materials == null || materials.Length == 0) return null; return materials[Random.Range(0, materials.Length)]; } private object GetProperty(Dictionary properties, string key) { object value; if (properties.TryGetValue(key, out value)) return value; return null; } private string GetTilePath(OnlineMapsMapboxTile tile) { return Application.persistentDataPath + "/" + cachePath + tile.zoom + "x" + tile.x + "x" + tile.y + ".pbf"; } private void OnChangePosition() { needUpdatePosition = true; needUpdateTiles = true; } private void OnChangeZoom() { needUpdateScale = true; needUpdateTiles = true; } private void OnLateUpdateAfter() { if (needUpdateTiles) { UpdateTiles(); needUpdateTiles = false; } if (activeTile == null || activeTile.status != OnlineMapsTileStatus.loading) StartLoadNextTile(); } private void OnTileWWWComplete(OnlineMapsWWW www) { OnlineMapsMapboxTile tile = www["tile"] as OnlineMapsMapboxTile; if (tile == null) return; if (www.hasError) { Debug.LogError(www.error); tile.status = OnlineMapsTileStatus.error; return; } byte[] bytes = www.bytes; string filename = GetTilePath(tile); FileInfo info = new FileInfo(filename); if (!info.Directory.Exists) info.Directory.Create(); File.WriteAllBytes(filename, bytes); try { byte[] decompressed = OnlineMapsZipDecompressor.Decompress(bytes); tile.Read(decompressed); Debug.Log("Generate from www " + tile); CreateBuildings(tile); tile.status = OnlineMapsTileStatus.loaded; } catch { tile.status = OnlineMapsTileStatus.error; } } private void Start() { if (map == null) map = OnlineMaps.instance; control = map.control as OnlineMapsTileSetControl; if (control == null) { Debug.LogError("Tileset Control required."); Destroy(this); return; } newTiles = new List(); tiles = new Dictionary(); lastSizeInScene = control.sizeInScene; container = new GameObject("Buildings"); container.transform.parent = transform; container.transform.localPosition = Vector3.zero; container.transform.localRotation = Quaternion.Euler(Vector3.zero); container.transform.localScale = Vector3.one; container.layer = map.gameObject.layer; map.OnChangePosition += OnChangePosition; map.OnChangeZoom += OnChangeZoom; map.OnLateUpdateAfter += OnLateUpdateAfter; control.OnMeshUpdated += UpdateBuildings; needUpdateTiles = true; } private void StartLoadNextTile() { if (newTiles.Count == 0) return; int index = 0; double minDistance = double.MaxValue; activeTile = null; double px, py; OnlineMaps.instance.GetTilePosition(out px, out py, zoom); for (int i = 0; i < newTiles.Count; i++) { OnlineMapsMapboxTile t = newTiles[i]; double lx = px - t.x - 0.5; double ly = py - t.y - 0.5; double d = lx * lx + ly * ly; if (d < minDistance) { activeTile = t; index = i; minDistance = d; } } newTiles.RemoveAt(index); TryLoadTile(activeTile); } private void TryLoadTile(OnlineMapsMapboxTile tile) { string filename = GetTilePath(tile); if (File.Exists(filename)) { byte[] bytes = File.ReadAllBytes(filename); try { byte[] decompressed = OnlineMapsZipDecompressor.Decompress(bytes); tile.Read(decompressed); CreateBuildings(tile); tile.status = OnlineMapsTileStatus.loaded; } catch { tile.status = OnlineMapsTileStatus.error; } } else { string url = tile.url; url = url.Replace("mapbox.mapbox-streets-v7", "mapbox.mapbox-streets-v8"); tile.www = new OnlineMapsWWW(url); tile.www["tile"] = tile; tile.www.OnComplete += OnTileWWWComplete; tile.status = OnlineMapsTileStatus.loading; } } private void UpdateBuildingPosition(Building building, double tlx, double tly, double brx, double bry) { Vector3 newPosition = control.GetWorldPositionWithElevation(building.centerCoordinates.x, building.centerCoordinates.y, tlx, tly, brx, bry); building.gameObject.transform.position = newPosition; } private void UpdateBuildingScale(Building building) { Vector3 s = control.sizeInScene; Vector3 c = new Vector3(s.x / building.initialSizeInScene.x, 1, s.y / building.initialSizeInScene.y); c.y = (c.x + c.z) / 2; if (Math.Abs(building.initialZoom - map.floatZoom) < float.Epsilon) s = c; else if (building.initialZoom < map.floatZoom) s = c * Mathf.Pow(2, map.floatZoom - building.initialZoom); else if (building.initialZoom > map.floatZoom) s = c / Mathf.Pow(2, building.initialZoom - map.floatZoom); building.gameObject.transform.localScale = s; } private void UpdateBuildings() { if (lastSizeInScene != control.sizeInScene) { needUpdateScale = true; } if (needUpdateScale) { UpdateBuildingsScale(); } else if (needUpdatePosition) { UpdateBuildingsPosition(); } needUpdatePosition = false; needUpdateScale = false; } private void UpdateBuildingsPosition() { Bounds bounds = new Bounds(); double tlx, tly, brx, bry; map.GetCorners(out tlx, out tly, out brx, out bry); bounds.min = new Vector3((float)tlx, (float)bry); bounds.max = new Vector3((float)brx, (float)tly); foreach (BuildingGroup buildingGroup in buildings) { foreach (Building building in buildingGroup.buildings) { if (!bounds.Intersects(building.boundsCoords)) building.active = false; else { building.active = true; UpdateBuildingPosition(building, tlx, tly, brx, bry); } } } } private void UpdateBuildingsScale() { lastSizeInScene = control.sizeInScene; UpdateBuildingsPosition(); foreach (BuildingGroup buildingGroup in buildings) { foreach (Building building in buildingGroup.buildings) { UpdateBuildingScale(building); } } } private void UpdateTiles() { if (zoomRange.InRange(map.floatZoom)) { double tlx, tly, brx, bry; map.GetTileCorners(out tlx, out tly, out brx, out bry, zoom); if ((int)tlx == ptlx && (int)tly == ptly && (int)brx == pbrx && (int)bry == pbry) return; ptlx = (int)tlx; ptly = (int)tly; pbrx = (int)brx; pbry = (int)bry; foreach (var pair in tiles) pair.Value.used = false; for (int x = (int)tlx; x <= (int)brx; x++) { for (int y = (int)tly; y <= (int)bry; y++) { ulong key = OnlineMapsTileManager.GetTileKey(zoom, x, y); OnlineMapsMapboxTile tile; if (tiles.TryGetValue(key, out tile)) { tile.used = true; continue; } tile = new OnlineMapsMapboxTile(x, y, zoom, map, false); tiles.Add(key, tile); newTiles.Add(tile); } } List unused = new List(); foreach (var pair in tiles) { OnlineMapsMapboxTile tile = pair.Value; if (tile.used) continue; if (tile == activeTile) activeTile = null; unused.Add(pair.Key); List tileBuildings = tile["buildings"] as List; if (tileBuildings != null) { foreach (BuildingGroup b in tileBuildings) { b.Dispose(); buildings.Remove(b); } } newTiles.Remove(tile); } foreach (ulong k in unused) { tiles.Remove(k); } } else { ptlx = int.MinValue; activeTile = null; foreach (BuildingGroup buildingGroup in buildings) { buildingGroup.Dispose(); } buildings.Clear(); tiles.Clear(); newTiles.Clear(); } } public class BuildingGroup { public ulong id; public List buildings = new List(); public List tiles = new List(); public void Add(Building building) { buildings.Add(building); } public void AddTile(OnlineMapsMapboxTile tile) { tiles.Add(tile); } public void Dispose() { foreach (Building building in buildings) building.Dispose(); buildings = null; tiles = null; } public void RemoveTile(OnlineMapsMapboxTile tile) { tiles.Remove(tile); if (tiles.Count == 0) Dispose(); } } public class Building { public Vector2 centerCoordinates; public GameObject gameObject; public Vector2 initialSizeInScene; public float initialZoom; public Bounds boundsCoords; private bool _active = false; public bool active { get { return _active; } set { if (_active == value) return; _active = value; gameObject.SetActive(value); } } public void Dispose() { if (gameObject != null) { OnlineMapsUtils.Destroy(gameObject); gameObject = null; } } } }