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();
}
}
}
}
}