

int middleRayIndex = (forwardSensorCount == 1) ? 0 : Mathf.CeilToInt(forwardSensorCount / 2.0f);
Vector3 origin = transform.position;
//Check for left side
hitSomething = Physics.SphereCastNonAlloc(origin - transform.right * sideSensingWidth/2.0f,
sensorRadius,
-transform.right,
obstacleHits,
sideSensingDistance,
obstacleMask.value & ~roadMask.value) > 0;
avoidSteerPercent = (hitSomething) ? 1.0f - obstacleHits[0].distance / sideSensingDistance : 0.0f;
//Debug.Log("Avoid Steer Percent from left: " + avoidSteerPercent);
leftAvoidSteerPercent = avoidSteerPercent;
requiredInterest = -transform.right;
dangers[0] = hitSomething;
if (dangers[0])
{
requiredInterest = Vector3.zero;
}
interests[0] = requiredInterest;
//Check for right side
hitSomething = Physics.SphereCastNonAlloc(origin + transform.right * sideSensingWidth / 2.0f,
sensorRadius,
transform.right,
obstacleHits,
sideSensingDistance,
obstacleMask.value & ~roadMask.value) > 0;
avoidSteerPercent = (hitSomething) ? 1.0f - obstacleHits[0].distance / sideSensingDistance : 0.0f;
//Debug.Log("Avoid Steer Percent from right: " + avoidSteerPercent);
rightAvoidSteerPercent = avoidSteerPercent;
steerPercentDifference = rightAvoidSteerPercent - leftAvoidSteerPercent;
//Debug.Log("Difference in avoid steer percent: " + steerPercentDifference);
if(allowSidewaysRepelForce && carManager.ForwardNormalizedSpeed > 0.3f && Mathf.Abs(steerPercentDifference) >= 0.85f)
{
carManager.Repel(-transform.right * sidewaysRepelForce * steerPercentDifference);
}
requiredInterest = transform.right;
dangers[length - 1] = hitSomething;
if (dangers[length - 1])
{
requiredInterest = Vector3.zero;
}
interests[length - 1] = requiredInterest;
//Now, check between left and right sides.
origin = ForwardCheckPos;
for (int i = 1; i < length - 1; i++)
{
if(i == middleRayIndex && (forwardSensorCount + 2) % 2 == 0)
{
interests[i] = Vector3.zero;
continue;
}
angle = Mathf.Lerp(-forwardSensingHalfAngle, forwardSensingHalfAngle, (float)i / (float)forwardSensorCount);
checkDirection = Quaternion.AngleAxis(angle, transform.up) * transform.forward;
hitSomething = Physics.SphereCastNonAlloc(origin,
sensorRadius,
checkDirection,
obstacleHits,
forwardSensingDistance,
obstacleMask.value & ~roadMask.value) > 0;
requiredInterest = checkDirection;
dangers[i] = hitSomething;
if (dangers[i])
{
requiredInterest = Vector3.zero;
}
interests[i] = requiredInterest;
}
requiredAvoidSteerInput = 0.0f;
Vector3 total = Vector3.zero;
for(int i = 0; i < length; i++)
{
total += interests[i];
}
total.Normalize();

