using System; using UnityEngine; using UnityEngine.UI; using Random = UnityEngine.Random; public class TrafficSimulation : MonoBehaviour { /// /// Resolution of the road network. /// const int roadCount = 20; /// /// The maximum number of items for which the array is initialized. /// const int _trueMaxItems = 15000; /// /// The maximum number of items. /// public int maxItems = 15000; /// /// The speed of generation of new items (item/sec). /// public float generateSpeed = 100; /// /// Materials of items. /// public Material[] materials; /// /// Moving speed (km/h) /// public float speed = 600; /// /// The text, which shows the current number of items. /// public Text countItemsText; /// /// The slider changes the maximum number of items. /// public Slider maxItemsSlider; /// /// The text, which shows the maximum number of items. /// public Text maxItemsText; /// /// Array of items /// private static Item[] items = new Item[_trueMaxItems]; /// /// Count items /// private static int countItems = 0; /// /// Array of road points /// private static RoadPoint[,] roadPoints = new RoadPoint[roadCount, roadCount]; /// /// The number of item types /// private static int[] itemTypes; /// /// Last generation time (ticks). /// private long lastGenerateTicks; /// /// Reference to map /// private OnlineMaps map; /// /// Generate new items /// private void GenerateItems() { if (countItems >= maxItems) return; long ticks = DateTime.Now.Ticks; int countNewItems = Mathf.RoundToInt(Random.Range(0.5f, 1.5f) * generateSpeed * (ticks - lastGenerateTicks) / 10000000); if (countNewItems == 0) return; if (countItems + countNewItems > maxItems) countNewItems = maxItems - countItems; for (int i = 0; i < countNewItems; i++) { int sx = Random.Range(0, roadCount); int sy = Random.Range(0, roadCount); RoadPoint startPoint = roadPoints[sx, sy]; int ex = Random.Range(0, roadCount); int ey = Random.Range(0, roadCount); RoadPoint endPoint = roadPoints[ex, ey]; int materialIndex = Random.Range(0, materials.Length); Item item = new Item( new [] { startPoint.longitude, startPoint.latitude, startPoint.longitude, endPoint.latitude, endPoint.longitude, endPoint.latitude }, materialIndex, speed ); itemTypes[materialIndex]++; items[i + countItems] = item; } countItems += countNewItems; lastGenerateTicks = ticks; } /// /// Generate road network /// private void GenerateRoads() { double tlx, tly, brx, bry; map.GetTopLeftPosition(out tlx, out tly); map.GetBottomRightPosition(out brx, out bry); map.projection.CoordinatesToTile(tlx, tly, map.zoom, out tlx, out tly); map.projection.CoordinatesToTile(brx, bry, map.zoom, out brx, out bry); double stepX = 1d / roadCount * (brx - tlx); double stepY = 1d / roadCount * (bry - tly); for (int x = 0; x < roadCount; x++) { double px = x * stepX + tlx; for (int y = 0; y < roadCount; y++) { double py = y * stepY + tly; double cpx, cpy; map.projection.TileToCoordinates(px, py, map.zoom, out cpx, out cpy); roadPoints[x, y] = new RoadPoint(cpx, cpy); } } } /// /// Render items /// public void OnRenderObject() { if (countItems == 0) return; double sx = map.tilesetSize.x * OnlineMapsUtils.tileSize / -map.tilesetWidth; double sy = map.tilesetSize.y * OnlineMapsUtils.tileSize / map.tilesetHeight; double tlx, tly; map.GetTopLeftPosition(out tlx, out tly); int zoom = map.zoom; OnlineMapsProjection projection = map.projection; projection.CoordinatesToTile(tlx, tly, zoom, out tlx, out tly); Vector3 mapPos = map.transform.position; int maxX = 1 << zoom; int halfMaxX = maxX / -2; GL.PushMatrix(); GL.MultMatrix(transform.localToWorldMatrix); for (int i = 0; i < materials.Length; i++) { materials[i].SetPass(0); for (int j = 0; j < countItems; j++) { Item item = items[j]; int materialIndex = item.materialIndex; if (i != materialIndex) continue; double px, py; projection.CoordinatesToTile(item.longitude, item.latitude, zoom, out px, out py); px -= tlx; py -= tly; if (px < halfMaxX) px += maxX; px *= sx; py *= sy; float fx = (float) px + mapPos.x; float fy = mapPos.y; float fz = (float) py + mapPos.z; Vector3 p1 = item.vertices[0]; Vector3 p2 = item.vertices[1]; Vector3 p3 = item.vertices[2]; Vector3 p4 = item.vertices[3]; GL.Begin(GL.QUADS); GL.TexCoord2(0, 1); GL.Vertex3(p1.x + fx, p1.y + fy, p1.z + fz); GL.TexCoord2(0, 0); GL.Vertex3(p2.x + fx, p2.y + fy, p2.z + fz); GL.TexCoord2(1, 0); GL.Vertex3(p3.x + fx, p3.y + fy, p3.z + fz); GL.TexCoord2(1, 1); GL.Vertex3(p4.x + fx, p4.y + fy, p4.z + fz); GL.End(); } } GL.PopMatrix(); } /// /// This method is called when the changed value of the slider. /// public void OnChangeMaxItems() { maxItems = Mathf.Min((int)maxItemsSlider.value, _trueMaxItems); maxItemsText.text = "Max items: " + maxItems; } private void Start() { map = OnlineMaps.instance; lastGenerateTicks = DateTime.Now.Ticks; itemTypes = new int[materials.Length]; maxItems = (int)maxItemsSlider.value; maxItemsText.text = "Max items: " + maxItems; GenerateRoads(); } private void Update() { GenerateItems(); UpdatePositions(); countItemsText.text = "Count items: " + countItems; } /// /// Updating the position of items. /// private void UpdatePositions() { int finishedItems = 0; for (int i = 0; i < countItems; i++) { Item item = items[i]; item.UpdatePosition(); if (item.finished) { itemTypes[item.materialIndex]--; finishedItems++; items[i] = null; } } if (finishedItems > 0) { int o = 0; for (int i = 0; i < countItems; i++) { if (items[i] == null) o++; else items[i - o] = items[i]; } countItems -= o; } } /// /// Point of road network /// internal class RoadPoint { public readonly double latitude; public readonly double longitude; public RoadPoint(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } } /// /// Item /// internal class Item { /// /// Size of item plane /// const float size = 24f; const float halfSize = size / 2; /// /// Latitude /// public double latitude; /// /// Longitude /// public double longitude; /// /// Index of material /// public int materialIndex; /// /// Array of route points (lng, lat, lng, lat, ... etc). /// public double[] route; /// /// Moving speed (km/h) /// public float speed; /// /// Rotation to next point /// public double rotation; /// /// Array of vertices /// public Vector3[] vertices; /// /// Is finished? /// public bool finished; /// /// Distance to next point /// private double stepDistance; /// /// Index of current point /// private int pointIndex = 0; /// /// Current point longitude /// private double currentLongitude; /// /// Current point latitude /// private double currentLatitude; /// /// Next point longitude /// private double nextLongitude; /// /// Next point latitude /// private double nextLatitude; /// /// Step duration (sec) /// private double totalTime; /// /// Step progress (0-1) /// private double progress; public Item(double[] route, int materialIndex, float speed) { this.route = route; this.materialIndex = materialIndex; this.speed = speed; InitValues(); } /// /// Initialize/cache values for a quick calculation. /// private void InitValues() { longitude = currentLongitude = route[pointIndex]; latitude = currentLatitude = route[pointIndex + 1]; nextLongitude = route[pointIndex + 2]; nextLatitude = route[pointIndex + 3]; double dx, dy; OnlineMapsUtils.DistanceBetweenPoints(longitude, latitude, nextLongitude, nextLatitude, out dx, out dy); stepDistance = Math.Sqrt(dx * dx + dy * dy); totalTime = stepDistance / speed * 3600; rotation = 450 - OnlineMapsUtils.Angle2D(currentLongitude, currentLatitude, nextLongitude, nextLatitude); Matrix4x4 matrix = new Matrix4x4(); matrix.SetTRS(Vector3.zero, Quaternion.Euler(0, (float)rotation, 0), Vector3.one); vertices = new [] { matrix.MultiplyPoint(new Vector3(-halfSize, 0, -halfSize)), matrix.MultiplyPoint(new Vector3(-halfSize, 0, halfSize)), matrix.MultiplyPoint(new Vector3(halfSize, 0, halfSize)), matrix.MultiplyPoint(new Vector3(halfSize, 0, -halfSize)) }; } /// /// Update position of item /// public void UpdatePosition() { progress += Time.deltaTime / totalTime; if (progress < 1) { longitude = (nextLongitude - currentLongitude) * progress + currentLongitude; latitude = (nextLatitude - currentLatitude) * progress + currentLatitude; } else { pointIndex += 2; progress = 0; if (pointIndex >= route.Length - 2) { route = null; finished = true; pointIndex = -1; } else { InitValues(); } } } } }