Browse Source

Added traffic system, added first car

Traffic System added from repo: https://github.com/mchrbn/unity-traffic-simulation
manuel.lehe 2 years ago
parent
commit
2305662e7f
32 changed files with 2934 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 8 0
      Assets/Prefabs/Cars.meta
  3. 1354 0
      Assets/Prefabs/Cars/Firetruck.prefab
  4. 7 0
      Assets/Prefabs/Cars/Firetruck.prefab.meta
  5. BIN
      Assets/Scenes/MainScene.unity
  6. 8 0
      Assets/Scripts/TrafficSimulation.meta
  7. BIN
      Assets/Scripts/TrafficSimulation/.DS_Store
  8. 8 0
      Assets/Scripts/TrafficSimulation/Editor.meta
  9. 100 0
      Assets/Scripts/TrafficSimulation/Editor/EditorHelper.cs
  10. 11 0
      Assets/Scripts/TrafficSimulation/Editor/EditorHelper.cs.meta
  11. 43 0
      Assets/Scripts/TrafficSimulation/Editor/IntersectionEditor.cs
  12. 11 0
      Assets/Scripts/TrafficSimulation/Editor/IntersectionEditor.cs.meta
  13. 264 0
      Assets/Scripts/TrafficSimulation/Editor/TrafficEditor.cs
  14. 11 0
      Assets/Scripts/TrafficSimulation/Editor/TrafficEditor.cs.meta
  15. 100 0
      Assets/Scripts/TrafficSimulation/Editor/TrafficEditorInspector.cs
  16. 11 0
      Assets/Scripts/TrafficSimulation/Editor/TrafficEditorInspector.cs.meta
  17. 101 0
      Assets/Scripts/TrafficSimulation/Editor/TrafficSystemGizmos.cs
  18. 11 0
      Assets/Scripts/TrafficSimulation/Editor/TrafficSystemGizmos.cs.meta
  19. 40 0
      Assets/Scripts/TrafficSimulation/Editor/VehicleEditor.cs
  20. 11 0
      Assets/Scripts/TrafficSimulation/Editor/VehicleEditor.cs.meta
  21. 193 0
      Assets/Scripts/TrafficSimulation/Intersection.cs
  22. 11 0
      Assets/Scripts/TrafficSimulation/Intersection.cs.meta
  23. 29 0
      Assets/Scripts/TrafficSimulation/Segment.cs
  24. 11 0
      Assets/Scripts/TrafficSimulation/Segment.cs.meta
  25. 50 0
      Assets/Scripts/TrafficSimulation/TrafficSystem.cs
  26. 11 0
      Assets/Scripts/TrafficSimulation/TrafficSystem.cs.meta
  27. 324 0
      Assets/Scripts/TrafficSimulation/VehicleAI.cs
  28. 11 0
      Assets/Scripts/TrafficSimulation/VehicleAI.cs.meta
  29. 32 0
      Assets/Scripts/TrafficSimulation/Waypoint.cs
  30. 11 0
      Assets/Scripts/TrafficSimulation/Waypoint.cs.meta
  31. 140 0
      Assets/Scripts/TrafficSimulation/WheelDrive.cs
  32. 11 0
      Assets/Scripts/TrafficSimulation/WheelDrive.cs.meta

+ 1 - 0
.gitignore

@@ -62,3 +62,4 @@ crashlytics-build.properties
 /Assets/Plotting/plots/
 /Assets/Logs/
 /Assets/SuperCombiner/
+*.DS_Store

+ 8 - 0
Assets/Prefabs/Cars.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 26680720fdf714ffa92ac5ca892538e7
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1354 - 0
Assets/Prefabs/Cars/Firetruck.prefab