public Vector3 GetRoutePosition(float dist)
{
int point = 0;
dist = Mathf.Repeat(dist, trackLength);
while (distances[point] < dist)
{
++point;
}
// get nearest two points, ensuring points wrap-around start & end of circuit
p1n = ((point - 1) + numPoints) % numPoints;
p2n = point;
// found point numbers, now find interpolation value between the two middle points
interpolation = Mathf.InverseLerp(distances[p1n], distances[p2n], dist);
if (smoothPath)
{
// smooth catmull-rom calculation between the two relevant points
// get indices for the surrounding 2 points, because
// four points are required by the catmull-rom function
p0n = ((point - 2) + numPoints) % numPoints;
p3n = (point + 1) % numPoints;
// 2nd point may have been the 'last' point - a dupe of the first,
// (to give a value of max track distance instead of zero)
// but now it must be wrapped back to zero if that was the case.
p2n = p2n % numPoints;
P0 = points[p0n];
P1 = points[p1n];
P2 = points[p2n];
P3 = points[p3n];
return CatmullRom(P0, P1, P2, P3, interpolation);
}
// simple linear lerp between the two points:
p1n = ((point - 1) + numPoints) % numPoints;
p2n = point;
return Vector3.Lerp(points[p1n], points[p2n], interpolation);
}
private Vector3 CatmullRom(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float i)
{
// You can google it to see what is this.
return 0.5f *
((2 * p1) + (-p0 + p2) * i + (2 * p0 - 5 * p1 + 4 * p2 - p3) * i * i +
(-p0 + 3 * p1 - 3 * p2 + p3) * i * i * i);
}
public bool IsTooCurvy(float checkAngle, int lookaheadDistance)
{
if(progressStyle == ProgressStyle.SmoothAlongRoute &&
lookAhead.Length != smoothLookAheadDistance)
{
lookAhead = new TrackManager.RoutePoint[smoothLookAheadDistance];
}
float halfCheckAngle = checkAngle / 2.0f;
float angle = 0.0f;
Vector3 previous = Vector3.zero;
Vector3 current = Vector3.zero;
Vector3 next = Vector3.zero;
Vector3 direction1 = Vector3.zero;
Vector3 direction2 = Vector3.zero;
if(progressStyle == ProgressStyle.SmoothAlongRoute)
{
for (int i = 0; i < smoothLookAheadDistance && i < lookaheadDistance; i++)
{
lookAhead[i] = trackManager.GetRoutePoint(progressDistance +
i * lookAheadForTargetOffset +
i * lookAheadForTargetFactor * speed);
}
for (int i = 1; i < smoothLookAheadDistance && i < lookaheadDistance - 1; i++)
{
previous = lookAhead[i - 1].position;
current = lookAhead[i].position;
next = lookAhead[i + 1].position;
direction1 = (current - previous).normalized;
direction2 = (next - current).normalized;
angle = Vector3.SignedAngle(direction1, direction2, Vector3.up);
if (angle >= -halfCheckAngle && angle <= halfCheckAngle)
{
continue;
}
else
{
//Debug.Log("Angle: " + angle);
return true;
}
}
}
else if(progressStyle == ProgressStyle.PointToPoint)
{
int length = trackManager.WaypointsList.Waypoints.Length;
for(int i = 1; i < pointLookAheadAmount && i < lookaheadDistance - 1; i++)
{
previous = trackManager.WaypointsList.
Waypoints[(progressWaypoint + i - 1) % length].Position;
current = trackManager.WaypointsList.
Waypoints[(progressWaypoint + i) % length].Position;
next = trackManager.WaypointsList.
Waypoints[(progressWaypoint + i + 1) % length].Position;
direction1 = (current - previous).normalized;
direction2 = (next - current).normalized;
angle = Vector3.SignedAngle(direction1, direction2, Vector3.up);
if (!(angle >= -halfCheckAngle && angle <= halfCheckAngle))
{
return true;
}
}
}
return false;
}
public float CalculateSteeringForce()
{
if (target == null)
{
Debug.LogError("Target is null!");
return 0.0f;
}
// Calculate the direction from the agent position to the next waypoint
Vector3 desiredDirection = target.position - transform.position;
desiredDirection.y = 0.0f;
desiredDirection = desiredDirection.normalized;
float angle = Vector3.SignedAngle(transform.forward, desiredDirection, Vector3.up);
previousSteeringForce = currentSteeringForce;
//Calculate steering force based on angle between AI's forward and desired direction and turn curve.
currentSteeringForce = angle/carManager.MaxSteeringAngle;
if(currentAccelerationForce < 0.0f)
{
currentSteeringForce *= -1.0f;
}
//If angle lies between under target, then zero it.
if (angle >= -underTargetAngle/2f && angle <= underTargetAngle/2f && currentAccelerationForce >= 0.0f)
{
currentSteeringForce = 0.0f;
}
if (allowObstacleSteering && currentAccelerationForce >= 0.0f)
{
currentSteeringForce += CalculateObstacleAvoidanceSteering();
}
//Debug.Log("Current steer: " + currentSteeringForce);
currentSteeringForce *= turnCurve.Evaluate(carManager.NormalizedSpeed);
currentSteeringForce = Mathf.Clamp(currentSteeringForce, -1.0f, 1.0f);
return currentSteeringForce;
}
public float CalculateHandbrakeForce()
{
if (target == null)
{
Debug.LogError("Target is null!");
return 0.0f;
}
currentHandbrakeForce = 0.0f;
if ((forwardDangersCount >= minDangerCheckCount && carManager.NormalizedSpeed >= 0.1f) || (carManager.NormalizedSpeed >= cornerNormalizedSpeed && wayPtProgress.IsTooCurvy(cornerCheckAngle, brakeLookAheadDistance)))
{
currentHandbrakeForce = brakePercent;
}
//If some braking is applied, then change drift parameters.
if (currentHandbrakeForce > 0.0f)
{
carManager.MaxCarGrip = driftGrip;
carManager.GripSpeedFactor = driftGripFactor;
}
else
{
carManager.MaxCarGrip = maxGrip;
carManager.GripSpeedFactor = gripFactor;
}
return currentHandbrakeForce;
}


