#if GLEY_TRAFFIC_SYSTEM using Unity.Burst; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; namespace Gley.TrafficSystem.Internal { /// /// Handles driving part of the vehicle /// [BurstCompile] public struct DriveJob : IJobParallelFor { public NativeArray BodyForce; public NativeArray TrailerForce; public NativeArray ActionValue; public NativeArray WheelRotation; public NativeArray TurnAngle; public NativeArray VehicleRotationAngle; public NativeArray Gear; public NativeArray ReadyToRemove; public NativeArray NeedsWaypoint; public NativeArray IsBraking; [ReadOnly] public NativeArray SpecialDriveAction; [ReadOnly] public NativeArray TargetWaypointPosition; [ReadOnly] public NativeArray AllBotsPosition; [ReadOnly] public NativeArray GroundDirection; [ReadOnly] public NativeArray ForwardDirection; [ReadOnly] public NativeArray RightDirection; [ReadOnly] public NativeArray TrailerRightDirection; [ReadOnly] public NativeArray TrailerForwardDirection; [ReadOnly] public NativeArray TriggerForwardDirection; [ReadOnly] public NativeArray DownDirection; [ReadOnly] public NativeArray CarVelocity; [ReadOnly] public NativeArray TrailerVelocity; [ReadOnly] public NativeArray CameraPositions; [ReadOnly] public NativeArray ClosestObstacle; [ReadOnly] public NativeArray WheelCircumferences; [ReadOnly] public NativeArray MaxSteer; [ReadOnly] public NativeArray PowerStep; [ReadOnly] public NativeArray BrakeStep; [ReadOnly] public NativeArray Drag; [ReadOnly] public NativeArray TrailerDrag; [ReadOnly] public NativeArray MaxSpeed; [ReadOnly] public NativeArray WheelDistance; [ReadOnly] public NativeArray SteeringStep; [ReadOnly] public NativeArray VehicleLength; [ReadOnly] public NativeArray MassDifference; [ReadOnly] public NativeArray DistanceToStop; [ReadOnly] public NativeArray WheelSign; [ReadOnly] public NativeArray NrOfWheels; [ReadOnly] public NativeArray TrailerNrOfWheels; [ReadOnly] public NativeArray ExcludedVehicles; [ReadOnly] public float3 WorldUp; [ReadOnly] public float DistanceToRemove; [ReadOnly] public float FixedDeltaTime; private float3 _waypointDirection; private float _minWaypointDistance; private float _targetSpeed; //required car speed in next frame private float _currentSpeed; //speed in current frame private float _dotProduct; private float _waypointDistance; private float _angle; private bool _avoidBackward; private bool _avoidForward; public void Execute(int index) { //check if vehicle is far enough for the player and it can be removed RemoveVehicle(index); if (ExcludedVehicles[index]==true) { return; } //reset variables _avoidForward = false; _avoidBackward = false; IsBraking[index] = false; //compute current frame values float3 forwardVelocity = ForwardDirection[index] * Vector3.Dot(CarVelocity[index], ForwardDirection[index]); _targetSpeed = math.length(forwardVelocity); _currentSpeed = _targetSpeed * math.sign(Vector3.Dot(forwardVelocity, ForwardDirection[index])); _waypointDirection = TargetWaypointPosition[index] - AllBotsPosition[index]; _dotProduct = math.dot(_waypointDirection, ForwardDirection[index]);// used to check if vehicle passed the current waypoint _waypointDirection.y = 0; _waypointDistance = math.distance(TargetWaypointPosition[index], AllBotsPosition[index]); //Debug.Log(forwardDirection[index]); //change the distance to change waypoints based on vehicle speed //at 50 kmh -> min distance =1.5 //at 100 kmh -> min distance =2.5 //kmh to ms => 50/3.6 = 13.88 if (_currentSpeed < 13.88f) { _minWaypointDistance = 1.5f; } else { _minWaypointDistance = 1.5f + (_currentSpeed * 3.6f - 50) / 50; } //compute acceleration based on the current vehicle actions Drive(index); //compute forces required for the target speed to be achieved ComputeBodyForce(index, MaxSpeed[index], Gear[index]); //check if a new waypoint is required ChangeWaypoint(index); //compute the wheel turn amount ComputeWheelRotationAngle(index); //compute steering angle ComputeSteerAngle(index, MaxSteer[index], _targetSpeed); } #region Drive /// /// Compute acceleration value based on the current vehicle`s driving actions /// /// index of the current vehicle private void Drive(int index) { if (ActionValue[index] != math.INFINITY) { ActionValue[index] -= FixedDeltaTime; } switch (SpecialDriveAction[index]) { case DriveActions.Reverse: Reverse(index); break; case DriveActions.AvoidReverse: AvoidReverse(index); break; case DriveActions.StopTemp: case DriveActions.NoWaypoint: case DriveActions.NoPath: case DriveActions.Stop: StopNow(index); break; case DriveActions.StopInDistance: StopInDistance(index); break; case DriveActions.StopInPoint: case DriveActions.GiveWay: StopInPoint(index); break; case DriveActions.Follow: case DriveActions.Overtake: Follow(index, MaxSpeed[index]); break; default: Forward(index, MaxSpeed[index]); break; } } /// /// Normal drive /// /// /// max possible speed of the current vehicle void Forward(int index, float maxSpeed) { if (IsInCorrectGear(index)) { //slow down in corners if (maxSpeed / _targetSpeed < 1.5) { if (math.abs(TurnAngle[index]) > 5) { //Debug.Log(1 + math.abs(turnAngle[index]) / maxSteer[index]); //ApplyBrakes(index, 1 + math.abs(turnAngle[index]) / maxSteer[index]); ApplyBrakes(index, 1); //isBraking[index] = true; return; } } //set speed exactly to max speed if it is close float speedDifference = _targetSpeed - maxSpeed; if (math.abs(speedDifference) < PowerStep[index] || math.abs(speedDifference) < BrakeStep[index]) { _targetSpeed = maxSpeed; return; } //brake if the vehicle runs faster than the max allowed speed if (_targetSpeed > maxSpeed) { //for the brake lights to be active only when hard brakes are needed, to avoid short blinking if (speedDifference > 1) { //turn on braking lights IsBraking[index] = true; } ApplyBrakes(index, 1); return; } ApplyAcceleration(index); } else { ApplyBrakes(index, 1); PutInDrive(index); } } /// /// Go backwards /// /// void Reverse(int index) { if (IsInCorrectGear(index)) { ApplyAcceleration(index); } else { ApplyBrakes(index, 1); PutInReverse(index); } } /// /// Go backwards in opposite direction /// /// private void AvoidReverse(int index) { if (IsInCorrectGear(index)) { AvoidBackward(); Reverse(index); } else { StopInDistance(index); PutInReverse(index); } } /// /// Stop vehicle immediately /// /// void StopNow(int index) { _targetSpeed = 0; IsBraking[index] = true; } /// /// Stop the car in a given distance /// /// private void StopInDistance(int index) { float stopDistance = math.distance(ClosestObstacle[index], AllBotsPosition[index]) - DistanceToStop[index]; IsBraking[index] = true; if (stopDistance <= 0) { StopNow(index); return; } if (_currentSpeed <= BrakeStep[index]) { StopNow(index); return; } float velocityPerFrame = _currentSpeed * FixedDeltaTime; int nrOfFrames = (int)(stopDistance / velocityPerFrame) + 1; int brakeNrOfFrames = (int)(_currentSpeed / BrakeStep[index]); if (brakeNrOfFrames >= nrOfFrames) { ApplyBrakes(index, (float)brakeNrOfFrames / nrOfFrames); } } /// /// Stop the vehicle precisely on a waypoint /// /// void StopInPoint(int index) { //if there is something in trigger closer -> stop if (!ClosestObstacle[index].Equals(float3.zero)) { if (math.distance(ClosestObstacle[index], AllBotsPosition[index]) < _waypointDistance) { StopInDistance(index); } } //stop if the waypoint is behind the vehicle if (_dotProduct < 0) { StopNow(index); return; } //compute per frame velocity float velocityPerFrame = _currentSpeed * FixedDeltaTime; //check number of frames required to reach next waypoint int nrOfFrames = (int)(_waypointDistance / velocityPerFrame); if (nrOfFrames < 0) { nrOfFrames = int.MaxValue; } //if vehicle is in target -> stop if (nrOfFrames == 0) { StopNow(index); return; } //number of frames required to brake int brakeNrOfFrames = (int)(_currentSpeed / BrakeStep[index]); //calculate the required brake power if (brakeNrOfFrames > nrOfFrames) { ApplyBrakes(index, (float)brakeNrOfFrames / nrOfFrames); } else { //if target waypoint is far -> accelerate if (nrOfFrames - brakeNrOfFrames > 60) { ApplyAcceleration(index); return; } } //turn on the brake lights IsBraking[index] = true; } /// /// Opposite direction is required forward /// /// void AvoidForward(int index) { _avoidForward = true; Forward(index, MaxSpeed[index]); } /// /// Follow the front vehicle /// /// /// the speed of the front vehicle private void Follow(int index, float followSpeed) { float distance = math.distance(ClosestObstacle[index], AllBotsPosition[index]) - DistanceToStop[index]; if (distance < 0) { //distance is dangerously close apply emergency brake _targetSpeed = followSpeed - 1; return; } float speedDifference = _targetSpeed - followSpeed; //current vehicle moves slower then the vehicle it follows if (speedDifference <= 0) { //speeds are close enough, match them if (math.abs(speedDifference) < math.max(PowerStep[index], BrakeStep[index])) { _targetSpeed = followSpeed; return; } //vehicle needs to accelerate to catch the followed vehicle ApplyAcceleration(index); return; } //compute per frame velocity float velocityPerFrame = (speedDifference) * FixedDeltaTime; //check number of frames required to slow down int nrOfFrames = (int)(distance / velocityPerFrame); //if nr of frames = 0 => distance is 0, it means that the 2 cars are close enough, set the speed to be equal to the follow speed if (nrOfFrames == 0) { _targetSpeed = followSpeed; return; } //number of frames required to brake int brakeNrOfFrames = (int)(speedDifference / BrakeStep[index]); //calculate the required brake power if (brakeNrOfFrames >= nrOfFrames) { if (nrOfFrames > 0) { ApplyBrakes(index, (float)brakeNrOfFrames / nrOfFrames); } } } #endregion /// /// Compute the next frame force to be applied to RigidBody /// /// current vehicle index /// target linear speed /// private void ComputeBodyForce(int index, float maxSpeed, int gear) { //set speed limit if (maxSpeed == 0 || (math.sign(_targetSpeed * gear) != math.sign(_currentSpeed) && math.abs(_currentSpeed) > 0.01f)) { _targetSpeed = 0; } maxSpeed = math.max(_targetSpeed, maxSpeed); if (gear == -1) { if (_targetSpeed < -maxSpeed / 5) { _targetSpeed = -maxSpeed / 5; } } else { if (_targetSpeed > maxSpeed) { _targetSpeed = maxSpeed; } } //Debug.Log(maxSpeed * 3.6 + " " + _targetSpeed * 3.6f + " " + currentSpeed * 3.6f); float dSpeed = _targetSpeed * gear - _currentSpeed; float velocity = dSpeed + GetDrag(_targetSpeed, Drag[index], FixedDeltaTime); //if has trailer if (TrailerNrOfWheels[index] > 0) { TrailerForce[index] = -TrailerRightDirection[index] * Vector3.Dot(TrailerVelocity[index], TrailerRightDirection[index]) / TrailerNrOfWheels[index]; if (_targetSpeed != 0) { velocity += dSpeed * MassDifference[index] + GetDrag(_targetSpeed, TrailerDrag[index], FixedDeltaTime); } } BodyForce[index] = velocity * ForwardDirection[index] / NrOfWheels[index]; } /// /// Check if new waypoint is required /// /// current vehicle index void ChangeWaypoint(int index) { if (_waypointDistance < _minWaypointDistance || (_dotProduct < 0 && _waypointDistance < _minWaypointDistance * 5)) { NeedsWaypoint[index] = true; } } /// /// Compute the wheel turn amount /// /// current vehicle index void ComputeWheelRotationAngle(int index) { WheelRotation[index] += (360 * (_currentSpeed / WheelCircumferences[index]) * FixedDeltaTime); } /// /// Compute the required steering angle /// /// /// /// void ComputeSteerAngle(int index, float maxSteer, float targetVelocity) { float currentTurnAngle = TurnAngle[index]; float currentStep = SteeringStep[index]; //increase turn angle with speed if (targetVelocity > 14) { //if speed is greater than 50 km/h increase the turn speed; currentStep *= targetVelocity / 14; } float wheelAngle = SignedAngle(TriggerForwardDirection[index], _waypointDirection, WorldUp); //determine the target angle if (_avoidBackward) { _angle = WheelSign[index] * -maxSteer; } else { if (_avoidForward) { _angle = maxSteer; } else { _angle = SignedAngle(ForwardDirection[index], _waypointDirection, WorldUp); } } if (!_avoidBackward && !_avoidForward) { //if car is stationary, do not turn the wheels if (_currentSpeed < 1) { if (SpecialDriveAction[index] != TrafficSystem.DriveActions.StopInDistance && SpecialDriveAction[index] != TrafficSystem.DriveActions.ChangeLane) { _angle = 0; } } //check if the car can turn at current speed float framesToReach = _waypointDistance / (targetVelocity * FixedDeltaTime); //if it is close to the waypoint turn at normal speed if (framesToReach > 5) { //calculate the number of frames required to rotate to the target amount float framesToRotate = math.abs(_angle - currentTurnAngle) / currentStep; //car is too fast for this corner //increase the speed turn amount to be able to corner if (framesToRotate > framesToReach + 5) { currentStep *= framesToRotate / framesToReach; } else { //used to straight the wheels after a curve if (math.sign(_angle) != math.sign(wheelAngle) && math.abs(_angle - wheelAngle) > 10) { currentStep *= framesToRotate / 5; } } } } //apply turning speed if (_angle - currentTurnAngle < -currentStep) { currentTurnAngle -= currentStep; } else { if (_angle - currentTurnAngle > currentStep) { currentTurnAngle += currentStep; } else { currentTurnAngle = _angle; } } //if the wheel sign and turn angle sign are different the wheel is heading to the destination waypoint, it just needs to keep that direction //no additional turning is required so keep the waypoint direction if (math.sign(currentTurnAngle) != math.sign(wheelAngle) && math.abs(wheelAngle) < 5) { currentTurnAngle = _angle; } //clamp the value if (currentTurnAngle > maxSteer) { currentTurnAngle = maxSteer; } if (currentTurnAngle < -maxSteer) { currentTurnAngle = -maxSteer; } //currentTurnAngle = angle; //compute the body turn angle based on wheel turn amount float turnRadius = WheelDistance[index] / math.tan(math.radians(currentTurnAngle)); VehicleRotationAngle[index] = (180 * targetVelocity * FixedDeltaTime) / (math.PI * turnRadius) * Gear[index]; TurnAngle[index] = currentTurnAngle; } /// /// Checks if the vehicle can be removed from scene /// /// the list index of the vehicle void RemoveVehicle(int index) { bool remove = true; for (int i = 0; i < CameraPositions.Length; i++) { if (math.distancesq(AllBotsPosition[index], CameraPositions[i]) < DistanceToRemove) { remove = false; break; } } ReadyToRemove[index] = remove; } /// /// Determine if a car has can change the heading direction /// /// /// private bool IsInCorrectGear(int index) { switch (SpecialDriveAction[index]) { case TrafficSystem.DriveActions.Reverse: case TrafficSystem.DriveActions.AvoidReverse: if (Gear[index] != -1) { return false; } break; default: if (Gear[index] != 1) { return false; } break; } return true; } void PutInDrive(int index) { if (_targetSpeed == 0) { if (math.abs(_currentSpeed) < 0.0001) { Gear[index] = 1; } } } void PutInReverse(int index) { if (_targetSpeed == 0) { if (math.abs(_currentSpeed) < 0.0001f) { Gear[index] = -1; } } } /// /// Accelerate current vehicle /// /// void ApplyAcceleration(int index) { _targetSpeed += PowerStep[index]; } /// /// Brake the vehicle /// /// /// void ApplyBrakes(int index, float power) { //this is a workaround to mediate the brake power power /= 2; if(power<1) { power = 1; } _targetSpeed -= BrakeStep[index] * power; if (_targetSpeed < 0) { StopNow(index); } } /// /// Opposite direction is required in reverse /// void AvoidBackward() { _avoidBackward = true; } /// /// Compute sign angle between 2 directions /// /// /// /// /// float SignedAngle(float3 dir1, float3 dir2, float3 normal) { if (dir1.Equals(float3.zero)) { return 0; } dir1 = math.normalize(dir1); return math.degrees(math.atan2(math.dot(math.cross(dir1, dir2), normal), math.dot(dir1, dir2))); } /// /// Compensate the drag from the physics engine /// /// /// /// float GetDrag(float targetSpeed, float drag, float fixedDeltaTime) { float result = targetSpeed / (1 - drag * fixedDeltaTime) - targetSpeed; return result; } } } #endif