@@ -0,0 +1,1354 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1137637412972746
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4304418085386390}
+  - component: {fileID: 33171348892035408}
+  - component: {fileID: 23642535698496922}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Hose_Nozzle
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4304418085386390
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1137637412972746}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.020241335, y: 1.9401231, z: 3.3850105}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1515087734278422522}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33171348892035408
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1137637412972746}
+  m_Mesh: {fileID: 4300010, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23642535698496922
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1137637412972746}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1209555985277952
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4956868172205330}
+  - component: {fileID: 33147891530052890}
+  - component: {fileID: 23453952507279772}
+  - component: {fileID: 7741969206963712706}
+  - component: {fileID: 573572051731504426}
+  - component: {fileID: 3880824957832167989}
+  - component: {fileID: 8122268525373113365}
+  m_Layer: 15
+  m_Name: Firetruck
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4956868172205330
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  m_LocalRotation: {x: 0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 1515087734278422522}
+  - {fileID: 8921640799261735204}
+  - {fileID: 1383371783137187048}
+  - {fileID: 5502960979692394408}
+  m_Father: {fileID: 0}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33147891530052890
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  m_Mesh: {fileID: 4300000, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23453952507279772
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!65 &7741969206963712706
+BoxCollider:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  m_Material: {fileID: 0}
+  m_IsTrigger: 0
+  m_Enabled: 1
+  serializedVersion: 2
+  m_Size: {x: 3.1101341, y: 2.839326, z: 8.822845}
+  m_Center: {x: 0, y: 1.5879507, z: -0.19174194}
+--- !u!54 &573572051731504426
+Rigidbody:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  serializedVersion: 2
+  m_Mass: 1
+  m_Drag: 0
+  m_AngularDrag: 0.05
+  m_UseGravity: 1
+  m_IsKinematic: 0
+  m_Interpolate: 0
+  m_Constraints: 0
+  m_CollisionDetection: 0
+--- !u!114 &3880824957832167989
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: cde2d72bdb21e8542bfb0a4516cc158b, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  trafficSystem: {fileID: 0}
+  waypointThresh: 6
+  raycastAnchor: {fileID: 5502960979692394408}
+  raycastLength: 5
+  raySpacing: 2
+  raysNumber: 6
+  emergencyBrakeThresh: 2
+  slowDownThresh: 4
+  vehicleStatus: 0
+--- !u!114 &8122268525373113365
+MonoBehaviour:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1209555985277952}
+  m_Enabled: 1
+  m_EditorHideFlags: 0
+  m_Script: {fileID: 11500000, guid: 5021f9004f348d74abb4f0ae3ed02375, type: 3}
+  m_Name: 
+  m_EditorClassIdentifier: 
+  downForce: 100
+  maxAngle: 30
+  steeringLerp: 5
+  steeringSpeedMax: 20
+  maxTorque: 300
+  brakeTorque: 30000
+  unitType: 0
+  minSpeed: 5
+  maxSpeed: 50
+  leftWheelShape: {fileID: 1538023519652988}
+  rightWheelShape: {fileID: 1794930348095846}
+  animateWheels: 1
+  driveType: 0
+--- !u!1 &1271201421836116
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4698808947176074}
+  - component: {fileID: 33366114234034482}
+  - component: {fileID: 23384025361977160}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_rr
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4698808947176074
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1271201421836116}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.9739195, y: 0.5558693, z: -2.3083627}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1383371783137187048}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33366114234034482
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1271201421836116}
+  m_Mesh: {fileID: 4300014, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23384025361977160
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1271201421836116}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1412869493330810
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4578274517413190}
+  - component: {fileID: 33449996988345642}
+  - component: {fileID: 23389099305021818}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_SteeringW
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4578274517413190
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1412869493330810}
+  m_LocalRotation: {x: 0.28342983, y: -0, z: -0, w: 0.958993}
+  m_LocalPosition: {x: -0.7483696, y: 0.8516722, z: 2.6746564}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1515087734278422522}
+  m_RootOrder: 5
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33449996988345642
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1412869493330810}
+  m_Mesh: {fileID: 4300002, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23389099305021818
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1412869493330810}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1538023519652988
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4533898910731788}
+  - component: {fileID: 33253731965986630}
+  - component: {fileID: 23875124302485972}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_fl
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4533898910731788
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1538023519652988}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.9019006, y: 0.5558693, z: 2.1506045}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1383371783137187048}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33253731965986630
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1538023519652988}
+  m_Mesh: {fileID: 4300018, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23875124302485972
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1538023519652988}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1583297495521082
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4565016775278500}
+  - component: {fileID: 33883398134778410}
+  - component: {fileID: 23459712508780754}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Ladder_Base
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4565016775278500
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1583297495521082}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.09087372, y: 1.5927362, z: -3.5492158}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1515087734278422522}
+  m_RootOrder: 4
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33883398134778410
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1583297495521082}
+  m_Mesh: {fileID: 4300004, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23459712508780754
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1583297495521082}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1635495230986812
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4867696658964076}
+  - component: {fileID: 33747982441695700}
+  - component: {fileID: 23879312999245584}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Glass
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4867696658964076
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1635495230986812}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.09087372, y: -0.895406, z: -0.5307708}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1515087734278422522}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33747982441695700
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1635495230986812}
+  m_Mesh: {fileID: 4300012, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23879312999245584
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1635495230986812}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28d911d60eed0d64fb122f25adeed646, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1790701596022368
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4369576596880992}
+  - component: {fileID: 33758580602473612}
+  - component: {fileID: 23726099992420758}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_rl
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4369576596880992
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1790701596022368}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.9739195, y: 0.5558693, z: -2.3083627}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1383371783137187048}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33758580602473612
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1790701596022368}
+  m_Mesh: {fileID: 4300020, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23726099992420758
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1790701596022368}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1794930348095846
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4862647682717530}
+  - component: {fileID: 33628295937981854}
+  - component: {fileID: 23230941429967496}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_fr
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4862647682717530
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1794930348095846}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.91488904, y: 0.5558693, z: 2.1506045}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1383371783137187048}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33628295937981854
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1794930348095846}
+  m_Mesh: {fileID: 4300016, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23230941429967496
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1794930348095846}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1924865590489050
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4609109924593560}
+  - component: {fileID: 33612611162145918}
+  - component: {fileID: 23238592803640194}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Ladder_01
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4609109924593560
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1924865590489050}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.09087372, y: 1.9661655, z: -3.6044643}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1515087734278422522}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33612611162145918
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1924865590489050}
+  m_Mesh: {fileID: 4300006, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23238592803640194
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1924865590489050}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &1986284464626976
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 4666690271976180}
+  - component: {fileID: 33095195790982066}
+  - component: {fileID: 23313674175311264}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Ladder_02
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &4666690271976180
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1986284464626976}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.09087372, y: 2.4027913, z: 0.79428625}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 1515087734278422522}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!33 &33095195790982066
+MeshFilter:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1986284464626976}
+  m_Mesh: {fileID: 4300008, guid: b951ce64c26326341a411f33b1c66036, type: 3}
+--- !u!23 &23313674175311264
+MeshRenderer:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1986284464626976}
+  m_Enabled: 1
+  m_CastShadows: 1
+  m_ReceiveShadows: 1
+  m_DynamicOccludee: 1
+  m_MotionVectors: 1
+  m_LightProbeUsage: 1
+  m_ReflectionProbeUsage: 1
+  m_RayTracingMode: 2
+  m_RayTraceProcedural: 0
+  m_RenderingLayerMask: 1
+  m_RendererPriority: 0
+  m_Materials:
+  - {fileID: 2100000, guid: 28646867ddcf90a4989276acd313ea43, type: 2}
+  m_StaticBatchInfo:
+    firstSubMesh: 0
+    subMeshCount: 0
+  m_StaticBatchRoot: {fileID: 0}
+  m_ProbeAnchor: {fileID: 0}
+  m_LightProbeVolumeOverride: {fileID: 0}
+  m_ScaleInLightmap: 1
+  m_ReceiveGI: 1
+  m_PreserveUVs: 0
+  m_IgnoreNormalsForChartDetection: 0
+  m_ImportantGI: 0
+  m_StitchLightmapSeams: 0
+  m_SelectedEditorRenderState: 3
+  m_MinimumChartSize: 4
+  m_AutoUVMaxDistance: 0.5
+  m_AutoUVMaxAngle: 89
+  m_LightmapParameters: {fileID: 0}
+  m_SortingLayerID: 0
+  m_SortingLayer: 0
+  m_SortingOrder: 0
+  m_AdditionalVertexStreams: {fileID: 0}
+--- !u!1 &791813691055292160
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 8921640799261735204}
+  m_Layer: 15
+  m_Name: Wheels
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &8921640799261735204
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 791813691055292160}
+  m_LocalRotation: {x: -0, y: 0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.09087372, y: 0.895406, z: 0.5307708}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 2507283545109379050}
+  - {fileID: 3137285774495685116}
+  - {fileID: 1509034136096054204}
+  - {fileID: 250131484148058690}
+  m_Father: {fileID: 4956868172205330}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1092994167545690430
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1515087734278422522}
+  m_Layer: 15
+  m_Name: Body
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &1515087734278422522
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 1092994167545690430}
+  m_LocalRotation: {x: -0, y: 0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.09087372, y: 0.895406, z: 0.5307708}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 4867696658964076}
+  - {fileID: 4304418085386390}
+  - {fileID: 4609109924593560}
+  - {fileID: 4666690271976180}
+  - {fileID: 4565016775278500}
+  - {fileID: 4578274517413190}
+  m_Father: {fileID: 4956868172205330}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &2359335785803332429
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1509034136096054204}
+  - component: {fileID: 3363408649227195304}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_fr
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &1509034136096054204
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 2359335785803332429}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.8240153, y: -0.205, z: 1.6198337}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 8921640799261735204}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!146 &3363408649227195304
+WheelCollider:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 2359335785803332429}
+  m_Center: {x: 0, y: 0, z: 0}
+  m_Radius: 0.5
+  m_SuspensionSpring:
+    spring: 35000
+    damper: 4500
+    targetPosition: 0.5
+  m_SuspensionDistance: 0.3
+  m_ForceAppPointDistance: 0
+  m_Mass: 20
+  m_WheelDampingRate: 0.25
+  m_ForwardFriction:
+    m_ExtremumSlip: 0.4
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.8
+    m_AsymptoteValue: 0.5
+    m_Stiffness: 1
+  m_SidewaysFriction:
+    m_ExtremumSlip: 0.2
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.5
+    m_AsymptoteValue: 0.75
+    m_Stiffness: 1
+  m_Enabled: 1
+--- !u!1 &2738159477874962642
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 3137285774495685116}
+  - component: {fileID: 3303691711778640440}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_rl
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &3137285774495685116
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 2738159477874962642}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -1.0647932, y: -0.227, z: -2.8391335}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 8921640799261735204}
+  m_RootOrder: 1
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!146 &3303691711778640440
+WheelCollider:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 2738159477874962642}
+  m_Center: {x: 0, y: 0, z: 0}
+  m_Radius: 0.5
+  m_SuspensionSpring:
+    spring: 35000
+    damper: 4500
+    targetPosition: 0.5
+  m_SuspensionDistance: 0.3
+  m_ForceAppPointDistance: 0
+  m_Mass: 20
+  m_WheelDampingRate: 0.25
+  m_ForwardFriction:
+    m_ExtremumSlip: 0.4
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.8
+    m_AsymptoteValue: 0.5
+    m_Stiffness: 1
+  m_SidewaysFriction:
+    m_ExtremumSlip: 0.2
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.5
+    m_AsymptoteValue: 0.75
+    m_Stiffness: 1
+  m_Enabled: 1
+--- !u!1 &5859880725207878884
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 2507283545109379050}
+  - component: {fileID: 9203406769390748061}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_rr
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &2507283545109379050
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5859880725207878884}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: 0.8830458, y: -0.233, z: -2.8391335}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 8921640799261735204}
+  m_RootOrder: 0
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!146 &9203406769390748061
+WheelCollider:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 5859880725207878884}
+  m_Center: {x: 0, y: 0, z: 0}
+  m_Radius: 0.5
+  m_SuspensionSpring:
+    spring: 35000
+    damper: 4500
+    targetPosition: 0.5
+  m_SuspensionDistance: 0.3
+  m_ForceAppPointDistance: 0
+  m_Mass: 20
+  m_WheelDampingRate: 0.25
+  m_ForwardFriction:
+    m_ExtremumSlip: 0.4
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.8
+    m_AsymptoteValue: 0.5
+    m_Stiffness: 1
+  m_SidewaysFriction:
+    m_ExtremumSlip: 0.2
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.5
+    m_AsymptoteValue: 0.75
+    m_Stiffness: 1
+  m_Enabled: 1
+--- !u!1 &6707237091725674463
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 250131484148058690}
+  - component: {fileID: 3048764555690869702}
+  m_Layer: 15
+  m_Name: SM_Veh_Firetruck_Wheel_fl
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &250131484148058690
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6707237091725674463}
+  m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+  m_LocalPosition: {x: -0.9927743, y: -0.234, z: 1.6198337}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 8921640799261735204}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!146 &3048764555690869702
+WheelCollider:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6707237091725674463}
+  m_Center: {x: 0, y: 0, z: 0}
+  m_Radius: 0.5
+  m_SuspensionSpring:
+    spring: 35000
+    damper: 4500
+    targetPosition: 0.5
+  m_SuspensionDistance: 0.3
+  m_ForceAppPointDistance: 0
+  m_Mass: 20
+  m_WheelDampingRate: 0.25
+  m_ForwardFriction:
+    m_ExtremumSlip: 0.4
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.8
+    m_AsymptoteValue: 0.5
+    m_Stiffness: 1
+  m_SidewaysFriction:
+    m_ExtremumSlip: 0.2
+    m_ExtremumValue: 1
+    m_AsymptoteSlip: 0.5
+    m_AsymptoteValue: 0.75
+    m_Stiffness: 1
+  m_Enabled: 1
+--- !u!1 &6857769584919289824
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 5502960979692394408}
+  m_Layer: 15
+  m_Name: Raycast Anchor
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &5502960979692394408
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 6857769584919289824}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 1.27, z: 3.97}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children: []
+  m_Father: {fileID: 4956868172205330}
+  m_RootOrder: 3
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &8954000292960718646
+GameObject:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  serializedVersion: 6
+  m_Component:
+  - component: {fileID: 1383371783137187048}
+  m_Layer: 15
+  m_Name: WheelsVisual
+  m_TagString: Untagged
+  m_Icon: {fileID: 0}
+  m_NavMeshLayer: 0
+  m_StaticEditorFlags: 0
+  m_IsActive: 1
+--- !u!4 &1383371783137187048
+Transform:
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_GameObject: {fileID: 8954000292960718646}
+  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+  m_LocalPosition: {x: 0, y: 0, z: 0}
+  m_LocalScale: {x: 1, y: 1, z: 1}
+  m_Children:
+  - {fileID: 4533898910731788}
+  - {fileID: 4862647682717530}
+  - {fileID: 4369576596880992}
+  - {fileID: 4698808947176074}
+  m_Father: {fileID: 4956868172205330}
+  m_RootOrder: 2
+  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

