Newer
Older
Traffic-Simulator / Assets / Scripts / Car / Car.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Car {
    private Route route;
    public Road road;
    private int roadIndex = 0;
    public float roadPositon = 0f, speed = 0f, airResistance;
    private GameObject gameObject;
    public Config config;
    public Vector3 position, direction;
    public bool isAlive = true;
    private CarData carData;
    private float brakingTime, brakingDistance;
    private static float g = - Physics.gravity.y;
    private static float breakawayAcceleration = 0.9f * g * (float) Mathf.Sqrt(2) / 2f, rollingAcceleration = 0.02f * g;

    public Car(Route route, Transform parent, Config config) {
        this.route = route;
        this.config = config;
        road = route.roads[0];
        foreach (Road currentRoad in route.roads) {
            currentRoad.carsOnRoute.Add(this);
        }
        airResistance = config.carAirResistanceModifier * config.carFrontalArea * config.airDensity * 0.5f;
        setupGameObject(parent);
        position = road.path.getPosition(0f);
    }

    private void setupGameObject(Transform parent) {
        gameObject = new GameObject();
        FlatCircleRenderer renderer = new FlatCircleRenderer(0.8f, 0.2f, 32, 0.01f);
        MeshRenderer meshRenderer = gameObject.AddComponent<MeshRenderer>();
        meshRenderer.material = config.carAccelerationMaterial;
        meshRenderer.receiveShadows = false;
        gameObject.AddComponent<MeshFilter>().mesh = renderer.mesh;
        gameObject.transform.parent = parent;
        GameObject carMesh = new GameObject();
        carMesh.AddComponent<MeshRenderer>().material = config.carMaterial;
        carMesh.AddComponent<MeshFilter>().mesh = config.carMesh;
        carMesh.transform.parent = gameObject.transform;
        carData = gameObject.AddComponent<CarData>();
        carData.car = this;
    }

    private void updateBrakingParameters() {
        float B = - airResistance;
        float A = - rollingAcceleration - breakawayAcceleration;
        float sqrtAB = Mathf.Sqrt(A * B);
        float sqrtAbyB = Mathf.Sqrt(A / B);
        float offset = Mathf.Atan(1f / sqrtAbyB * speed);
        brakingTime = offset;
        brakingDistance = ((float)(Mathf.Log((float)System.Math.Cosh(sqrtAB * brakingTime - offset)) -
                                   Mathf.Log((float)System.Math.Cosh(offset)))) / B;
    }

    private void incrementRoad() {
        road.carsOnRoute.Remove(this);
        roadPositon -= road.path.length;
        roadIndex++;
        if (roadIndex == route.roads.Count) {
            GameObject.Destroy(gameObject);
            road.carsOnRoute.Remove(this);
            isAlive = false;
            return;
        }
        road = route.roads[roadIndex];
    }

    private float getMaxSpeed(Road road, float distance) {
        return Mathf.Sqrt((float)(Mathf.Abs(road.path.getRadius(road.path.getT(distance)))) * breakawayAcceleration);
    }

    private bool needsBraking(float totalDistance, float maxSpeed) {
        float B = - airResistance;
        float A = - rollingAcceleration - breakawayAcceleration;
        float sqrtAB = Mathf.Sqrt(A * B);
        float sqrtAbyB = Mathf.Sqrt(A / B);
        float sqrtBbyA = 1 / sqrtAbyB;
        float offset = Mathf.Atan(sqrtBbyA * speed);
        float time = (offset - Mathf.Atan(sqrtBbyA * maxSpeed)) / sqrtAB;
        float distance = (-Mathf.Log(Mathf.Cos(sqrtAB * time - offset)) + Mathf.Log(Mathf.Cos(offset))) / B;
        return totalDistance <= distance;
    }

    private bool needsBrakingForCar(Car car) {
        if (roadIndex == route.roads.Count - 1) {
            return false;
        }
        if (Vector3.Distance(position, car.position) > 10f) {
            return false;
        } 
        Road conflict = car.road;
        float otherDistance = conflict.path.length - car.roadPositon;
        for (int i = car.roadIndex; !this.route.roads.Contains(conflict) && i < car.route.roads.Count;) {
            i++;
            conflict = car.route.roads[i];
            otherDistance += conflict.path.length;
        }
        otherDistance -= conflict.path.length;
        float thisDistance = - this.roadPositon;
        for (int i = this.roadIndex ; i < this.route.roads.Count && this.route.roads[i] != conflict; i++) {
            thisDistance += this.route.roads[i].path.length;
        }
        float time = Mathf.Max(0.5f, thisDistance / speed);
        float otherTraveledDistance = car.speed * time;
        float currentCarDistance = thisDistance - otherDistance;
        float projectedDistance = otherTraveledDistance - otherDistance;
        float savetyDistance = 3.5f + speed * 1.8f;
        if (config.giveWay &&
            car.roadIndex + 1 < car.route.roads.Count && conflict == car.route.roads[car.roadIndex+1] && 
                roadIndex + 1 <     route.roads.Count && conflict == route.roads[roadIndex+1]) {
            Vector3 conflictPosition = conflict.nodes[0].position;
            if (Vector3.Cross(position - conflictPosition, car.position - conflictPosition).y > 0) {
                Debug.DrawLine(position + 1.5f * Vector3.up, car.position + 2 * Vector3.up, Color.cyan, 0f, false);
                return true;
            } else {
                return false;
            }
        }
        if (currentCarDistance < 0 || currentCarDistance > car.brakingDistance + savetyDistance) {
            return false;
        }
        if (currentCarDistance > 0 && currentCarDistance < savetyDistance ||
            projectedDistance  > 0 && projectedDistance  < savetyDistance) {
            Debug.DrawLine(position + 1.5f * Vector3.up, car.position + 2 * Vector3.up, Color.blue, 0f, false);
            return true;
        }
        if (needsBraking(Mathf.Min(projectedDistance, currentCarDistance) - savetyDistance, car.speed)) {
            Debug.DrawLine(position + 1.5f * Vector3.up, car.position + 2 * Vector3.up, Color.red, 0f, false);
            return true;
        }
        return false;
    }

    private bool isBraking() {
        float stoppingDistance = Mathf.Max(1.8f * speed, brakingDistance + speed) + 3.5f;
        Road currentRoad = road;
        float currentRoadPosition = roadPositon;
        int currentRoadIndex = roadIndex;
        int steps = 30;
        for (int i = 1; i <= steps; i++) {
            currentRoadPosition += stoppingDistance / steps;
            float totalDistance = stoppingDistance / steps * i;
            while (currentRoadPosition >= currentRoad.path.length) {
                if (currentRoadIndex == route.roads.Count - 1) {
                    goto end;
                }
                currentRoadIndex++;
                currentRoadPosition -= currentRoad.path.length;
                currentRoad = route.roads[currentRoadIndex];
            }
            if (needsBraking(totalDistance, getMaxSpeed(currentRoad, currentRoadPosition))) {
                return true;
            }
            if (currentRoad != road &&
                currentRoad.nodes[0] is CustomNode && 
                !((CustomNode)currentRoad.nodes[0]).isPassable &&
                totalDistance - 2f > brakingDistance && 
                needsBraking(totalDistance - 3f, 0f)) {
                return true;
            }
        }
    end:
        List<Car> carsToCheck = new List<Car>();
        for (int i = this.roadIndex; i <= currentRoadIndex; i++) {
            foreach (Car car in this.route.roads[i].carsOnRoute) {
                if (!carsToCheck.Contains(car)) {
                    carsToCheck.Add(car);
                }
            }
        }
        foreach (Car car in carsToCheck) {
            if (car != this && needsBrakingForCar(car)) {
                return true;
            }
        }
        return false;
    }

    private static float atanh(float x) {
        return 0.5f * (Mathf.Log(1 + x) - Mathf.Log(1 - x));
    }

    private void applyAcceleration(float A, float B, float deltaT) {
        if (A * B > 0) {
            if (speed == 0) {
                return;
            }
            float sqrtAB = Mathf.Sqrt(A * B);
            float sqrtAbyB = Mathf.Sqrt(A / B);
            float offset = Mathf.Atan(1f / sqrtAbyB * speed);
            speed = - sqrtAbyB * Mathf.Tan(sqrtAB * deltaT - offset);
            roadPositon += (-Mathf.Log(Mathf.Cos(sqrtAB * deltaT - offset)) + Mathf.Log(Mathf.Cos(offset))) / B;
        } else {
            float sqrtAB = Mathf.Sqrt(-A * B);
            float sqrtAbyB = Mathf.Sqrt(-A / B);
            float offset = atanh(1.0f / sqrtAbyB * speed);
            speed = sqrtAbyB * (float) System.Math.Tanh(sqrtAB * deltaT + offset);
            roadPositon += ((float)(Mathf.Log((float)System.Math.Cosh(sqrtAB * deltaT - offset)) - Mathf.Log((float)System.Math.Cosh(offset)))) / B;
        }
    }

    public void step(float deltaTime) {
        airResistance = config.carAirResistanceModifier * config.carFrontalArea * config.airDensity * 0.5f / config.carMass;
        updateBrakingParameters();
        float B = - airResistance;
        float A = - rollingAcceleration;
        if (isBraking()) {
            gameObject.GetComponent<MeshRenderer>().material = config.carBrakingMaterial;
            A -= breakawayAcceleration;
        } else {
            gameObject.GetComponent<MeshRenderer>().material = config.carAccelerationMaterial;
            if (speed > 0) {
                A += Mathf.Min(config.power / speed / config.carMass, breakawayAcceleration);
            } else {
                A += 1f;
            }
        }
        applyAcceleration(A, B, deltaTime);
        speed = Mathf.Max(0, speed);
        while (roadPositon > road.path.length) {
            incrementRoad();
        }
        float t = road.path.getT(roadPositon);
        position = road.path.getPosition(t);
        direction = road.path.getDirection(t);
        direction.Normalize();
    }
}