#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(TrackManager.WaypointList))]
public class WaypointListPropertyDrawer : PropertyDrawer
{
private TrackManager.WaypointList instance;
private SerializedProperty waypointsProp;
private StyleSheet editorStyleSheet = null;
private Foldout waypointsFoldout = null;
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var root = new VisualElement();
instance = (TrackManager.WaypointList)property.boxedValue;
var waypointsProp = property.FindPropertyRelative("waypoints");
this.waypointsProp = waypointsProp;
var waypointsFoldout = new Foldout();
waypointsFoldout.name = "Waypoints Foldout";
this.waypointsFoldout = waypointsFoldout;
if(editorStyleSheet == null)
{
editorStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/UI/EditorStuff/CommonEditorStylesheet.uss");
}
waypointsFoldout.RegisterCallback<ChangeEvent<bool>>(FoldoutChanged);
waypointsFoldout.Bind(waypointsProp.serializedObject);
root.Add(waypointsFoldout);
root.Bind(property.serializedObject);
return root;
}
private void FoldoutChanged(ChangeEvent<bool> evt)
{
this.waypointsFoldout.Clear();
if (!this.waypointsFoldout.value || this.waypointsProp == null || this.waypointsProp.arraySize == 0)
{
this.waypointsFoldout.styleSheets.Remove(editorStyleSheet);
return;
}
this.waypointsFoldout.styleSheets.Add(editorStyleSheet);
int length = this.waypointsProp.arraySize;
for(int i = 0; i < length; i++)
{
TrackManager.Waypoint waypoint = (TrackManager.Waypoint)waypointsProp.GetArrayElementAtIndex(i).boxedValue;
Vector3 position = waypoint.Position;
int waypointIndex = i;
Button wayPtBtn = new Button(()=> FocusOnWaypoint(position, waypointIndex));
wayPtBtn.text = waypoint.Name;
this.waypointsFoldout.Add(wayPtBtn);
}
}
private void FocusOnWaypoint(Vector3 position, int index)
{
SceneView sceneView = SceneView.lastActiveSceneView;
sceneView.pivot = position;
sceneView.size = 5.0f;
}
}
#endif#if UNITY_EDITOR
[CustomEditor(typeof(TrackManager))]
public class TrackManagerEditor : Editor
{
private TrackManager trackManager;
private SerializedProperty trackProp;
private SerializedProperty roadSamplesProp;
private SerializedProperty smoothPathProp;
private SerializedProperty showPositionHandlesProp;
private SerializedProperty showWaypointPositionsProp;
private SerializedProperty waypointListProp;
private SerializedProperty waypointsProp;
private SceneView sceneView;
private float dashLength = 2.0f;
private void OnEnable()
{
trackManager = (TrackManager)target;
trackProp = serializedObject.FindProperty("track");
roadSamplesProp = serializedObject.FindProperty("roadSamples");
smoothPathProp = serializedObject.FindProperty("smoothPath");
showPositionHandlesProp = serializedObject.FindProperty("showPositionHandles");
showWaypointPositionsProp = serializedObject.FindProperty("showWaypointPositions");
waypointListProp = serializedObject.FindProperty("waypointList");
waypointsProp = waypointListProp.FindPropertyRelative("waypoints");
sceneView = SceneView.lastActiveSceneView;
}
public override VisualElement CreateInspectorGUI()
{
VisualElement root = new VisualElement();
if(waypointListProp == null)
{
Debug.LogError("Waypoint List Property is null");
}
PropertyField trackField = new PropertyField(trackProp, "Track");
PropertyField roadSamplesField = new PropertyField(roadSamplesProp, "Road Samples");
PropertyField smoothPathField = new PropertyField(smoothPathProp, "Smooth Path");
PropertyField showPositionHandlesField = new PropertyField(showPositionHandlesProp, "Show Gizmo");
PropertyField showWaypointPositionsField = new PropertyField(showWaypointPositionsProp, "Show Waypoint Positions");
PropertyField waypointListField = new PropertyField(waypointListProp, "Waypoint List");
waypointListField.label = "Waypoint List";
Label waypointListLabel = new Label("Waypoint List");
float marginLeftValue = waypointListLabel.style.marginLeft.value.value;
waypointListLabel.style.marginLeft = marginLeftValue + 4;
Button generateWaypointsButton = new Button(trackManager.GenerateWaypoints);
generateWaypointsButton.text = "Generate Waypoints";
root.Add(trackField);
root.Add(roadSamplesField);
root.Add(smoothPathField);
root.Add(showPositionHandlesField);
root.Add(showWaypointPositionsField);
root.Add(waypointListField);
root.Add(generateWaypointsButton);
return root;
}
private void OnSceneGUI()
{
var waypoints = trackManager.WaypointsList.Waypoints;
if(waypoints == null || waypoints.Length == 0)
{
Debug.LogError("No waypoints. Spline container reference is probably null or waypoints length is 0.");
return;
}
var trackLength = trackManager.TrackLength;
for (int i = 0; i < waypoints.Length; i++)
{
var currentWayPt = waypoints[i];
Vector3 position = currentWayPt.Position;
// Get distance from scene camera to the object
float sqrDistance = Vector3.SqrMagnitude(sceneView.camera.transform.position - position);
float minSqrDistance = CustomEditorConstant.MinSceneGUIRenderDistance * CustomEditorConstant.MinSceneGUIRenderDistance;
if (sqrDistance > minSqrDistance)
{
continue;
}
if (trackManager.ShowPositionHandles)
{
Handles.color = Color.yellow;
Vector3 up = currentWayPt.UpDir;
Vector3 forward = currentWayPt.ForwardDir;
// Draw position handle (movable)
EditorGUI.BeginChangeCheck();
Quaternion rotation = Quaternion.LookRotation(forward, up);
position = Handles.PositionHandle(position, rotation);
//Handles.TransformHandle(ref position, ref rotation);
/*
Handles.color = Color.green;
Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(up, forward), 1f, EventType.Repaint);
Handles.color = Color.blue;
Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(forward, up), 1f, EventType.Repaint);
*/
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Move Waypoint");
waypoints[i].Position = position;
//waypoints[i].UpDir = rotation * up;
//waypoints[i].ForwardDir = rotation * forward;
EditorUtility.SetDirty(target);
}
}
if(trackManager.ShowWaypointPositions)
{
Handles.color = Color.blue;
Handles.DrawSolidDisc(currentWayPt.Position, currentWayPt.UpDir, 1.0f);
}
Handles.BeginGUI();
Handles.Label(currentWayPt.Position, currentWayPt.Name, EditorStyles.toolbar);
Handles.EndGUI();
}
Handles.color = Color.white;
Vector3 prev = waypoints[0].Position;
for (int n = 0; n < waypoints.Length; ++n)
{
Vector3 next = waypoints[(n + 1) % waypoints.Length].Position;
Handles.DrawDottedLine(prev, next, dashLength);
prev = next;
}
}
}
#endif