+ 7 - 0
Assets/Prefabs/Cars/Firetruck.prefab.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: bf3c70c8d4c5c487ea987d50a1401756
+PrefabImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Scenes/MainScene.unity


+ 8 - 0
Assets/Scripts/TrafficSimulation.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 297823880e154492b83802c9551a6897
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/Scripts/TrafficSimulation/.DS_Store


+ 8 - 0
Assets/Scripts/TrafficSimulation/Editor.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e96330aa770f3d947afa5520028d6ad2
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 100 - 0
Assets/Scripts/TrafficSimulation/Editor/EditorHelper.cs

@@ -0,0 +1,100 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using UnityEditor;
+using UnityEngine;
+
+namespace TrafficSimulation {
+    public static class EditorHelper {
+        public static void SetUndoGroup(string label) {
+            //Create new Undo Group to collect all changes in one Undo
+            Undo.SetCurrentGroupName(label);
+        }
+        
+        public static void BeginUndoGroup(string undoName, TrafficSystem trafficSystem) {
+            //Create new Undo Group to collect all changes in one Undo
+            Undo.SetCurrentGroupName(undoName);
+
+            //Register all TrafficSystem changes after this (string not relevant here)
+            Undo.RegisterFullObjectHierarchyUndo(trafficSystem.gameObject, undoName);
+        }
+
+        public static GameObject CreateGameObject(string name, Transform parent = null) {
+            GameObject newGameObject = new GameObject(name);
+
+            //Register changes for Undo (string not relevant here)
+            Undo.RegisterCreatedObjectUndo(newGameObject, "Spawn new GameObject");
+            Undo.SetTransformParent(newGameObject.transform, parent, "Set parent");
+
+            return newGameObject;
+        }
+
+        public static T AddComponent<T>(GameObject target) where T : Component {
+            return Undo.AddComponent<T>(target);
+        }
+        
+        //Determines if a ray hits a sphere
+        public static bool SphereHit(Vector3 center, float radius, Ray r) {
+            Vector3 oc = r.origin - center;
+            float a = Vector3.Dot(r.direction, r.direction);
+            float b = 2f * Vector3.Dot(oc, r.direction);
+            float c = Vector3.Dot(oc, oc) - radius * radius;
+            float discriminant = b * b - 4f * a * c;
+
+            if (discriminant < 0f) {
+                return false;
+            }
+
+            float sqrt = Mathf.Sqrt(discriminant);
+
+            return -b - sqrt > 0f || -b + sqrt > 0f;
+        }
+
+        //From S_Darkwell: https://forum.unity.com/threads/adding-layer-by-script.41970/
+        public static void CreateLayer(string name){
+            if (string.IsNullOrEmpty(name))
+                throw new System.ArgumentNullException("name", "New layer name string is either null or empty.");
+
+            var tagManager = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/TagManager.asset")[0]);
+            var layerProps = tagManager.FindProperty("layers");
+            var propCount = layerProps.arraySize;
+
+            SerializedProperty firstEmptyProp = null;
+
+            for (var i = 0; i < propCount; i++)
+            {
+                var layerProp = layerProps.GetArrayElementAtIndex(i);
+
+                var stringValue = layerProp.stringValue;
+
+                if (stringValue == name) return;
+
+                if (i < 8 || stringValue != string.Empty) continue;
+
+                if (firstEmptyProp == null)
+                    firstEmptyProp = layerProp;
+            }
+
+            if (firstEmptyProp == null)
+            {
+                UnityEngine.Debug.LogError("Maximum limit of " + propCount + " layers exceeded. Layer \"" + name + "\" not created.");
+                return;
+            }
+
+            firstEmptyProp.stringValue = name;
+            tagManager.ApplyModifiedProperties();
+        }
+
+        //From SkywardRoy: https://forum.unity.com/threads/change-gameobject-layer-at-run-time-wont-apply-to-child.10091/
+        public static void SetLayer (this GameObject gameObject, int layer, bool includeChildren = false) {
+            if (!includeChildren) {
+                gameObject.layer = layer;
+                return;
+            }
+        
+            foreach (var child in gameObject.GetComponentsInChildren(typeof(Transform), true)) {
+                child.gameObject.layer = layer;
+            }
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Editor/EditorHelper.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cb606a2d3cf799e4c9a2183355c940a7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 43 - 0
Assets/Scripts/TrafficSimulation/Editor/IntersectionEditor.cs

@@ -0,0 +1,43 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using UnityEngine;
+using UnityEditor;
+
+namespace TrafficSimulation {
+    [CustomEditor(typeof(Intersection))]
+    public class IntersectionEditor : Editor
+    {
+        private Intersection intersection;
+
+        void OnEnable(){
+            intersection = target as Intersection;
+        }
+
+        public override void OnInspectorGUI(){
+            intersection.intersectionType = (IntersectionType) EditorGUILayout.EnumPopup("Intersection type", intersection.intersectionType);
+
+            EditorGUI.BeginDisabledGroup(intersection.intersectionType != IntersectionType.STOP);
+
+            EditorGUILayout.LabelField("Stop", EditorStyles.boldLabel);
+            SerializedProperty sPrioritySegments = serializedObject.FindProperty("prioritySegments");
+            EditorGUILayout.PropertyField(sPrioritySegments, new GUIContent("Priority Segments"), true);
+            serializedObject.ApplyModifiedProperties();
+
+            EditorGUI.EndDisabledGroup();
+
+            EditorGUI.BeginDisabledGroup(intersection.intersectionType != IntersectionType.TRAFFIC_LIGHT);
+
+            EditorGUILayout.LabelField("Traffic Lights", EditorStyles.boldLabel);
+            intersection.lightsDuration = EditorGUILayout.FloatField("Light Duration (in s.)", intersection.lightsDuration);
+            intersection.orangeLightDuration = EditorGUILayout.FloatField("Orange Light Duration (in s.)", intersection.orangeLightDuration);
+            SerializedProperty sLightsNbr1 = serializedObject.FindProperty("lightsNbr1");
+            SerializedProperty sLightsNbr2 = serializedObject.FindProperty("lightsNbr2");
+            EditorGUILayout.PropertyField(sLightsNbr1, new GUIContent("Lights #1 (first to be red)"), true);
+            EditorGUILayout.PropertyField(sLightsNbr2, new GUIContent("Lights #2"), true);
+            serializedObject.ApplyModifiedProperties();
+            
+            EditorGUI.EndDisabledGroup();
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Editor/IntersectionEditor.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e3426e75dd5e4504d8d6a4a17d6e1df1
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 264 - 0
Assets/Scripts/TrafficSimulation/Editor/TrafficEditor.cs

@@ -0,0 +1,264 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEditor;
+
+namespace TrafficSimulation {
+    [CustomEditor(typeof(TrafficSystem))]
+    public class TrafficEditor : Editor {
+
+        private TrafficSystem wps;
+        
+        //References for moving a waypoint
+        private Vector3 startPosition;
+        private Vector3 lastPoint;
+        private Waypoint lastWaypoint;
+        
+        [MenuItem("Component/Traffic Simulation/Create Traffic Objects")]
+        private static void CreateTraffic(){
+            EditorHelper.SetUndoGroup("Create Traffic Objects");
+            
+            GameObject mainGo = EditorHelper.CreateGameObject("Traffic System");
+            mainGo.transform.position = Vector3.zero;
+            EditorHelper.AddComponent<TrafficSystem>(mainGo);
+
+            GameObject segmentsGo = EditorHelper.CreateGameObject("Segments", mainGo.transform);
+            segmentsGo.transform.position = Vector3.zero;
+
+            GameObject intersectionsGo = EditorHelper.CreateGameObject("Intersections", mainGo.transform);
+            intersectionsGo.transform.position = Vector3.zero;
+            
+            //Close Undo Operation
+            Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
+        }
+
+        void OnEnable(){
+            wps = target as TrafficSystem;
+        }
+
+        private void OnSceneGUI() {
+            Debug.Log("Getting SceneGUI");
+            Event e = Event.current;
+            if (e == null) return;
+
+            Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition);
+
+            if (Physics.Raycast(ray, out RaycastHit hit) && e.type == EventType.MouseDown && e.button == 0) {
+                //Add a new waypoint on mouseclick + shift
+                if (e.shift) {
+                    if (wps.curSegment == null) {
+                        return;
+                    }
+
+                    EditorHelper.BeginUndoGroup("Add Waypoint", wps);
+                    AddWaypoint(hit.point);
+
+                    //Close Undo Group
+                    Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
+                }
+
+                //Create a segment + add a new waypoint on mouseclick + ctrl
+                else if (e.control) {
+                    EditorHelper.BeginUndoGroup("Add Segment", wps);
+                    AddSegment(hit.point);
+                    AddWaypoint(hit.point);
+
+                    //Close Undo Group
+                    Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
+                }
+
+                //Create an intersection type
+                else if (e.alt) {
+                    EditorHelper.BeginUndoGroup("Add Intersection", wps);
+                    AddIntersection(hit.point);
+
+                    //Close Undo Group
+                    Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
+                }
+            }
+
+            //Set waypoint system as the selected gameobject in hierarchy
+            Selection.activeGameObject = wps.gameObject;
+
+            //Handle the selected waypoint
+            if (lastWaypoint != null) {
+                //Uses a endless plain for the ray to hit
+                Plane plane = new Plane(Vector3.up.normalized, lastWaypoint.GetVisualPos());
+                plane.Raycast(ray, out float dst);
+                Vector3 hitPoint = ray.GetPoint(dst);
+
+                //Reset lastPoint if the mouse button is pressed down the first time
+                if (e.type == EventType.MouseDown && e.button == 0) {
+                    lastPoint = hitPoint;
+                    startPosition = lastWaypoint.transform.position;
+                }
+
+                //Move the selected waypoint
+                if (e.type == EventType.MouseDrag && e.button == 0) {
+                    Vector3 realDPos = new Vector3(hitPoint.x - lastPoint.x, 0, hitPoint.z - lastPoint.z);
+
+                    lastWaypoint.transform.position += realDPos;
+                    lastPoint = hitPoint;
+                }
+
+                //Release the selected waypoint
+                if (e.type == EventType.MouseUp && e.button == 0) {
+                    Vector3 curPos = lastWaypoint.transform.position;
+                    lastWaypoint.transform.position = startPosition;
+                    Undo.RegisterFullObjectHierarchyUndo(lastWaypoint, "Move Waypoint");
+                    lastWaypoint.transform.position = curPos;
+                }
+
+                //Draw a Sphere
+                Handles.SphereHandleCap(0, lastWaypoint.GetVisualPos(), Quaternion.identity, wps.waypointSize * 2f, EventType.Repaint);
+                HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
+                SceneView.RepaintAll();
+            }
+
+            //Set the current hovering waypoint
+            if (lastWaypoint == null) {
+                lastWaypoint = wps.GetAllWaypoints().FirstOrDefault(i => EditorHelper.SphereHit(i.GetVisualPos(), wps.waypointSize, ray));
+            }
+
+            //Update the current segment to the currently interacting one
+            if (lastWaypoint != null && e.type == EventType.MouseDown) {
+                wps.curSegment = lastWaypoint.segment;
+            }
+            
+            //Reset current waypoint
+            else if (lastWaypoint != null && e.type == EventType.MouseMove) {
+                lastWaypoint = null;
+            }
+        }
+
+        public override void OnInspectorGUI() {
+            EditorGUI.BeginChangeCheck();
+
+            //Register an Undo if changes are made after this call
+            Undo.RecordObject(wps, "Traffic Inspector Edit");
+
+            //Draw the Inspector
+            TrafficEditorInspector.DrawInspector(wps, serializedObject, out bool restructureSystem);
+
+            //Rename waypoints if some have been deleted
+            if (restructureSystem) {
+                RestructureSystem();
+            }
+
+            //Repaint the scene if values have been edited
+            if (EditorGUI.EndChangeCheck()) {
+                SceneView.RepaintAll();
+            }
+
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        private void AddWaypoint(Vector3 position) {
+            GameObject go = EditorHelper.CreateGameObject("Waypoint-" + wps.curSegment.waypoints.Count, wps.curSegment.transform);
+            go.transform.position = position;
+
+            Waypoint wp = EditorHelper.AddComponent<Waypoint>(go);
+            wp.Refresh(wps.curSegment.waypoints.Count, wps.curSegment);
+
+            //Record changes to the TrafficSystem (string not relevant here)
+            Undo.RecordObject(wps.curSegment, "");
+            wps.curSegment.waypoints.Add(wp);
+        }
+
+        private void AddSegment(Vector3 position) {
+            int segId = wps.segments.Count;
+            GameObject segGo = EditorHelper.CreateGameObject("Segment-" + segId, wps.transform.GetChild(0).transform);
+            segGo.transform.position = position;
+
+            wps.curSegment = EditorHelper.AddComponent<Segment>(segGo);
+            wps.curSegment.id = segId;
+            wps.curSegment.waypoints = new List<Waypoint>();
+            wps.curSegment.nextSegments = new List<Segment>();
+
+            //Record changes to the TrafficSystem (string not relevant here)
+            Undo.RecordObject(wps, "");
+            wps.segments.Add(wps.curSegment);
+        }
+
+        private void AddIntersection(Vector3 position) {
+            int intId = wps.intersections.Count;
+            GameObject intGo = EditorHelper.CreateGameObject("Intersection-" + intId, wps.transform.GetChild(1).transform);
+            intGo.transform.position = position;
+
+            BoxCollider bc = EditorHelper.AddComponent<BoxCollider>(intGo);
+            bc.isTrigger = true;
+            Intersection intersection = EditorHelper.AddComponent<Intersection>(intGo);
+            intersection.id = intId;
+
+            //Record changes to the TrafficSystem (string not relevant here)
+            Undo.RecordObject(wps, "");
+            wps.intersections.Add(intersection);
+        }
+
+        void RestructureSystem(){
+            //Rename and restructure segments and waypoints
+            List<Segment> nSegments = new List<Segment>();
+            int itSeg = 0;
+            foreach(Transform tS in wps.transform.GetChild(0).transform){
+                Segment segment = tS.GetComponent<Segment>();
+                if(segment != null){
+                    List<Waypoint> nWaypoints = new List<Waypoint>();
+                    segment.id = itSeg;
+                    segment.gameObject.name = "Segment-" + itSeg;
+                    
+                    int itWp = 0;
+                    foreach(Transform tW in segment.gameObject.transform){
+                        Waypoint waypoint = tW.GetComponent<Waypoint>();
+                        if(waypoint != null) {
+                            waypoint.Refresh(itWp, segment);
+                            nWaypoints.Add(waypoint);
+                            itWp++;
+                        }
+                    }
+
+                    segment.waypoints = nWaypoints;
+                    nSegments.Add(segment);
+                    itSeg++;
+                }
+            }
+
+            //Check if next segments still exist
+            foreach(Segment segment in nSegments){
+                List<Segment> nNextSegments = new List<Segment>();
+                foreach(Segment nextSeg in segment.nextSegments){
+                    if(nextSeg != null){
+                        nNextSegments.Add(nextSeg);
+                    }
+                }
+                segment.nextSegments = nNextSegments;
+            }
+            wps.segments = nSegments;
+
+            //Check intersections
+            List<Intersection> nIntersections = new List<Intersection>();
+            int itInter = 0;
+            foreach(Transform tI in wps.transform.GetChild(1).transform){
+                Intersection intersection = tI.GetComponent<Intersection>();
+                if(intersection != null){
+                    intersection.id = itInter;
+                    intersection.gameObject.name = "Intersection-" + itInter;
+                    nIntersections.Add(intersection);
+                    itInter++;
+                }
+            }
+            wps.intersections = nIntersections;
+            
+            //Tell Unity that something changed and the scene has to be saved
+            if (!EditorUtility.IsDirty(target)) {
+                EditorUtility.SetDirty(target);
+            }
+
+            Debug.Log("[Traffic Simulation] Successfully rebuilt the traffic system.");
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Editor/TrafficEditor.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 75945a817795cfe4a8dcae3ae12e44b2
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 100 - 0
Assets/Scripts/TrafficSimulation/Editor/TrafficEditorInspector.cs

@@ -0,0 +1,100 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using System;
+using UnityEditor;
+using UnityEngine;
+
+namespace TrafficSimulation {
+    public static class TrafficEditorInspector {
+        //Whole Inspector layout
+        public static void DrawInspector(TrafficSystem trafficSystem, SerializedObject serializedObject, out bool restructureSystem) {
+            //-- Gizmo settings
+            Header("Gizmo Config");
+            Toggle("Hide Gizmos", ref trafficSystem.hideGuizmos);
+            
+            //Arrow config
+            DrawArrowTypeSelection(trafficSystem);
+            FloatField("Waypoint Size", ref trafficSystem.waypointSize);
+            EditorGUILayout.Space();
+            
+            //-- System config
+            Header("System Config");
+            FloatField("Segment Detection Threshold", ref trafficSystem.segDetectThresh);
+
+            PropertyField("Collision Layers", "collisionLayers", serializedObject);
+            
+            EditorGUILayout.Space();
+
+            //Helper
+            HelpBox("Ctrl + Left Click to create a new segment\nShift + Left Click to create a new waypoint.\nAlt + Left Click to create a new intersection");
+            HelpBox("Reminder: The vehicles will follow the point depending on the sequence you added them. (go to the 1st waypoint added, then to the second, etc.)");
+            EditorGUILayout.Space();
+
+            restructureSystem = Button("Re-Structure Traffic System");
+        }
+
+        //-- Helper to draw the Inspector
+        private static void Label(string label) {
+            EditorGUILayout.LabelField(label);
+        }
+
+        private static void Header(string label) {
+            EditorGUILayout.LabelField(label, EditorStyles.boldLabel);
+        }
+
+        private static void Toggle(string label, ref bool toggle) {
+            toggle = EditorGUILayout.Toggle(label, toggle);
+        }
+
+        private static void IntField(string label, ref int value) {
+            value = EditorGUILayout.IntField(label, value);
+        }
+        
+        private static void IntField(string label, ref int value, int min, int max) {
+            value = Mathf.Clamp(EditorGUILayout.IntField(label, value), min, max);
+        }
+        
+        private static void FloatField(string label, ref float value) {
+            value = EditorGUILayout.FloatField(label, value);
+        }
+
+        private static void PropertyField(string label, string value, SerializedObject serializedObject){
+            SerializedProperty extra = serializedObject.FindProperty(value);
+            EditorGUILayout.PropertyField(extra, new GUIContent(label), true);
+        }
+
+        private static void HelpBox(string content) {
+            EditorGUILayout.HelpBox(content, MessageType.Info);
+        }
+
+        private static bool Button(string label) {
+            return GUILayout.Button(label);
+        }
+        
+        private static void DrawArrowTypeSelection(TrafficSystem trafficSystem) {
+            trafficSystem.arrowDrawType = (ArrowDraw) EditorGUILayout.EnumPopup("Arrow Draw Type", trafficSystem.arrowDrawType);
+            EditorGUI.indentLevel++;
+            
+            switch (trafficSystem.arrowDrawType) {
+                case ArrowDraw.FixedCount:
+                    IntField("Count", ref trafficSystem.arrowCount, 1, int.MaxValue);
+                    break;
+                case ArrowDraw.ByLength:
+                    FloatField("Distance Between Arrows", ref trafficSystem.arrowDistance);
+                    break;
+                case ArrowDraw.Off:
+                    break;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+            
+            if (trafficSystem.arrowDrawType != ArrowDraw.Off) {
+                FloatField("Arrow Size Waypoint", ref trafficSystem.arrowSizeWaypoint);
+                FloatField("Arrow Size Intersection", ref trafficSystem.arrowSizeIntersection);
+            }
+            
+            EditorGUI.indentLevel--;
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Editor/TrafficEditorInspector.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e64108d6725f5eb4d80407a5fcea6d66
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 101 - 0
Assets/Scripts/TrafficSimulation/Editor/TrafficSystemGizmos.cs

@@ -0,0 +1,101 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using System;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+namespace TrafficSimulation {
+    public static class TrafficSystemGizmos {
+        //Custom Gizmo function
+        [DrawGizmo(GizmoType.Selected | GizmoType.NonSelected | GizmoType.Active)]
+        private static void DrawGizmo(TrafficSystem script, GizmoType gizmoType) {
+            //Don't go further if we hide gizmos
+            if (script.hideGuizmos) {
+                return;
+            }
+
+            foreach (Segment segment in script.segments) {
+                //Draw segment names
+                GUIStyle style = new GUIStyle {normal = {textColor = new Color(1, 0, 0)}, fontSize = 15};
+                Handles.Label(segment.transform.position, segment.name, style);
+
+                //Draw waypoint
+                for (int j = 0; j < segment.waypoints.Count; j++) {
+                    //Get current waypoint position
+                    Vector3 p = segment.waypoints[j].GetVisualPos();
+
+                    //Draw sphere, increase color to show the direction
+                    Gizmos.color = new Color(0f, 0f, 1f, (j + 1) / (float) segment.waypoints.Count);
+                    Gizmos.DrawSphere(p, script.waypointSize);
+                    
+                    //Get next waypoint position
+                    Vector3 pNext = Vector3.zero;
+
+                    if (j < segment.waypoints.Count - 1 && segment.waypoints[j + 1] != null) {
+                        pNext = segment.waypoints[j + 1].GetVisualPos();
+                    }
+
+                    if (pNext != Vector3.zero) {
+                        if (segment == script.curSegment) {
+                            Gizmos.color = new Color(1f, .3f, .1f);
+                        } else {
+                            Gizmos.color = new Color(1f, 0f, 0f);
+                        }
+
+                        //Draw connection line of the two waypoints
+                        Gizmos.DrawLine(p, pNext);
+
+                        //Set arrow count based on arrowDrawType
+                        int arrows = GetArrowCount(p, pNext, script);
+
+                        //Draw arrows
+                        for (int i = 1; i < arrows + 1; i++) {
+                            Vector3 point = Vector3.Lerp(p, pNext, (float) i / (arrows + 1));
+                            DrawArrow(point, p - pNext, script.arrowSizeWaypoint);
+                        }
+                    }
+                }
+
+                //Draw line linking segments
+                foreach (Segment nextSegment in segment.nextSegments) {
+                    if (nextSegment != null){
+                        Vector3 p1 = segment.waypoints.Last().GetVisualPos();
+                        Vector3 p2 = nextSegment.waypoints.First().GetVisualPos();
+
+                        Gizmos.color = new Color(1f, 1f, 0f);
+                        Gizmos.DrawLine(p1, p2);
+
+                        if (script.arrowDrawType != ArrowDraw.Off) {
+                            DrawArrow((p1 + p2) / 2f, p1 - p2, script.arrowSizeIntersection);
+                        }
+                    }
+                }
+            }
+        }
+
+        private static void DrawArrow(Vector3 point, Vector3 forward, float size) {
+            forward = forward.normalized * size;
+            Vector3 left = Quaternion.Euler(0, 45, 0) * forward;
+            Vector3 right = Quaternion.Euler(0, -45, 0) * forward;
+
+            Gizmos.DrawLine(point, point + left);
+            Gizmos.DrawLine(point, point + right);
+        }
+
+        private static int GetArrowCount(Vector3 pointA, Vector3 pointB, TrafficSystem script) {
+            switch (script.arrowDrawType) {
+                case ArrowDraw.FixedCount:
+                    return script.arrowCount;
+                case ArrowDraw.ByLength:
+                    //Minimum of one arrow
+                    return Mathf.Max(1, (int) (Vector3.Distance(pointA, pointB) / script.arrowDistance));
+                case ArrowDraw.Off:
+                    return 0;
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Editor/TrafficSystemGizmos.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1b1ee7d23e45e2d43b738d75e11a5f79
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 40 - 0
Assets/Scripts/TrafficSimulation/Editor/VehicleEditor.cs

@@ -0,0 +1,40 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using UnityEngine;
+using UnityEditor;
+
+namespace TrafficSimulation{
+    public class VehicleEditor : Editor
+    {
+        [MenuItem("Component/Traffic Simulation/Setup Vehicle")]
+        private static void SetupVehicle(){
+            EditorHelper.SetUndoGroup("Setup Vehicle");
+
+            GameObject selected = Selection.activeGameObject;
+
+            //Create raycast anchor
+            GameObject anchor = EditorHelper.CreateGameObject("Raycast Anchor", selected.transform);
+
+            //Add AI scripts
+            VehicleAI veAi = EditorHelper.AddComponent<VehicleAI>(selected);
+            WheelDrive wheelDrive = EditorHelper.AddComponent<WheelDrive>(selected);
+
+            TrafficSystem ts = GameObject.FindObjectOfType<TrafficSystem>();
+
+            //Configure the vehicle AI script with created objects
+            anchor.transform.localPosition = Vector3.zero;
+            anchor.transform.localRotation = Quaternion.Euler(Vector3.zero);
+            veAi.raycastAnchor = anchor.transform;
+
+            if(ts != null) veAi.trafficSystem = ts;
+
+            //Create layer AutonomousVehicle if it doesn't exist
+            EditorHelper.CreateLayer("AutonomousVehicle");
+            
+            //Set the tag and layer name
+            selected.tag = "AutonomousVehicle";
+            EditorHelper.SetLayer(selected, LayerMask.NameToLayer("AutonomousVehicle"), true);
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Editor/VehicleEditor.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0aa0f5086b9e3bb4e8056d7e7f462add
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 193 - 0
Assets/Scripts/TrafficSimulation/Intersection.cs

@@ -0,0 +1,193 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace TrafficSimulation{
+    public enum IntersectionType{
+        STOP,
+        TRAFFIC_LIGHT
+    }
+
+    public class Intersection : MonoBehaviour
+    {   
+        public IntersectionType intersectionType;
+        public int id;  
+
+        //For stop only
+        public List<Segment> prioritySegments;
+
+        //For traffic lights only
+        public float lightsDuration = 8;
+        public float orangeLightDuration = 2;
+        public List<Segment> lightsNbr1;
+        public List<Segment> lightsNbr2;
+
+        private List<GameObject> vehiclesQueue;
+        private List<GameObject> vehiclesInIntersection;
+        private TrafficSystem trafficSystem;
+        
+        [HideInInspector] public int currentRedLightsGroup = 1;
+
+        void Start(){
+            vehiclesQueue = new List<GameObject>();
+            vehiclesInIntersection = new List<GameObject>();
+            if(intersectionType == IntersectionType.TRAFFIC_LIGHT)
+                InvokeRepeating("SwitchLights", lightsDuration, lightsDuration);
+        }
+
+        void SwitchLights(){
+
+            if(currentRedLightsGroup == 1) currentRedLightsGroup = 2;
+            else if(currentRedLightsGroup == 2) currentRedLightsGroup = 1;            
+            
+            //Wait few seconds after light transition before making the other car move (= orange light)
+            Invoke("MoveVehiclesQueue", orangeLightDuration);
+        }
+
+        void OnTriggerEnter(Collider _other) {
+            //Check if vehicle is already in the list if yes abort
+            //Also abort if we just started the scene (if vehicles inside colliders at start)
+            if(IsAlreadyInIntersection(_other.gameObject) || Time.timeSinceLevelLoad < .5f) return;
+
+            if(_other.tag == "AutonomousVehicle" && intersectionType == IntersectionType.STOP)
+                TriggerStop(_other.gameObject);
+            else if(_other.tag == "AutonomousVehicle" && intersectionType == IntersectionType.TRAFFIC_LIGHT)
+                TriggerLight(_other.gameObject);
+        }
+
+        void OnTriggerExit(Collider _other) {
+            if(_other.tag == "AutonomousVehicle" && intersectionType == IntersectionType.STOP)
+                ExitStop(_other.gameObject);
+            else if(_other.tag == "AutonomousVehicle" && intersectionType == IntersectionType.TRAFFIC_LIGHT)
+                ExitLight(_other.gameObject);
+        }
+
+        void TriggerStop(GameObject _vehicle){
+            VehicleAI vehicleAI = _vehicle.GetComponent<VehicleAI>();
+            
+            //Depending on the waypoint threshold, the car can be either on the target segment or on the past segment
+            int vehicleSegment = vehicleAI.GetSegmentVehicleIsIn();
+
+            if(!IsPrioritySegment(vehicleSegment)){
+                if(vehiclesQueue.Count > 0 || vehiclesInIntersection.Count > 0){
+                    vehicleAI.vehicleStatus = Status.STOP;
+                    vehiclesQueue.Add(_vehicle);
+                }
+                else{
+                    vehiclesInIntersection.Add(_vehicle);
+                    vehicleAI.vehicleStatus = Status.SLOW_DOWN;
+                }
+            }
+            else{
+                vehicleAI.vehicleStatus = Status.SLOW_DOWN;
+                vehiclesInIntersection.Add(_vehicle);
+            }
+        }
+
+        void ExitStop(GameObject _vehicle){
+
+            _vehicle.GetComponent<VehicleAI>().vehicleStatus = Status.GO;
+            vehiclesInIntersection.Remove(_vehicle);
+            vehiclesQueue.Remove(_vehicle);
+
+            if(vehiclesQueue.Count > 0 && vehiclesInIntersection.Count == 0){
+                vehiclesQueue[0].GetComponent<VehicleAI>().vehicleStatus = Status.GO;
+            }
+        }
+
+        void TriggerLight(GameObject _vehicle){
+            VehicleAI vehicleAI = _vehicle.GetComponent<VehicleAI>();
+            int vehicleSegment = vehicleAI.GetSegmentVehicleIsIn();
+
+            if(IsRedLightSegment(vehicleSegment)){
+                vehicleAI.vehicleStatus = Status.STOP;
+                vehiclesQueue.Add(_vehicle);
+            }
+            else{
+                vehicleAI.vehicleStatus = Status.GO;
+            }
+        }
+
+        void ExitLight(GameObject _vehicle){
+            _vehicle.GetComponent<VehicleAI>().vehicleStatus = Status.GO;
+        }
+
+        bool IsRedLightSegment(int _vehicleSegment){
+            if(currentRedLightsGroup == 1){
+                foreach(Segment segment in lightsNbr1){
+                    if(segment.id == _vehicleSegment)
+                        return true;
+                }
+            }
+            else{
+                foreach(Segment segment in lightsNbr2){
+                    if(segment.id == _vehicleSegment)
+                        return true;
+                }
+            }
+            return false;
+        }
+
+        void MoveVehiclesQueue(){
+            //Move all vehicles in queue
+            List<GameObject> nVehiclesQueue = new List<GameObject>(vehiclesQueue);
+            foreach(GameObject vehicle in vehiclesQueue){
+                int vehicleSegment = vehicle.GetComponent<VehicleAI>().GetSegmentVehicleIsIn();
+                if(!IsRedLightSegment(vehicleSegment)){
+                    vehicle.GetComponent<VehicleAI>().vehicleStatus = Status.GO;
+                    nVehiclesQueue.Remove(vehicle);
+                }
+            }
+            vehiclesQueue = nVehiclesQueue;
+        }
+
+        bool IsPrioritySegment(int _vehicleSegment){
+            foreach(Segment s in prioritySegments){
+                if(_vehicleSegment == s.id)
+                    return true;
+            }
+            return false;
+        }
+
+        bool IsAlreadyInIntersection(GameObject _target){
+            foreach(GameObject vehicle in vehiclesInIntersection){
+                if(vehicle.GetInstanceID() == _target.GetInstanceID()) return true;
+            }
+            foreach(GameObject vehicle in vehiclesQueue){
+                if(vehicle.GetInstanceID() == _target.GetInstanceID()) return true;
+            }
+
+            return false;
+        } 
+
+
+        private List<GameObject> memVehiclesQueue = new List<GameObject>();
+        private List<GameObject> memVehiclesInIntersection = new List<GameObject>();
+
+        public void SaveIntersectionStatus(){
+            memVehiclesQueue = vehiclesQueue;
+            memVehiclesInIntersection = vehiclesInIntersection;
+        }
+
+        public void ResumeIntersectionStatus(){
+            foreach(GameObject v in vehiclesInIntersection){
+                foreach(GameObject v2 in memVehiclesInIntersection){
+                    if(v.GetInstanceID() == v2.GetInstanceID()){
+                        v.GetComponent<VehicleAI>().vehicleStatus = v2.GetComponent<VehicleAI>().vehicleStatus;
+                        break;
+                    }
+                }
+            }
+            foreach(GameObject v in vehiclesQueue){
+                foreach(GameObject v2 in memVehiclesQueue){
+                    if(v.GetInstanceID() == v2.GetInstanceID()){
+                        v.GetComponent<VehicleAI>().vehicleStatus = v2.GetComponent<VehicleAI>().vehicleStatus;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Intersection.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 08f233b62f5ccc049b5d20eba55f4afb
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 29 - 0
Assets/Scripts/TrafficSimulation/Segment.cs

@@ -0,0 +1,29 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace TrafficSimulation {
+    public class Segment : MonoBehaviour {
+        public List<Segment> nextSegments;
+        
+        [HideInInspector] public int id;
+        [HideInInspector] public List<Waypoint> waypoints;
+
+        public bool IsOnSegment(Vector3 _p){
+            TrafficSystem ts = GetComponentInParent<TrafficSystem>();
+
+            for(int i=0; i < waypoints.Count - 1; i++){
+                float d1 = Vector3.Distance(waypoints[i].transform.position, _p);
+                float d2 = Vector3.Distance(waypoints[i+1].transform.position, _p);
+                float d3 = Vector3.Distance(waypoints[i].transform.position, waypoints[i+1].transform.position);
+                float a = (d1 + d2) - d3;
+                if(a < ts.segDetectThresh && a > -ts.segDetectThresh)
+                    return true;
+
+            }
+            return false;
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Segment.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 69c0d85471449dd49be2e12eca329466
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 50 - 0
Assets/Scripts/TrafficSimulation/TrafficSystem.cs

@@ -0,0 +1,50 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace TrafficSimulation {
+    public class TrafficSystem : MonoBehaviour {
+        public bool hideGuizmos = false;
+        public float segDetectThresh = 0.1f;
+        public ArrowDraw arrowDrawType = ArrowDraw.ByLength;
+        public int arrowCount = 1;
+        public float arrowDistance = 5;
+        public float arrowSizeWaypoint = 1;
+        public float arrowSizeIntersection = 0.5f;
+        public float waypointSize = 0.5f;
+        public string[] collisionLayers;
+        
+        public List<Segment> segments = new List<Segment>();
+        public List<Intersection> intersections = new List<Intersection>();
+        public Segment curSegment = null;  
+        
+        public List<Waypoint> GetAllWaypoints() {
+            List<Waypoint> points = new List<Waypoint>();
+
+            foreach (Segment segment in segments) {
+                points.AddRange(segment.waypoints);
+            }
+
+            return points;
+        }
+
+        public void SaveTrafficSystem(){
+            Intersection[] its  = GameObject.FindObjectsOfType<Intersection>();
+            foreach(Intersection it in its)
+                it.SaveIntersectionStatus();
+        }
+
+        public void ResumeTrafficSystem(){
+            Intersection[] its  = GameObject.FindObjectsOfType<Intersection>();
+            foreach(Intersection it in its)
+                it.ResumeIntersectionStatus();
+        }
+    }
+
+    public enum ArrowDraw {
+        FixedCount, ByLength, Off
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/TrafficSystem.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 29f344d0f8a5f0b4a80a47e9946d8e85
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 324 - 0
Assets/Scripts/TrafficSimulation/VehicleAI.cs

@@ -0,0 +1,324 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace TrafficSimulation {
+
+    /*
+        [-] Check prefab #6 issue
+        [-] Deaccelerate when see stop in front
+        [-] Smooth sharp turns when two segments are linked
+        
+    */
+
+    public struct Target{
+        public int segment;
+        public int waypoint;
+    }
+
+    public enum Status{
+        GO,
+        STOP,
+        SLOW_DOWN
+    }
+
+    public class VehicleAI : MonoBehaviour
+    {
+        [Header("Traffic System")]
+        [Tooltip("Current active traffic system")]
+        public TrafficSystem trafficSystem;
+
+        [Tooltip("Determine when the vehicle has reached its target. Can be used to \"anticipate\" earlier the next waypoint (the higher this number his, the earlier it will anticipate the next waypoint)")]
+        public float waypointThresh = 6;
+
+
+        [Header("Radar")]
+
+        [Tooltip("Empty gameobject from where the rays will be casted")]
+        public Transform raycastAnchor;
+
+        [Tooltip("Length of the casted rays")]
+        public float raycastLength = 5;
+
+        [Tooltip("Spacing between each rays")]
+        public int raySpacing = 2;
+
+        [Tooltip("Number of rays to be casted")]
+        public int raysNumber = 6;
+
+        [Tooltip("If detected vehicle is below this distance, ego vehicle will stop")]
+        public float emergencyBrakeThresh = 2f;
+
+        [Tooltip("If detected vehicle is below this distance (and above, above distance), ego vehicle will slow down")]
+        public float slowDownThresh = 4f;
+
+        [HideInInspector] public Status vehicleStatus = Status.GO;
+
+        private WheelDrive wheelDrive;
+        private float initMaxSpeed = 0;
+        private int pastTargetSegment = -1;
+        private Target currentTarget;
+        private Target futureTarget;
+
+        void Start()
+        {
+            wheelDrive = this.GetComponent<WheelDrive>();
+
+            if(trafficSystem == null)
+                return;
+
+            initMaxSpeed = wheelDrive.maxSpeed;
+            SetWaypointVehicleIsOn();
+        }
+
+        void Update(){
+            if(trafficSystem == null)
+                return;
+
+            WaypointChecker();
+            MoveVehicle();
+        }
+
+
+        void WaypointChecker(){
+            GameObject waypoint = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].gameObject;
+
+            //Position of next waypoint relative to the car
+            Vector3 wpDist = this.transform.InverseTransformPoint(new Vector3(waypoint.transform.position.x, this.transform.position.y, waypoint.transform.position.z));
+
+            //Go to next waypoint if arrived to current
+            if(wpDist.magnitude < waypointThresh){
+                //Get next target
+                currentTarget.waypoint++;
+                if(currentTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count){
+                    pastTargetSegment = currentTarget.segment;
+                    currentTarget.segment = futureTarget.segment;
+                    currentTarget.waypoint = 0;
+                }
+
+                //Get future target
+                futureTarget.waypoint = currentTarget.waypoint + 1;
+                if(futureTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count){
+                    futureTarget.waypoint = 0;
+                    futureTarget.segment = GetNextSegmentId();
+                }
+            }
+        }
+
+        void MoveVehicle(){
+
+            //Default, full acceleration, no break and no steering
+            float acc = 1;
+            float brake = 0;
+            float steering = 0;
+            wheelDrive.maxSpeed = initMaxSpeed;
+
+            //Calculate if there is a planned turn
+            Transform targetTransform = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].transform;
+            Transform futureTargetTransform = trafficSystem.segments[futureTarget.segment].waypoints[futureTarget.waypoint].transform;
+            Vector3 futureVel = futureTargetTransform.position - targetTransform.position;
+            float futureSteering = Mathf.Clamp(this.transform.InverseTransformDirection(futureVel.normalized).x, -1, 1);
+
+            //Check if the car has to stop
+            if(vehicleStatus == Status.STOP){
+                acc = 0;
+                brake = 1;
+                wheelDrive.maxSpeed = Mathf.Min(wheelDrive.maxSpeed / 2f, 5f);
+            }
+            else{
+                
+                //Not full acceleration if have to slow down
+                if(vehicleStatus == Status.SLOW_DOWN){
+                    acc = .3f;
+                    brake = 0f;
+                }
+
+                //If planned to steer, decrease the speed
+                if(futureSteering > .3f || futureSteering < -.3f){
+                    wheelDrive.maxSpeed = Mathf.Min(wheelDrive.maxSpeed, wheelDrive.steeringSpeedMax);
+                }
+
+                //2. Check if there are obstacles which are detected by the radar
+                float hitDist;
+                GameObject obstacle = GetDetectedObstacles(out hitDist);
+
+                //Check if we hit something
+                if(obstacle != null){
+
+                    WheelDrive otherVehicle = null;
+                    otherVehicle = obstacle.GetComponent<WheelDrive>();
+
+                    ///////////////////////////////////////////////////////////////
+                    //Differenciate between other vehicles AI and generic obstacles (including controlled vehicle, if any)
+                    if(otherVehicle != null){
+                        //Check if it's front vehicle
+                        float dotFront = Vector3.Dot(this.transform.forward, otherVehicle.transform.forward);
+
+                        //If detected front vehicle max speed is lower than ego vehicle, then decrease ego vehicle max speed
+                        if(otherVehicle.maxSpeed < wheelDrive.maxSpeed && dotFront > .8f){
+                            float ms = Mathf.Max(wheelDrive.GetSpeedMS(otherVehicle.maxSpeed) - .5f, .1f);
+                            wheelDrive.maxSpeed = wheelDrive.GetSpeedUnit(ms);
+                        }
+                        
+                        //If the two vehicles are too close, and facing the same direction, brake the ego vehicle
+                        if(hitDist < emergencyBrakeThresh && dotFront > .8f){
+                            acc = 0;
+                            brake = 1;
+                            wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
+                        }
+
+                        //If the two vehicles are too close, and not facing same direction, slight make the ego vehicle go backward
+                        else if(hitDist < emergencyBrakeThresh && dotFront <= .8f){
+                            acc = -.3f;
+                            brake = 0f;
+                            wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
+
+                            //Check if the vehicle we are close to is located on the right or left then apply according steering to try to make it move
+                            float dotRight = Vector3.Dot(this.transform.forward, otherVehicle.transform.right);
+                            //Right
+                            if(dotRight > 0.1f) steering = -.3f;
+                            //Left
+                            else if(dotRight < -0.1f) steering = .3f;
+                            //Middle
+                            else steering = -.7f;
+                        }
+
+                        //If the two vehicles are getting close, slow down their speed
+                        else if(hitDist < slowDownThresh){
+                            acc = .5f;
+                            brake = 0f;
+                            //wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 1.5f, wheelDrive.minSpeed);
+                        }
+                    }
+
+                    ///////////////////////////////////////////////////////////////////
+                    // Generic obstacles
+                    else{
+                        //Emergency brake if getting too close
+                        if(hitDist < emergencyBrakeThresh){
+                            acc = 0;
+                            brake = 1;
+                            wheelDrive.maxSpeed = Mathf.Max(wheelDrive.maxSpeed / 2f, wheelDrive.minSpeed);
+                        }
+
+                        //Otherwise if getting relatively close decrease speed
+                         else if(hitDist < slowDownThresh){
+                            acc = .5f;
+                            brake = 0f;
+                        }
+                    }
+                }
+
+                //Check if we need to steer to follow path
+                if(acc > 0f){
+                    Vector3 desiredVel = trafficSystem.segments[currentTarget.segment].waypoints[currentTarget.waypoint].transform.position - this.transform.position;
+                    steering = Mathf.Clamp(this.transform.InverseTransformDirection(desiredVel.normalized).x, -1f, 1f);
+                }
+
+            }
+
+            //Move the car
+            wheelDrive.Move(acc, steering, brake);
+        }
+
+
+        GameObject GetDetectedObstacles(out float _hitDist){
+            GameObject detectedObstacle = null;
+            float minDist = 1000f;
+
+            float initRay = (raysNumber / 2f) * raySpacing;
+            float hitDist =  -1f;
+            for(float a=-initRay; a<=initRay; a+=raySpacing){
+                CastRay(raycastAnchor.transform.position, a, this.transform.forward, raycastLength, out detectedObstacle, out hitDist);
+
+                if(detectedObstacle == null) continue;
+
+                float dist = Vector3.Distance(this.transform.position, detectedObstacle.transform.position);
+                if(dist < minDist) {
+                    minDist = dist;
+                    break;
+                }
+            }
+
+            _hitDist = hitDist;
+            return detectedObstacle;
+        }
+
+        
+        void CastRay(Vector3 _anchor, float _angle, Vector3 _dir, float _length, out GameObject _outObstacle, out float _outHitDistance){
+            _outObstacle = null;
+            _outHitDistance = -1f;
+
+            //Draw raycast
+            Debug.DrawRay(_anchor, Quaternion.Euler(0, _angle, 0) * _dir * _length, new Color(1, 0, 0, 0.5f));
+
+            //Detect hit only on the autonomous vehicle layer
+            int layer = 1 << LayerMask.NameToLayer("AutonomousVehicle");
+            int finalMask = layer;
+
+            foreach(string layerName in trafficSystem.collisionLayers){
+                int id = 1 << LayerMask.NameToLayer(layerName);
+                finalMask = finalMask | id;
+            }
+
+            RaycastHit hit;
+            if(Physics.Raycast(_anchor, Quaternion.Euler(0, _angle, 0) * _dir, out hit, _length, finalMask)){
+                _outObstacle = hit.collider.gameObject;
+                _outHitDistance = hit.distance;
+            }
+        }
+
+        int GetNextSegmentId(){
+            if(trafficSystem.segments[currentTarget.segment].nextSegments.Count == 0)
+                return 0;
+            int c = Random.Range(0, trafficSystem.segments[currentTarget.segment].nextSegments.Count);
+            return trafficSystem.segments[currentTarget.segment].nextSegments[c].id;
+        }
+
+        void SetWaypointVehicleIsOn(){
+            //Find current target
+            foreach(Segment segment in trafficSystem.segments){
+                if(segment.IsOnSegment(this.transform.position)){
+                    currentTarget.segment = segment.id;
+
+                    //Find nearest waypoint to start within the segment
+                    float minDist = float.MaxValue;
+                    for(int j=0; j<trafficSystem.segments[currentTarget.segment].waypoints.Count; j++){
+                        float d = Vector3.Distance(this.transform.position, trafficSystem.segments[currentTarget.segment].waypoints[j].transform.position);
+
+                        //Only take in front points
+                        Vector3 lSpace = this.transform.InverseTransformPoint(trafficSystem.segments[currentTarget.segment].waypoints[j].transform.position);
+                        if(d < minDist && lSpace.z > 0){
+                            minDist = d;
+                            currentTarget.waypoint = j;
+                        }
+                    }
+                    break;
+                }
+            }
+
+            //Get future target
+            futureTarget.waypoint = currentTarget.waypoint + 1;
+            futureTarget.segment = currentTarget.segment;
+
+            if(futureTarget.waypoint >= trafficSystem.segments[currentTarget.segment].waypoints.Count){
+                futureTarget.waypoint = 0;
+                futureTarget.segment = GetNextSegmentId();
+            }
+        }
+
+        public int GetSegmentVehicleIsIn(){
+            int vehicleSegment = currentTarget.segment;
+            bool isOnSegment = trafficSystem.segments[vehicleSegment].IsOnSegment(this.transform.position);
+            if(!isOnSegment){
+                bool isOnPSegement = trafficSystem.segments[pastTargetSegment].IsOnSegment(this.transform.position);
+                if(isOnPSegement)
+                    vehicleSegment = pastTargetSegment;
+            }
+            return vehicleSegment;
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/VehicleAI.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: cde2d72bdb21e8542bfb0a4516cc158b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 32 - 0
Assets/Scripts/TrafficSimulation/Waypoint.cs

@@ -0,0 +1,32 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+
+using UnityEngine;
+
+namespace TrafficSimulation {
+    public class Waypoint : MonoBehaviour {
+        [HideInInspector] public Segment segment;
+
+        public void Refresh(int _newId, Segment _newSegment) {
+            segment = _newSegment;
+            name = "Waypoint-" + _newId;
+            tag = "Waypoint";
+            
+            //Set the layer to Default
+            gameObject.layer = 0;
+            
+            //Remove the Collider cause it it not necessary any more
+            RemoveCollider();
+        }
+
+        public void RemoveCollider() {
+            if (GetComponent<SphereCollider>()) {
+                DestroyImmediate(gameObject.GetComponent<SphereCollider>());
+            }
+        }
+
+        public Vector3 GetVisualPos() {
+            return transform.position + new Vector3(0, 0.5f, 0);
+        }
+    }
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/Waypoint.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 399c99cbf30f13c4f9a848c618de7a58
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 140 - 0
Assets/Scripts/TrafficSimulation/WheelDrive.cs

@@ -0,0 +1,140 @@
+// Traffic Simulation
+// https://github.com/mchrbn/unity-traffic-simulation
+// Based on the Vehicle Tools package from Unity
+
+using UnityEngine;
+using System;
+
+namespace TrafficSimulation{
+    [Serializable]
+    public enum DriveType{
+        RearWheelDrive,
+        FrontWheelDrive,
+        AllWheelDrive
+    }
+
+    [Serializable]
+    public enum UnitType{
+        KMH,
+        MPH
+    }
+
+    public class WheelDrive : MonoBehaviour
+    {
+        [Tooltip("Downforce applied to the vehicle")]
+        public float downForce = 100f;
+
+        [Tooltip("Maximum steering angle of the wheels")]
+        public float maxAngle = 30f;
+
+        [Tooltip("Speed at which we will reach the above steering angle (lerp)")]
+        public float steeringLerp = 5f;
+        
+        [Tooltip("Max speed (in unit choosen below) when the vehicle is about to steer")]
+        public float steeringSpeedMax = 20f;
+
+        [Tooltip("Maximum torque applied to the driving wheels")]
+        public float maxTorque = 300f;
+
+        [Tooltip("Maximum brake torque applied to the driving wheels")]
+        public float brakeTorque = 30000f;
+
+        [Tooltip("Unit Type")]
+        public UnitType unitType;
+
+        [Tooltip("Min Speed - when driving (not including stops/brakes), in the unit choosen above. Should be > 0.")]
+        public float minSpeed = 5;
+
+        [Tooltip("Max Speed in the unit choosen above")]
+        public float maxSpeed = 50;
+
+        [Tooltip("Drag the wheel shape here.")]
+        public GameObject leftWheelShape;
+        public GameObject rightWheelShape;
+
+        [Tooltip("Whether you want to animate the wheels")]
+        public bool animateWheels = true;
+        
+        [Tooltip("The vehicle's drive type: rear-wheels drive, front-wheels drive or all-wheels drive.")]
+        public DriveType driveType;
+
+        private WheelCollider[] wheels;
+        private float currentSteering = 0f;
+
+        void OnEnable(){
+            wheels = GetComponentsInChildren<WheelCollider>();
+
+            for (int i = 0; i < wheels.Length; ++i) 
+            {
+                var wheel = wheels [i];
+
+                // Create wheel shapes only when needed.
+                if (leftWheelShape != null && wheel.transform.localPosition.x < 0)
+                {
+                    var ws = Instantiate (leftWheelShape);
+                    ws.transform.parent = wheel.transform;
+                }
+                else if(rightWheelShape != null && wheel.transform.localPosition.x > 0){
+                    var ws = Instantiate(rightWheelShape);
+                    ws.transform.parent = wheel.transform;
+                }
+
+                wheel.ConfigureVehicleSubsteps(10, 1, 1);
+            }
+        }
+
+        public void Move(float _acceleration, float _steering, float _brake)
+        {
+
+            float nSteering = Mathf.Lerp(currentSteering, _steering, Time.deltaTime * steeringLerp);
+            currentSteering = nSteering;
+
+            Rigidbody rb = this.GetComponent<Rigidbody>();
+
+            float angle = maxAngle * nSteering;
+            float torque = maxTorque * _acceleration;
+
+            float handBrake = _brake > 0 ? brakeTorque : 0;
+
+            foreach (WheelCollider wheel in wheels){
+                // Steer front wheels only
+                if (wheel.transform.localPosition.z > 0) wheel.steerAngle = angle;
+
+                if (wheel.transform.localPosition.z < 0) wheel.brakeTorque = handBrake;
+
+                if (wheel.transform.localPosition.z < 0 && driveType != DriveType.FrontWheelDrive) wheel.motorTorque = torque;
+
+                if (wheel.transform.localPosition.z >= 0 && driveType != DriveType.RearWheelDrive) wheel.motorTorque = torque;
+
+
+                // Update visual wheels if allowed
+                if(animateWheels){
+                    Quaternion q;
+                    Vector3 p;
+                    wheel.GetWorldPose(out p, out q);
+
+                    Transform shapeTransform = wheel.transform.GetChild (0);
+                    shapeTransform.position = p;
+                    shapeTransform.rotation = q;
+                }
+            }
+
+
+            //Apply speed
+            float s = GetSpeedUnit(rb.velocity.magnitude);
+            if(s > maxSpeed) rb.velocity = GetSpeedMS(maxSpeed) * rb.velocity.normalized;
+
+            
+            //Apply downforce
+            rb.AddForce(-transform.up * downForce * rb.velocity.magnitude);
+        }
+
+        public float GetSpeedMS(float _s){
+            return unitType == UnitType.KMH ? _s / 3.6f : _s / 2.237f;
+        }
+
+        public float GetSpeedUnit(float _s){
+            return unitType == UnitType.KMH ? _s * 3.6f : _s * 2.237f;
+        }
+    }   
+}

+ 11 - 0
Assets/Scripts/TrafficSimulation/WheelDrive.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5021f9004f348d74abb4f0ae3ed02375
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: