Attribute VB_Name = "VehicleCollection"

Option Explicit

Public Type WheelProperty
    BoneIndex As Long
    Location As Vector3
    AxisLocation As Vector3
    Steering As Boolean
    Driving As Boolean
    Braking As Boolean
    Friction As AdvancedFriction
End Type
Public Type Wheel
    PreviousSpringPressure As Single
    XAngle As Single 'Orientation from driving
    XAnglularVelocity As Single 'Angular velocity around it's local X axis
End Type
Const MaxNumberOfWheels As Long = 4
Public Type WheelCollectionProperties
    NumberOfWheels As Long
    Wheels(0 To MaxNumberOfWheels - 1) As WheelProperty
    Radius As Single
    MinOffset As Single
    MaxOffset As Single
    Mass As Single
    SpringForce As Single 'Local force from pressure
    Damping As Single 'Local force from derivative of pressure
    MaxForce As Single
    DifferentialLock As Boolean
        DifferentialLockWheelIndexA As Integer
        DifferentialLockWheelIndexB As Integer
End Type
Public Type WheelCollection
    Wheels(0 To MaxNumberOfWheels - 1) As Wheel
    GoalYAngle As Single
    PhysicalYAngle As Single 'Move closer to GoalYAngle in fixed timesteps
    GraphicalYAngle As Single 'Move closer to GoalYAngle in dynamic timesteps
End Type
Public Type BoneData
    GunLongitude As Long
    GunLattitude As Long
        GunPivot As Vector3
    FrontLeft As Long
        FrontLeftWheelStartPos As Vector3
        FrontLeftWheelEndPos As Vector3
    FrontRight As Long
        FrontRightWheelStartPos As Vector3
        FrontRightWheelEndPos As Vector3
    RearLeft As Long
        RearLeftWheelStartPos As Vector3
        RearLeftWheelEndPos As Vector3
    RearRight As Long
        RearRightWheelStartPos As Vector3
        RearRightWheelEndPos As Vector3
End Type
Public Type TypeOfVehicle
    Model_Body As Long 'Visual model for the body
    Model_Wheel As Long 'Visual model for the wheels
    CollisionShape As Long 'Physical shape
    MaxGasInjection As Single
    SidewayTiltPower As Single
    ForwardTiltPower As Single
    MaxAntiSpinnForce As Single 'Prevent spinning around when the vehicle tries to go straight
    MaxSpinn As Single 'The maximum allowed Y spinn before being adjusted
    TiltWhenSteering As Single 'Tilt sideways when driving fast in curves
    LinearAirResistance As Single
    AngularAirResistance As Single
    RollPrevention As Single '0 gives no help and 1 takes away all unwanted angular forces from the wheels
    DownForce As Single
    Wheels As WheelCollectionProperties
    Radius As Single
    Mass As Single
    FloatForce As Single
    GunLattitude_Min As Single
    GunLattitude_Max As Single
    MaxHealth As Single
    RegenerationPerSecond As Single
    BD As BoneData
    EngineSpeedResistance As Single
    GearboxResistance As Single
    HighestGear As Long
    GearMultiplier As Single
    EngineVolume As Single
    StartEngineAcceleration As Single
End Type
Public Enum ControlType
    ControlType_None
    ControlType_Player
    ControlType_Bot
End Enum
Public Type ControlData
    Steer As Single 'From -1 to +1
    Accelerate As Single
    Brake As Single
    TiltSideway As Single
    TiltForward As Single
    Shoot As Boolean
    SelectedWeapon As Long
    FireMode As Long 'FireMode_Primary or FireMode_Secondary
    GunAim As Vector2
End Type
Public Type BotData
    PreferDirection As Boolean
    PreferredDirection As Long
        NewActionTimer As Single
    ChangeDirectionTimer As Single
    ChangeSteeringTimer As Single
    CollidedWithItemLastStep As Boolean 'If it collided with an item the last physical step
    TargetIndex As Integer 'A spotted enemy's vehicle index
    Desperate As Single 'How large risks it is ready to take
        DesperateCount As Long 'How many times in a row has it been desperate
    WheelContact As Boolean 'Are all wheels in contact with the ground
End Type
Public Type VehicleSoundEffect
    EffectSum As Single
    FinalEffect As Single
End Type
Public Type VehicleSoundEffects
    Slide As VehicleSoundEffect
    FuelAcceleration As VehicleSoundEffect
End Type
Public Type Vehicle
    VehicleTypeIndex As Long
    Working As Boolean
    Instance_Body As Long 'Visual instance for the body
    Instance_Wheel(0 To MaxNumberOfWheels - 1) As Long 'Visual instances for the wheels
    Instance_Laser As Long
    BoneFrame As Long
    LaserDotLightSource As Long
    RigidBody As Long 'Physical body using the shape
    LastPosition As Vector3 'The position after the last physical step to be used by AI and game rules
    ControlT As ControlType 'How it is controlled
    ControlD As ControlData 'The input from the player or robot
    SpawnTimer As Single 'The time that remains until the weapon can fire again
    BotD As BotData 'Information that is used when the vehicle is controlled by a robot or an autopilot
    TeamID As Long 'Vehicles of different teams will try to kill each other
    WeaponIndex As Long 'The currently selected weapon
    GunLongitude As Single
    GunLattitude As Single
    VisualGunDirection As Vector3
    SoundEffects As VehicleSoundEffects
    CurrentHealth As Single
    Wheels As WheelCollection
    Gear As Long
    ShortGearDelay As Single 'The time that the clutch will be released while changing gear
    LongGearDelay As Single 'The delay until another forward gear can be selected
    EngineSpeed As Single
    GearboxSpeed As Single
    DifferentialLocked As Boolean
    DifferentialLockDelay As Single
End Type
Dim VehicleZero As Vehicle 'Zero data for reusing the memory

'Models
Dim Model_Laser As Long

'Control smoothing
Const TurningSpeed As Single = 1.2

'Vehicle types
Const VT_Car As Long = 0
Const NumberOfVehicleTypes As Long = 1
Dim VehicleTypes(0 To NumberOfVehicleTypes - 1) As TypeOfVehicle

'Vehicles
Public Const MaxNumberOfVehicles As Integer = 8
Public NumberOfVehicles As Integer
Dim Vehicles(0 To MaxNumberOfVehicles - 1) As Vehicle
Public Vehicle_Player As Integer

'Gear box
Const HighestPossibleGear As Long = 5
Dim RatioFromGearBox(0 To HighestPossibleGear) As Single
Const CurrentGearBonusMultiplier As Single = 1.01 'How much will it try to keep the current gear

Public Sub Vehicle_Init()
    Dim I As Integer
    Dim J As Integer
    
    Vehicle_Player = -1
    
    'Set the gears
    RatioFromGearBox(0) = -0.8 'Reverse
    RatioFromGearBox(1) = 0.8
    RatioFromGearBox(2) = 1
    RatioFromGearBox(3) = 1.3
    RatioFromGearBox(4) = 1.6
    RatioFromGearBox(5) = 1.9
    
    'Load models
    Model_Laser = LoadModel("Model_Laser")
    
    'Create vehicle types
    Dim Dimensions As Vector3
    I = VT_Car
        'Visual
        VehicleTypes(I).Model_Body = LoadModel("Model_Vehicle_Car"): Dimensions = GetDimensionsFromModel(VehicleTypes(I).Model_Body)
        VehicleTypes(I).BD = GetBoneIndexFromModel(VehicleTypes(I).Model_Body)
        VehicleTypes(I).Model_Wheel = LoadModel("Model_VehiclePart_Wheel")
        
        'Physics
        VehicleTypes(I).CollisionShape = GetCollisionShapeFromModel(VehicleTypes(I).Model_Body, 1)
        VehicleTypes(I).Mass = 6
        VehicleTypes(I).Radius = frmMain.DGE.Model_GetBoundingSphereRadius(VehicleTypes(I).Model_Body): RE
        
        'Fake physics
        VehicleTypes(I).ForwardTiltPower = 35
        VehicleTypes(I).SidewayTiltPower = 35
        VehicleTypes(I).MaxAntiSpinnForce = 30
        VehicleTypes(I).MaxSpinn = 1.5
        VehicleTypes(I).RollPrevention = 1
        
        'Aerodynamics
        VehicleTypes(I).LinearAirResistance = 0.05
        VehicleTypes(I).AngularAirResistance = 0.05
        VehicleTypes(I).DownForce = 0.5
        
        'Hydrodynamics
        VehicleTypes(I).FloatForce = VehicleTypes(I).Mass * 0.5 '-0.5 G
        
        'Health
        VehicleTypes(I).MaxHealth = 500
        VehicleTypes(I).RegenerationPerSecond = 50
        
        'Engine
        VehicleTypes(I).StartEngineAcceleration = 20
        VehicleTypes(I).MaxGasInjection = 100
        VehicleTypes(I).EngineVolume = 1.2
        VehicleTypes(I).EngineSpeedResistance = 0.5
        
        'Gearbox
        VehicleTypes(I).HighestGear = 5
        VehicleTypes(I).GearMultiplier = 0.4
        VehicleTypes(I).GearboxResistance = 0.004
        
        'Weapon
        VehicleTypes(I).GunLattitude_Min = -0.25
        VehicleTypes(I).GunLattitude_Max = 0.7
        
        'Wheels
        VehicleTypes(I).Wheels.NumberOfWheels = 4
            J = 0
                VehicleTypes(I).Wheels.Wheels(J).Steering = True
                VehicleTypes(I).Wheels.Wheels(J).Braking = False
                VehicleTypes(I).Wheels.Wheels(J).Friction = MakeAdvancedFriction(1, 1, 1, 1, 1)
                UseWheelAsFrontRight I, J
            J = J + 1
                VehicleTypes(I).Wheels.Wheels(J) = VehicleTypes(I).Wheels.Wheels(J - 1) 'Start with a copy of the previous wheel
                UseWheelAsFrontLeft I, J
            J = J + 1
                VehicleTypes(I).Wheels.Wheels(J).Driving = True
                VehicleTypes(I).Wheels.Wheels(J).Braking = True
                VehicleTypes(I).Wheels.Wheels(J).Friction = MakeAdvancedFriction(1, 1, 0.9, 1, 1)
                UseWheelAsRearRight I, J
            J = J + 1
                VehicleTypes(I).Wheels.Wheels(J) = VehicleTypes(I).Wheels.Wheels(J - 1) 'Start with a copy of the previous wheel
                UseWheelAsRearLeft I, J
            VehicleTypes(I).Wheels.Radius = 0.4
            VehicleTypes(I).Wheels.MinOffset = -0.2
            VehicleTypes(I).Wheels.MaxOffset = -0.04
            VehicleTypes(I).Wheels.Mass = 0.8
            VehicleTypes(I).Wheels.SpringForce = VehicleTypes(I).Mass * 500
            VehicleTypes(I).Wheels.Damping = 0.03
            VehicleTypes(I).Wheels.MaxForce = 120
            VehicleTypes(I).Wheels.DifferentialLock = True
                VehicleTypes(I).Wheels.DifferentialLockWheelIndexA = 2
                VehicleTypes(I).Wheels.DifferentialLockWheelIndexB = 3
    
    'Create vehicles
    'Vehicle_Player = CreateVehicle(Int(Rnd * NumberOfVehicleTypes), ControlType_Player, Team_Player)
    Vehicle_Player = CreateVehicle(VT_Car, ControlType_Player, Team_Player)
    'Vehicle_Player = CreateVehicle(VT_Car, ControlType_Bot, Team_Player)
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player
    'CreateVehicle VT_Car, ControlType_Bot, Team_Player ' Team_Computer
End Sub

Private Sub UseWheelAsFrontLeft(VehicleIndex As Integer, WheelIndex As Integer)
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).BoneIndex = VehicleTypes(VehicleIndex).BD.FrontLeft
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).AxisLocation = VehicleTypes(VehicleIndex).BD.FrontLeftWheelStartPos
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).Location = VehicleTypes(VehicleIndex).BD.FrontLeftWheelEndPos
End Sub

Private Sub UseWheelAsFrontRight(VehicleIndex As Integer, WheelIndex As Integer)
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).BoneIndex = VehicleTypes(VehicleIndex).BD.FrontRight
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).AxisLocation = VehicleTypes(VehicleIndex).BD.FrontRightWheelStartPos
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).Location = VehicleTypes(VehicleIndex).BD.FrontRightWheelEndPos
End Sub

Private Sub UseWheelAsRearLeft(VehicleIndex As Integer, WheelIndex As Integer)
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).BoneIndex = VehicleTypes(VehicleIndex).BD.RearLeft
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).AxisLocation = VehicleTypes(VehicleIndex).BD.RearLeftWheelStartPos
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).Location = VehicleTypes(VehicleIndex).BD.RearLeftWheelEndPos
End Sub

Private Sub UseWheelAsRearRight(VehicleIndex As Integer, WheelIndex As Integer)
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).BoneIndex = VehicleTypes(VehicleIndex).BD.RearRight
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).AxisLocation = VehicleTypes(VehicleIndex).BD.RearRightWheelStartPos
    VehicleTypes(VehicleIndex).Wheels.Wheels(WheelIndex).Location = VehicleTypes(VehicleIndex).BD.RearRightWheelEndPos
End Sub

Public Function GetBoneIndexFromModel(Model As Long) As BoneData
    Dim I As Long
    Dim Name As String
    
    'Reset to not existing
    GetBoneIndexFromModel.GunLongitude = -1
    GetBoneIndexFromModel.GunLattitude = -1
    GetBoneIndexFromModel.FrontLeft = -1
    GetBoneIndexFromModel.FrontRight = -1
    GetBoneIndexFromModel.RearLeft = -1
    GetBoneIndexFromModel.RearRight = -1
    
    'Get the bone index for every bone that match a name
    For I = 0 To frmMain.DGE.Model_GetNumberOfBones(Model) - 1
        frmMain.DGE.Model_Bone_GetName_OutSB Model, I: Name = GetStringFromEngine: RE
        Select Case Name
        Case "GunLongitude"
            GetBoneIndexFromModel.GunLongitude = I
        Case "GunLattitude"
            GetBoneIndexFromModel.GunLattitude = I
        Case "FrontLeft"
            GetBoneIndexFromModel.FrontLeft = I
        Case "FrontRight"
            GetBoneIndexFromModel.FrontRight = I
        Case "RearLeft"
            GetBoneIndexFromModel.RearLeft = I
        Case "RearRight"
            GetBoneIndexFromModel.RearRight = I
        End Select
    Next I
    
    'Get the initial positions
    GetBoneIndexFromModel.GunPivot = GetBonePosition(Model, GetBoneIndexFromModel.GunLattitude, 0)
    GetBoneIndexFromModel.FrontLeftWheelStartPos = GetBonePosition(Model, GetBoneIndexFromModel.FrontLeft, 0)
    GetBoneIndexFromModel.FrontLeftWheelEndPos = GetBonePosition(Model, GetBoneIndexFromModel.FrontLeft, 1)
    GetBoneIndexFromModel.FrontRightWheelStartPos = GetBonePosition(Model, GetBoneIndexFromModel.FrontRight, 0)
    GetBoneIndexFromModel.FrontRightWheelEndPos = GetBonePosition(Model, GetBoneIndexFromModel.FrontRight, 1)
    GetBoneIndexFromModel.RearLeftWheelStartPos = GetBonePosition(Model, GetBoneIndexFromModel.RearLeft, 0)
    GetBoneIndexFromModel.RearLeftWheelEndPos = GetBonePosition(Model, GetBoneIndexFromModel.RearLeft, 1)
    GetBoneIndexFromModel.RearRightWheelStartPos = GetBonePosition(Model, GetBoneIndexFromModel.RearRight, 0)
    GetBoneIndexFromModel.RearRightWheelEndPos = GetBonePosition(Model, GetBoneIndexFromModel.RearRight, 1)
End Function

Private Function GetBonePosition(Model As Long, BoneIndex As Long, StartEndRatio As Single) As Vector3
    Dim Length As Single
    Dim ZAxis As Vector3
    Dim StartPosition As Vector3
    Dim EndPosition As Vector3
    If BoneIndex > -1 Then
        frmMain.DGE.Model_Bone_GetObjectSpacePos_OutV3 Model, BoneIndex: StartPosition = GetVector3FromMatrixBuffer: RE
        frmMain.DGE.Model_Bone_GetZAxis_OutV3 Model, BoneIndex: ZAxis = GetVector3FromMatrixBuffer: RE
        Length = frmMain.DGE.Model_Bone_GetLength(Model, BoneIndex): RE
        EndPosition = AddVector3(StartPosition, MulVector3(ZAxis, Length))
        GetBonePosition = LerpVector3(StartPosition, EndPosition, StartEndRatio)
    Else
        GetBonePosition = MakeVector3(0, 0, 0)
    End If
End Function

Public Function GetVehicleRigidBody(Index As Long) As Long
    GetVehicleRigidBody = Vehicles(Index).RigidBody
End Function

Private Sub CountDown(ByRef TimerValue As Single)
    TimerValue = Max_Float(0, TimerValue - TimeStep)
End Sub

Public Function CreateVehicle(VehicleTypeIndex As Long, ControlT As ControlType, TeamID As Long) As Integer
    Dim NewLocation As Matrix4
    If NumberOfVehicles < MaxNumberOfVehicles Then
        'Reset memory
        Vehicles(NumberOfVehicles) = VehicleZero
        
        'Activate it
        Vehicles(NumberOfVehicles).Working = True
        
        'Remember the type index
        Vehicles(NumberOfVehicles).VehicleTypeIndex = VehicleTypeIndex
        
        'Generate a random location for the new vehicle
        NewLocation = GenerateRandomLocation(VehicleTypes(VehicleTypeIndex).Radius)
        
        'Initiate the visual gun direction
        Vehicles(NumberOfVehicles).VisualGunDirection = Vector4ToVector3(NewLocation.ZAxis)
        
        'Create the body's instance
        Vehicles(NumberOfVehicles).Instance_Body = frmMain.DGE.Instance_Create(VehicleTypes(VehicleTypeIndex).Model_Body): RE
        
        'Enable automatic detail level
        frmMain.DGE.Instance_SetAutoDetailLevel Vehicles(NumberOfVehicles).Instance_Body, True: RE
        
        Dim W As Long
        For W = 0 To VehicleTypes(VehicleTypeIndex).Wheels.NumberOfWheels - 1
            'Create the wheel's instance
            Vehicles(NumberOfVehicles).Instance_Wheel(W) = frmMain.DGE.Instance_Create(VehicleTypes(VehicleTypeIndex).Model_Wheel): RE
            
            'Enable automatic detail level
            frmMain.DGE.Instance_SetAutoDetailLevel Vehicles(NumberOfVehicles).Instance_Wheel(W), True: RE
        Next W
        
        'Create a bone frame and assign it to the instance
        Vehicles(NumberOfVehicles).BoneFrame = frmMain.DGE.BoneFrame_CreateFromDefaultPose(VehicleTypes(VehicleTypeIndex).Model_Body): RE
        frmMain.DGE.Instance_AssignBoneFrame Vehicles(NumberOfVehicles).Instance_Body, Vehicles(NumberOfVehicles).BoneFrame: RE
        
        'Create the laser
        Vehicles(NumberOfVehicles).Instance_Laser = frmMain.DGE.Instance_Create(Model_Laser): RE
        Vehicles(NumberOfVehicles).LaserDotLightSource = frmMain.DGE.LightSource_Create_Point_Shadowless(0, 0, 0, 1, 1, 1, 1)
        
        'Create a rigid body using a collision shape
        Vehicles(NumberOfVehicles).RigidBody = frmMain.DGE.RigidBody_Create_Dynamic(VehicleTypes(VehicleTypeIndex).CollisionShape, VehicleTypes(VehicleTypeIndex).Mass, NewLocation.WAxis.X, NewLocation.WAxis.Y, NewLocation.WAxis.Z, NewLocation.XAxis.X, NewLocation.XAxis.Y, NewLocation.XAxis.Z, NewLocation.YAxis.X, NewLocation.YAxis.Y, NewLocation.YAxis.Z): RE
        
        'Make sure that the rigid body can not be deactivated
        frmMain.DGE.RigidBody_SetActivationState Vehicles(NumberOfVehicles).RigidBody, DISABLE_DEACTIVATION: RE
        
        'Set friction
        frmMain.DGE.RigidBody_SetFriction Vehicles(NumberOfVehicles).RigidBody, 0.7: RE
        
        'Set restitution
        frmMain.DGE.RigidBody_SetRestitution Vehicles(NumberOfVehicles).RigidBody, DefaultRestitution: RE
        
        'Store index
        frmMain.DGE.RigidBody_SetUserData Vehicles(NumberOfVehicles).RigidBody, RBUDI_Collection, RBC_Vehicle: RE
        frmMain.DGE.RigidBody_SetUserData Vehicles(NumberOfVehicles).RigidBody, RBUDI_Index, NumberOfVehicles: RE
        
        'Let the visual instance follow the rigid body when Physics_LetInstancesFollowRigidBodies or Physics_LetInstancesFollowRigidBodies_Interpolated is called
        frmMain.DGE.Instance_FollowRigidBody Vehicles(NumberOfVehicles).Instance_Body, Vehicles(NumberOfVehicles).RigidBody, False: RE
        
        'Store properties
        Vehicles(NumberOfVehicles).ControlT = ControlT
        Vehicles(NumberOfVehicles).TeamID = TeamID
        
        'Reset properties
        Vehicles(NumberOfVehicles).CurrentHealth = VehicleTypes(VehicleTypeIndex).MaxHealth
        Vehicles(NumberOfVehicles).Gear = 2
        
        'Reset the target
        Vehicles(NumberOfVehicles).BotD.TargetIndex = -1
        
        'Start it's looped sounds
        Sound_StartVehicleLoop NumberOfVehicles
        
        'Update models for the next render
        PrepareVehicleForRendering NumberOfVehicles, 0, 0
        
        CreateVehicle = NumberOfVehicles
        NumberOfVehicles = NumberOfVehicles + 1
    Else
        'In case that too many dynamic physical items are spawned.
        MsgBox "The maximum number of physical items in the game has been reached."
        CreateVehicle = -1
    End If
End Function

Public Sub ConvertInputToPower(VehicleIndex As Integer)
    Dim VehicleTypeIndex As Long
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    If Vehicles(VehicleIndex).Working Then
        Vehicles(VehicleIndex).ControlD.Steer = Clamp(-1, Vehicles(VehicleIndex).ControlD.Steer, 1)
        Vehicles(VehicleIndex).ControlD.Accelerate = Clamp(-1, Vehicles(VehicleIndex).ControlD.Accelerate, 1)
        Vehicles(VehicleIndex).ControlD.Brake = Saturate(Vehicles(VehicleIndex).ControlD.Brake)
        Vehicles(VehicleIndex).ControlD.TiltSideway = Clamp(-1, Vehicles(VehicleIndex).ControlD.TiltSideway, 1) * VehicleTypes(VehicleTypeIndex).SidewayTiltPower
        Vehicles(VehicleIndex).ControlD.TiltForward = Clamp(-1, Vehicles(VehicleIndex).ControlD.TiltForward, 1) * VehicleTypes(VehicleTypeIndex).ForwardTiltPower
    Else
        Vehicles(VehicleIndex).ControlD.Steer = 0
        Vehicles(VehicleIndex).ControlD.Steer = 0
        Vehicles(VehicleIndex).ControlD.Accelerate = 0
        Vehicles(VehicleIndex).ControlD.Brake = 0
        Vehicles(VehicleIndex).ControlD.TiltSideway = 0
        Vehicles(VehicleIndex).ControlD.TiltForward = 0
        Vehicles(VehicleIndex).ControlD.Shoot = False
    End If
End Sub

Public Sub ControlPlayerGun()
    Dim MouseOffset As Vector2
    Dim VehicleTypeIndex As Long
    If frmMain.frmTools.Visible Then
        MouseOffset = MakeVector2(0, 0)
    Else
        MouseOffset = Mouse_GetOffsetSinceLastCall
    End If
    If Vehicle_Player > -1 Then
        VehicleTypeIndex = Vehicles(Vehicle_Player).VehicleTypeIndex
        Vehicles(Vehicle_Player).ControlD.GunAim = MakeVector2(MouseOffset.X * 0.1, MouseOffset.Y * -0.1)
        Vehicles(Vehicle_Player).GunLongitude = Vehicles(Vehicle_Player).GunLongitude + (Vehicles(Vehicle_Player).ControlD.GunAim.X * TimeStep)
        Vehicles(Vehicle_Player).GunLattitude = Clamp(VehicleTypes(VehicleTypeIndex).GunLattitude_Min, Vehicles(Vehicle_Player).GunLattitude + (Vehicles(Vehicle_Player).ControlD.GunAim.Y * TimeStep), VehicleTypes(VehicleTypeIndex).GunLattitude_Max)
    End If
End Sub

Public Sub ControlVehicle(VehicleIndex As Integer, DoHeavyCalculations As Boolean)
    Dim VehicleTypeIndex As Long
    Dim OwnAxisSystem As Matrix3
    Dim OwnPosition As Vector3
    Dim OwnLinearVelocity As Vector3
    Dim OwnLinearVelocity_ObjectSpace As Vector3
    Dim OwnSpeed As Single
    Dim OwnAngularVelocity As Vector3
    Dim OwnAngularVelocity_ObjectSpace As Vector3
    
    'The the index to the vehicle type
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    
    'We only need to take out the axis system since we already have the position
    frmMain.DGE.RigidBody_GetTransformation_OutM4 Vehicles(VehicleIndex).RigidBody: OwnAxisSystem = GetMatrix3FromMatrixBuffer: RE
    OwnPosition = Vehicles(VehicleIndex).LastPosition
    
    'Get the linear velocity
    frmMain.DGE.RigidBody_GetLinearVelocity_OutV3 Vehicles(VehicleIndex).RigidBody: OwnLinearVelocity = GetVector3FromMatrixBuffer: RE
    OwnLinearVelocity_ObjectSpace = MulVecMat3(OwnLinearVelocity, TransposeMatrix3(OwnAxisSystem))
    OwnSpeed = AbsVector3(OwnLinearVelocity)
    
    'Get the angular velocity
    frmMain.DGE.RigidBody_GetAngularVelocity_OutV3 Vehicles(VehicleIndex).RigidBody: OwnAngularVelocity = GetVector3FromMatrixBuffer: RE
    OwnAngularVelocity_ObjectSpace = MulVecMat3(OwnAngularVelocity, TransposeMatrix3(OwnAxisSystem))
    
    Select Case Vehicles(VehicleIndex).ControlT
    Case ControlType_None
        Vehicles(VehicleIndex).ControlD.Steer = 0
        Vehicles(VehicleIndex).ControlD.Accelerate = 0
        Vehicles(VehicleIndex).ControlD.Brake = 1
        Vehicles(VehicleIndex).ControlD.TiltSideway = 0
        Vehicles(VehicleIndex).ControlD.TiltForward = 0
        Vehicles(VehicleIndex).ControlD.Shoot = False
    Case ControlType_Player
        Vehicles(VehicleIndex).ControlD.Steer = ((KeyDown_ZeroToOne(vbKeyRight) + KeyDown_ZeroToOne(vbKeyD)) - (KeyDown_ZeroToOne(vbKeyLeft) + KeyDown_ZeroToOne(vbKeyA)))
        Vehicles(VehicleIndex).ControlD.Accelerate = ((KeyDown_ZeroToOne(vbKeyUp) + KeyDown_ZeroToOne(vbKeyW)) - (KeyDown_ZeroToOne(vbKeyDown) + KeyDown_ZeroToOne(vbKeyS)))
        Vehicles(VehicleIndex).ControlD.Brake = KeyDown_ZeroToOne(vbKeySpace)
        
        'Skip moving the gun using the mouse since it has to be synchronized with graphics
        
        If KeyDown_Truth(vbKeyRButton) Then
            Vehicles(VehicleIndex).ControlD.Shoot = True
            Vehicles(VehicleIndex).ControlD.FireMode = FireMode_Secondary
        ElseIf KeyDown_Truth(vbKeyLButton) Then
            Vehicles(VehicleIndex).ControlD.Shoot = True
            Vehicles(VehicleIndex).ControlD.FireMode = FireMode_Primary
        Else
            Vehicles(VehicleIndex).ControlD.Shoot = False
        End If
        Dim W As Long
        For W = 0 To NumberOfWeapons - 1
            If KeyDown_Truth(Weapons(W).Key) Then
                Vehicles(VehicleIndex).ControlD.SelectedWeapon = W
            End If
        Next W
    Case ControlType_Bot
        Dim NeedToBreak As Boolean
        
        'Wait for any reason to break
        NeedToBreak = False
        
        'Count down to zero
        CountDown Vehicles(VehicleIndex).BotD.NewActionTimer
        CountDown Vehicles(VehicleIndex).BotD.Desperate
        CountDown Vehicles(VehicleIndex).BotD.ChangeDirectionTimer
        CountDown Vehicles(VehicleIndex).BotD.ChangeSteeringTimer
        
        Dim D As Long
        Const Dir_Ahead As Long = 0
        Const Dir_LeftAhead As Long = 1
        Const Dir_RightAhead As Long = 2
        Const Dir_Back As Long = 3
        Const Dir_LeftBack As Long = 4
        Const Dir_RightBack As Long = 5
        Const NumberOfDirections As Long = 6
        
        Dim O As Long
        Dim OffsetMultiplier As Single
        
        Dim VisionLength As Single
        Dim MinimumAllowedRadius As Single
        Dim MinimumAllowedLength As Single
        Dim Offset(0 To NumberOfDirections - 1) As Vector2
        Dim Position As Vector3
        Dim Obstacle(0 To NumberOfDirections - 1) As Single
        Dim FrontObstacle As Single
        Dim BackObstacle As Single
        
        FrontObstacle = Terrain_GetDistanceToClosestObstacle(AddVector3(OwnPosition, OwnAxisSystem.ZAxis))
        BackObstacle = Terrain_GetDistanceToClosestObstacle(SubVector3(OwnPosition, OwnAxisSystem.ZAxis))
        
        VisionLength = Clamp(7, OwnSpeed * 2, 15)
        MinimumAllowedRadius = 0.5
        MinimumAllowedLength = 0.4
        
        Offset(Dir_Ahead) = MakeVector2(0, VisionLength)
        Offset(Dir_LeftAhead) = MakeVector2(-VisionLength * (0.7 + (Rnd * 0.3)), VisionLength * 0.8)
        Offset(Dir_RightAhead) = MakeVector2(VisionLength * (0.7 + (Rnd * 0.3)), VisionLength * 0.8)
        Offset(Dir_Back) = MakeVector2(0, -VisionLength)
        Offset(Dir_LeftBack) = MakeVector2(-VisionLength * (0.7 + (Rnd * 0.3)), -VisionLength * 0.8)
        Offset(Dir_RightBack) = MakeVector2(VisionLength * (0.7 + (Rnd * 0.3)), -VisionLength * 0.8)
        For D = 0 To NumberOfDirections - 1
            Dim ScaledOffset As Vector2
            Dim NewRadius As Single
            Obstacle(D) = 10000
            For O = 2 To 8
                OffsetMultiplier = (O + Rnd) / 8
                ScaledOffset.X = Offset(D).X * OffsetMultiplier ^ 2
                ScaledOffset.Y = Offset(D).Y * OffsetMultiplier
                Position = AddVector3(OwnPosition, AddVector3(MulVector3(OwnAxisSystem.ZAxis, ScaledOffset.Y), MulVector3(OwnAxisSystem.XAxis, ScaledOffset.X)))
                NewRadius = Terrain_GetDistanceToClosestObstacle(Position)
                'If True Then
                '    Position.Y = Terrain_GetHeightLinear(Position.X, Position.Z) + 0.5
                '    Particle_Create Position, MakeVector3(0, 0, 0), 0, ParticleType_PlasmaFragment, -1
                '    'Particle_Create Terrain_GetClosestObstacle(Position(D)), MakeVector3(0, 0, 0), 0, ParticleType_PlasmaFragment, -1
                'End If
                If NewRadius < MinimumAllowedRadius Then
                    Exit For
                End If
            Next O
            Obstacle(D) = OffsetMultiplier
        Next D
        
        'Calm down
        If Vehicles(VehicleIndex).BotD.Desperate < 0.001 Then
            Vehicles(VehicleIndex).BotD.PreferDirection = False
            Vehicles(VehicleIndex).BotD.DesperateCount = 0
        End If
        
        'Force it to try something
        If Vehicles(VehicleIndex).BotD.PreferDirection Then
            Vehicles(VehicleIndex).ControlD.Shoot = False
            Vehicles(VehicleIndex).ControlD.Brake = False
            Select Case Vehicles(VehicleIndex).BotD.PreferredDirection
            Case Dir_Ahead
                Vehicles(VehicleIndex).ControlD.Accelerate = 1
                Vehicles(VehicleIndex).ControlD.Steer = 0
            Case Dir_LeftAhead
                Vehicles(VehicleIndex).ControlD.Accelerate = 1
                Vehicles(VehicleIndex).ControlD.Steer = -1
            Case Dir_RightAhead
                Vehicles(VehicleIndex).ControlD.Accelerate = 1
                Vehicles(VehicleIndex).ControlD.Steer = 1
            Case Dir_Back
                Vehicles(VehicleIndex).ControlD.Accelerate = -1
                Vehicles(VehicleIndex).ControlD.Steer = 0
            Case Dir_LeftBack
                Vehicles(VehicleIndex).ControlD.Accelerate = -1
                Vehicles(VehicleIndex).ControlD.Steer = -1
            Case Dir_RightBack
                Vehicles(VehicleIndex).ControlD.Accelerate = -1
                Vehicles(VehicleIndex).ControlD.Steer = 1
            End Select
        Else
            'Set acceleration
            If Vehicles(VehicleIndex).BotD.ChangeDirectionTimer < 0.001 Then
                If Obstacle(Dir_Ahead) > MinimumAllowedLength Or Obstacle(Dir_LeftAhead) > MinimumAllowedLength Or Obstacle(Dir_RightAhead) > MinimumAllowedLength Then
                    'Drive forward
                    Vehicles(VehicleIndex).ControlD.Accelerate = 1
                    Vehicles(VehicleIndex).BotD.ChangeDirectionTimer = 0.5
                Else
                    If OwnLinearVelocity_ObjectSpace.Z > 10 Then
                        'Break
                        NeedToBreak = True
                        Vehicles(VehicleIndex).ControlD.Accelerate = 0
                    Else
                        'Drive backwards
                        Vehicles(VehicleIndex).ControlD.Accelerate = -1
                        Vehicles(VehicleIndex).BotD.ChangeDirectionTimer = 0.5
                    End If
                End If
            End If
            
            'Set steering
            If Vehicles(VehicleIndex).BotD.ChangeSteeringTimer < 0.001 Then
                If NeedToBreak Then
                    'Don't change the steering while breaking
                Else
                    If Vehicles(VehicleIndex).ControlD.Accelerate > 0 Then
                        'Forward
                        If Obstacle(Dir_Ahead) > MinimumAllowedLength Then
                            'Straight
                            Vehicles(VehicleIndex).ControlD.Steer = 0
                            Vehicles(VehicleIndex).BotD.ChangeSteeringTimer = 0.5
                        Else
                            If Obstacle(Dir_LeftAhead) < Obstacle(Dir_RightAhead) Then
                                'Right
                                Vehicles(VehicleIndex).ControlD.Steer = 1
                                Vehicles(VehicleIndex).BotD.ChangeSteeringTimer = 0.5
                            Else
                                'Left
                                Vehicles(VehicleIndex).ControlD.Steer = -1
                                Vehicles(VehicleIndex).BotD.ChangeSteeringTimer = 0.5
                            End If
                        End If
                    Else
                        'Reverse
                        If Vehicles(VehicleIndex).BotD.NewActionTimer < 0.001 Then
                            If Obstacle(Dir_Ahead) < 0 And Obstacle(Dir_LeftAhead) < 0 And Obstacle(Dir_RightAhead) < 0 And Obstacle(Dir_Back) < 0 And Obstacle(Dir_LeftBack) < 0 And Obstacle(Dir_RightBack) < 0 Then
                                Vehicles(VehicleIndex).BotD.DesperateCount = Vehicles(VehicleIndex).BotD.DesperateCount + 1: Vehicles(VehicleIndex).BotD.Desperate = 0.5 * Vehicles(VehicleIndex).BotD.DesperateCount
                                Vehicles(VehicleIndex).BotD.NewActionTimer = 1.5
                                Vehicles(VehicleIndex).BotD.PreferDirection = True
                                Vehicles(VehicleIndex).BotD.PreferredDirection = Int(Rnd * NumberOfDirections)
                            End If
                        End If
                        If Obstacle(Dir_Back) > Obstacle(Dir_LeftBack) And Obstacle(Dir_Back) > Obstacle(Dir_RightBack) Then
                            'Straight
                            Vehicles(VehicleIndex).ControlD.Steer = 0
                            Vehicles(VehicleIndex).BotD.ChangeSteeringTimer = 0.2
                        Else
                            If Obstacle(Dir_LeftBack) < Obstacle(Dir_RightBack) Then
                                'Right
                                Vehicles(VehicleIndex).ControlD.Steer = 1
                                Vehicles(VehicleIndex).BotD.ChangeSteeringTimer = 0.2
                            Else
                                'Left
                                Vehicles(VehicleIndex).ControlD.Steer = -1
                                Vehicles(VehicleIndex).BotD.ChangeSteeringTimer = 0.2
                            End If
                        End If
                    End If
                End If
            End If
            
            'Prefer a direction
            If Vehicles(VehicleIndex).BotD.NewActionTimer < 0.001 Then
                If Vehicles(VehicleIndex).BotD.CollidedWithItemLastStep Then
                    Vehicles(VehicleIndex).BotD.NewActionTimer = 1
                    Vehicles(VehicleIndex).BotD.DesperateCount = Vehicles(VehicleIndex).BotD.DesperateCount + 1: Vehicles(VehicleIndex).BotD.Desperate = 1 * Vehicles(VehicleIndex).BotD.DesperateCount
                    Vehicles(VehicleIndex).BotD.PreferDirection = True
                    Vehicles(VehicleIndex).BotD.PreferredDirection = Int(Rnd * NumberOfDirections)
                ElseIf OwnSpeed < 0.4 And OwnAxisSystem.ZAxis.Y > 0.4 Then
                    'Stuck against a hill
                    Vehicles(VehicleIndex).BotD.NewActionTimer = 3
                    Vehicles(VehicleIndex).BotD.DesperateCount = Vehicles(VehicleIndex).BotD.DesperateCount + 1: Vehicles(VehicleIndex).BotD.Desperate = 2 * Vehicles(VehicleIndex).BotD.DesperateCount
                    Vehicles(VehicleIndex).BotD.PreferDirection = True
                    If Rnd < 0.5 Then
                        Vehicles(VehicleIndex).BotD.PreferredDirection = Int(Dir_LeftBack)
                    Else
                        Vehicles(VehicleIndex).BotD.PreferredDirection = Int(Dir_RightBack)
                    End If
                End If
            End If
            
            'Break if needed
            If NeedToBreak Then
                Vehicles(VehicleIndex).ControlD.Accelerate = 0
                Vehicles(VehicleIndex).ControlD.Brake = 1
            Else
                Vehicles(VehicleIndex).ControlD.Brake = 0
            End If
            
            'Find a target in world space
            If DoHeavyCalculations Then
                Dim TargetDistance As Single
                Dim TargetIndex As Integer
                Dim TargetUtility As Single
                Dim BestTargetIndex As Integer
                Dim BestTargetUtility As Single
                BestTargetUtility = -1
                For TargetIndex = 0 To NumberOfVehicles - 1
                    If Vehicles(TargetIndex).TeamID <> Vehicles(VehicleIndex).TeamID And Vehicles(TargetIndex).Working Then
                        'Enemy
                        TargetDistance = DistVector3(Vehicles(TargetIndex).LastPosition, Vehicles(VehicleIndex).LastPosition)
                        If TargetDistance < BotSkill_MaximumViewDistance Then
                            'Line of sight
                            Dim IR As IntersectionResult
                            IR = LineIntersection(Vehicles(VehicleIndex).LastPosition, AddVector3(Vehicles(TargetIndex).LastPosition, GaussianRandom3D(1)), True, True, False, VehicleIndex)
                            If Not (IR.Collided) Then
                                'The utility function
                                TargetUtility = 1 / TargetDistance
                                
                                'Keep the best target
                                If TargetUtility > BestTargetUtility Then
                                    BestTargetIndex = TargetIndex
                                    BestTargetUtility = TargetUtility
                                End If
                            End If
                        End If
                    End If
                Next TargetIndex
                
                'Remember the best target location
                If BestTargetUtility > -0.5 Then
                    Vehicles(VehicleIndex).BotD.TargetIndex = BestTargetIndex
                Else
                    Vehicles(VehicleIndex).BotD.TargetIndex = -1
                End If
            End If
            
            'Shoot at the target by converting it's location to object space
            Dim TargetLongitude As Single
            Dim TargetLattitude As Single
            Dim CylinderDist As Single
            If Vehicles(VehicleIndex).BotD.TargetIndex > -1 Then
                Dim TargetLocation_ObjectSpace As Vector3
                Dim ShootingOrigin As Vector3
                ShootingOrigin = AddVector3(OwnPosition, MulVecMat3(VehicleTypes(VehicleTypeIndex).BD.GunPivot, OwnAxisSystem))
                TargetLocation_ObjectSpace = MulVecMat3(SubVector3(Vehicles(Vehicles(VehicleIndex).BotD.TargetIndex).LastPosition, ShootingOrigin), TransposeMatrix3(OwnAxisSystem))
                TargetLongitude = DirToAngle(MakeVector2(TargetLocation_ObjectSpace.X, -TargetLocation_ObjectSpace.Z))
                CylinderDist = AbsVector2(MakeVector2(TargetLocation_ObjectSpace.X, TargetLocation_ObjectSpace.Z))
                If CylinderDist > 0.5 Then
                    TargetLattitude = Atn(TargetLocation_ObjectSpace.Y / CylinderDist)
                Else
                    TargetLattitude = 0
                End If
                
                'Poor aiming
                TargetLongitude = TargetLongitude + (((Sin(Timer * 4.8627) * 0.8) + (Sin(Timer * 7.3653) * 0.2)) * BotSkill_Accuracy)
                TargetLattitude = TargetLattitude + (((Sin(Timer * 3.7451) * 0.8) + (Sin(Timer * 8.1753) * 0.2)) * BotSkill_Accuracy)
            Else
                TargetLongitude = 0
                TargetLattitude = 0
                CylinderDist = 0
            End If
            Dim TargetDiff As Vector2
            TargetDiff = MakeVector2(FixRadianCenter(TargetLongitude - Vehicles(VehicleIndex).GunLongitude), TargetLattitude - Vehicles(VehicleIndex).GunLattitude)
            Vehicles(VehicleIndex).ControlD.GunAim.X = Clamp(-10, TargetDiff.X * 50, 10)
            Vehicles(VehicleIndex).ControlD.GunAim.Y = Clamp(-10, TargetDiff.Y * 50, 10)
            
            'Shoot if needed
            If Vehicles(VehicleIndex).BotD.TargetIndex > -1 And AbsVector2(TargetDiff) < 0.1 Then
                'Shoot
                Vehicles(VehicleIndex).ControlD.Shoot = True
                
                'Using secondary fire mode because of the bot have poor aiming to let the player win
                Vehicles(VehicleIndex).ControlD.FireMode = FireMode_Secondary
            Else
                'Do not shoot
                Vehicles(VehicleIndex).ControlD.Shoot = False
            End If
        End If
        
        Vehicles(VehicleIndex).BotD.DesperateCount = Min_Long(4, Vehicles(VehicleIndex).BotD.DesperateCount)
    End Select
    
    'Automatically turn over when upside down or stuck on a rock
    If Vehicles(VehicleIndex).BotD.WheelContact = False And Vehicles(VehicleIndex).BotD.CollidedWithItemLastStep And Abs(OwnAngularVelocity_ObjectSpace.X) < 2 Then 'On top of a rock
        'Magnify any angular velocity along object X axis
        Vehicles(VehicleIndex).ControlD.TiltForward = Sgn(OwnAngularVelocity_ObjectSpace.X)
    ElseIf OwnAxisSystem.ZAxis.Y > 0.7 Then 'On it's back
        'Tilt forward
        Vehicles(VehicleIndex).ControlD.TiltSideway = 0
        Vehicles(VehicleIndex).ControlD.TiltForward = 1
    ElseIf OwnAxisSystem.ZAxis.Y < -0.7 Then 'On it's front
        'Tilt backward
        Vehicles(VehicleIndex).ControlD.TiltSideway = 0
        Vehicles(VehicleIndex).ControlD.TiltForward = -1
    ElseIf OwnAxisSystem.YAxis.Y < -0.2 And Abs(OwnAngularVelocity_ObjectSpace.Z) < 2 Then 'Up side down
        'Magnify any angular velocity along object Z axis
        Vehicles(VehicleIndex).ControlD.TiltSideway = Sgn(OwnAngularVelocity_ObjectSpace.Z)
        Vehicles(VehicleIndex).ControlD.TiltForward = 0
    Else
        'Do nothing
        Vehicles(VehicleIndex).ControlD.TiltSideway = 0
        Vehicles(VehicleIndex).ControlD.TiltForward = 0
    End If
End Sub

Public Sub Vehicle_Damage(VehicleIndex As Integer, DirectHitDamage As Single, Position As Vector3, Radius As Single, HitImpulse As Vector3)
    Dim V As Long
    Dim VT As Long
    Dim W As Long
    Dim DamageRatio As Single
    Dim Hit As Boolean
    Dim DirectHit As Boolean
    For V = 0 To NumberOfVehicles - 1
        VT = Vehicles(V).VehicleTypeIndex
        If V = VehicleIndex Then
            'Direct hit
            Vehicles(V).CurrentHealth = Vehicles(V).CurrentHealth - DirectHitDamage
            DamageRatio = 1
            Hit = True
            DirectHit = True
        Else
            If Radius > 0.00001 Then
                'Not a direct hit
                Dim Distance As Single
                Distance = DistVector3(Vehicles(V).LastPosition, Position)
                If Distance < Radius Then
                    DamageRatio = (Radius - Distance) / Radius
                    Hit = True
                Else
                    DamageRatio = 0
                    Hit = False
                End If
            Else
                DamageRatio = 0
                Hit = False
            End If
            DirectHit = False
        End If
        
        If Hit Then
            'Reduce health
            Vehicles(V).CurrentHealth = Vehicles(V).CurrentHealth - (DirectHitDamage * DamageRatio)
            
            'Apply impulse to the rigid body
            Dim Impulse As Vector3
            Impulse = MulVector3(HitImpulse, DamageRatio)
            If DirectHit Then
                Dim RelativePointOfImpact As Vector3
                RelativePointOfImpact = SubVector3(Position, Vehicles(V).LastPosition)
                frmMain.DGE.RigidBody_Dynamic_ApplyImpulse_Point Vehicles(V).RigidBody, Impulse.X, Impulse.Y, Impulse.Z, RelativePointOfImpact.X, RelativePointOfImpact.Y, RelativePointOfImpact.Z: RE
            Else
                Dim OldStrength As Single
                Dim NewDirection As Vector3
                OldStrength = AbsVector3(Impulse)
                NewDirection = NormalVector3(ToVector3(Position, Vehicles(V).LastPosition))
                Impulse = MulVector3(NewDirection, OldStrength)
                frmMain.DGE.RigidBody_Dynamic_ApplyImpulse_Linear Vehicles(V).RigidBody, Impulse.X, Impulse.Y, Impulse.Z: RE
            End If
            
            'See if it died
            If Vehicles(V).CurrentHealth <= 0 Then
                'No one is controlling the vehicle because anyone inside is now dead
                Vehicles(V).ControlT = ControlType_None
                
                'Make sure that no one else can use the vehicle
                Vehicles(V).Working = False
                
                'Set the team to neutral
                Vehicles(V).TeamID = Team_None
                
                'Stop wheels
                Vehicles(V).Wheels.GoalYAngle = 0
                For W = 0 To VehicleTypes(VT).Wheels.NumberOfWheels - 1
                    Vehicles(V).Wheels.Wheels(W).XAnglularVelocity = 0
                Next W
                
                'Stop looped sounds
                Sound_StopVehicleLoop NumberOfVehicles
            End If
        End If
    Next V
End Sub

Public Function GetWheelOffsetRatio(ByRef TheWheel As Wheel, ByRef TheWheelProperty As WheelProperty, WheelRadius As Single, MinOffset As Single, BodyCenter As Vector3, BodyOrientation As Matrix3, VehicleIndex As Integer) As IntersectionResult
    Dim Wheel_WorldPos As Vector3
    Dim Wheel_RelPos As Vector3
    Dim LineStart As Vector3
    Dim LineEnd As Vector3
    Wheel_RelPos = MulVecMat3(TheWheelProperty.Location, BodyOrientation)
    Wheel_WorldPos = AddVector3(BodyCenter, Wheel_RelPos)
    LineStart = AddVector3(Wheel_WorldPos, MulVector3(BodyOrientation.YAxis, WheelRadius))
    LineEnd = AddVector3(Wheel_WorldPos, MulVector3(BodyOrientation.YAxis, -WheelRadius + MinOffset))
    GetWheelOffsetRatio = LineIntersection(LineStart, LineEnd, True, True, False, VehicleIndex)
End Function

Public Sub PrepareVehicleForRendering(VehicleIndex As Integer, AnimationTimeStep As Single, InterpolationTime As Single)
    Dim VehicleTypeIndex As Long
    Dim BodyAxisSystem As Matrix4
    Dim BodyCenter As Vector3
    Dim BodyOrientation As Matrix3
    Dim StartPoint As Vector3
    Dim Direction As Vector3
    Dim EndPoint As Vector3
    Dim DotPos As Vector3
    Dim IntersectedEndPoint As Vector3
    Dim IR As IntersectionResult
    Dim W As Long
    Const LaserThickness As Single = 0.02
    
    'Get the type index
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    
    'Get axis system
    frmMain.DGE.Instance_Get4x4System_OutM4 Vehicles(VehicleIndex).Instance_Body: BodyAxisSystem = GetMatrix4FromMatrixBuffer: RE
    BodyCenter = Vector4ToVector3(BodyAxisSystem.WAxis)
    BodyOrientation = Matrix4ToMatrix3(BodyAxisSystem)
    
    'Regeneration
    If Vehicles(VehicleIndex).CurrentHealth > 0 Then
        Vehicles(VehicleIndex).CurrentHealth = Min_Float(VehicleTypes(VehicleTypeIndex).MaxHealth, Vehicles(VehicleIndex).CurrentHealth + (VehicleTypes(VehicleTypeIndex).RegenerationPerSecond * AnimationTimeStep))
    End If
    
    'Get team color
    Dim TeamColor As Vector3
    If Vehicles(VehicleIndex).TeamID = Team_Player Then
        TeamColor = MakeVector3(0.1, 0.5, 0.1)
    ElseIf Vehicles(VehicleIndex).TeamID = Team_Computer Then
        TeamColor = MakeVector3(0.5, 0.1, 0.1)
    Else
        TeamColor = MakeVector3(1, 1, 1)
    End If
    
    'Make the color darker when taking damage
    Dim BodyColor As Vector3
    BodyColor = MulVector3(TeamColor, Saturate(Vehicles(VehicleIndex).CurrentHealth / VehicleTypes(VehicleTypeIndex).MaxHealth))
    
    'Weapon
    If VehicleTypes(VehicleTypeIndex).BD.GunLongitude > -1 Then
        'Place the bone "GunLongitude"
        frmMain.DGE.BoneFrame_SetZAxis Vehicles(VehicleIndex).BoneFrame, VehicleTypes(VehicleTypeIndex).BD.GunLongitude, 0, 1, 0: RE
        frmMain.DGE.BoneFrame_SetYAxis Vehicles(VehicleIndex).BoneFrame, VehicleTypes(VehicleTypeIndex).BD.GunLongitude, Sin(Vehicles(VehicleIndex).GunLongitude), 0, Cos(Vehicles(VehicleIndex).GunLongitude): RE
    End If
    If VehicleTypes(VehicleTypeIndex).BD.GunLattitude > -1 Then
        'Get the gun location
        Dim InterpolatedGunLongitude As Single
        Dim InterpolatedGunLattitude As Single
        Dim GunAxisSystem_Local As Matrix3
        Dim GunAxisSystem_World As Matrix3
        Dim ShootingDirection As Vector3
        Dim ShootingOrigin As Vector3
        If VehicleIndex = Vehicle_Player Then
            'Take the player's aiming as it is
            InterpolatedGunLongitude = Vehicles(VehicleIndex).GunLongitude
            InterpolatedGunLattitude = Vehicles(VehicleIndex).GunLattitude
        Else
            'Interpolate bot's aiming
            InterpolatedGunLongitude = Vehicles(VehicleIndex).GunLongitude + Vehicles(VehicleIndex).ControlD.GunAim.X * InterpolationTime
            InterpolatedGunLattitude = Clamp(VehicleTypes(VehicleTypeIndex).GunLattitude_Min, Vehicles(VehicleIndex).GunLattitude + Vehicles(VehicleIndex).ControlD.GunAim.Y * InterpolationTime, VehicleTypes(VehicleTypeIndex).GunLattitude_Max)
        End If
        GunAxisSystem_Local = MakeAxisSystem_Polar(InterpolatedGunLongitude, InterpolatedGunLattitude)
        GunAxisSystem_World = MulMatMat3(GunAxisSystem_Local, BodyOrientation)
        ShootingOrigin = AddVector3(BodyCenter, MulVecMat3(VehicleTypes(VehicleTypeIndex).BD.GunPivot, BodyOrientation))
        ShootingDirection = NormalVector3(GunAxisSystem_World.ZAxis)
        Vehicles(VehicleIndex).VisualGunDirection = ShootingDirection
        
        'Place the gun's bone
        frmMain.DGE.BoneFrame_SetZAxis Vehicles(VehicleIndex).BoneFrame, VehicleTypes(VehicleTypeIndex).BD.GunLattitude, GunAxisSystem_Local.ZAxis.X, GunAxisSystem_Local.ZAxis.Y, GunAxisSystem_Local.ZAxis.Z: RE
        frmMain.DGE.BoneFrame_SetYAxis Vehicles(VehicleIndex).BoneFrame, VehicleTypes(VehicleTypeIndex).BD.GunLattitude, GunAxisSystem_Local.YAxis.X, GunAxisSystem_Local.YAxis.Y, GunAxisSystem_Local.YAxis.Z: RE
    End If
    If VehicleTypes(VehicleTypeIndex).BD.GunLattitude > -1 And Vehicles(VehicleIndex).Working Then
        'Place the laser beam
        StartPoint = ShootingOrigin
        EndPoint = AddVector3(StartPoint, MulVector3(ShootingDirection, Terrain_GroundModelRadius))
        IR = LineIntersection(StartPoint, EndPoint, True, True, True, VehicleIndex)
        If IR.Collided Then
            IntersectedEndPoint = IR.Position
        Else
            IntersectedEndPoint = EndPoint
        End If
        frmMain.DGE.Instance_SetPosition Vehicles(VehicleIndex).Instance_Laser, StartPoint.X, StartPoint.Y, StartPoint.Z: RE
        SetInstanceAxisSystem Vehicles(VehicleIndex).Instance_Laser, MakeMatrix3(MulVector3(GunAxisSystem_World.XAxis, LaserThickness), MulVector3(GunAxisSystem_World.YAxis, LaserThickness), ToVector3(StartPoint, IntersectedEndPoint))
        DotPos = AddVector3(IntersectedEndPoint, MulVector3(IR.Normal, 0.1))
        frmMain.DGE.LightSource_SetPos Vehicles(VehicleIndex).LaserDotLightSource, DotPos.X, DotPos.Y, DotPos.Z: RE
        
        'Show the laser beam
        frmMain.DGE.Instance_SetVisibility Vehicles(VehicleIndex).Instance_Laser, 0, True: RE
        frmMain.DGE.LightSource_SetEnabled Vehicles(VehicleIndex).LaserDotLightSource, IR.Collided: RE
        
        'Set the laser beam and light source colors
        frmMain.DGE.Instance_SetColor Vehicles(VehicleIndex).Instance_Laser, TeamColor.X * 10, TeamColor.Y * 10, TeamColor.Z * 10, 0.005: RE
        frmMain.DGE.LightSource_SetColor Vehicles(VehicleIndex).LaserDotLightSource, TeamColor.X * 10, TeamColor.Y * 10, TeamColor.Z * 10: RE
    Else
        'Hide the laser beam
        frmMain.DGE.Instance_SetVisibility Vehicles(VehicleIndex).Instance_Laser, 0, False: RE
        frmMain.DGE.LightSource_SetEnabled Vehicles(VehicleIndex).LaserDotLightSource, False: RE
    End If
    
    'Set the vehicle's color
    frmMain.DGE.Instance_SetColor Vehicles(VehicleIndex).Instance_Body, BodyColor.X, BodyColor.Y, BodyColor.Z, 1: RE
    
    'Place the wheels from the body instance's interpolated position
    For W = 0 To VehicleTypes(VehicleTypeIndex).Wheels.NumberOfWheels - 1
        PrepareWheelForRendering VehicleIndex, Vehicles(VehicleIndex).Wheels.Wheels(W), VehicleTypes(VehicleTypeIndex).Wheels.Wheels(W), Vehicles(VehicleIndex).Instance_Wheel(W), BodyCenter, BodyOrientation, AnimationTimeStep
    Next W
End Sub

Public Sub PrepareWheelForRendering(VehicleIndex As Integer, ByRef TheWheel As Wheel, ByRef TheWheelProperty As WheelProperty, Instance As Long, BodyCenter As Vector3, BodyOrientation As Matrix3, AnimationTimeStep As Single)
    Dim IR As IntersectionResult
    Dim YOffset As Single
    Dim Radius As Single
    Dim MinOffset As Single
    Dim MaxOffset As Single
    Dim VehicleTypeIndex As Long
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    Radius = VehicleTypes(VehicleTypeIndex).Wheels.Radius
    MinOffset = VehicleTypes(VehicleTypeIndex).Wheels.MinOffset
    MaxOffset = VehicleTypes(VehicleTypeIndex).Wheels.MaxOffset
    
    IR = GetWheelOffsetRatio(TheWheel, TheWheelProperty, Radius, MinOffset, BodyCenter, BodyOrientation, VehicleIndex)
    If IR.Collided Then
        YOffset = Min_Float(((1 - IR.StartEndRatio) * Radius * 2) + MinOffset, MaxOffset)
    Else
        YOffset = MinOffset
    End If
    
    'Place the axis
    Dim LocalAxisPosition As Vector3
    LocalAxisPosition = AddVector3(TheWheelProperty.AxisLocation, MakeVector3(0, YOffset, 0))
    frmMain.DGE.BoneFrame_SetPosition Vehicles(VehicleIndex).BoneFrame, TheWheelProperty.BoneIndex, LocalAxisPosition.X, LocalAxisPosition.Y, LocalAxisPosition.Z
    
    'Place the wheel
    Dim WheelPosition As Vector3
    WheelPosition = AddVector3(BodyCenter, MulVecMat3(AddVector3(TheWheelProperty.Location, MakeVector3(0, YOffset, 0)), BodyOrientation))
    frmMain.DGE.Instance_SetPosition Instance, WheelPosition.X, WheelPosition.Y, WheelPosition.Z
    
    'Spinn
    TheWheel.XAngle = TheWheel.XAngle + (TheWheel.XAnglularVelocity * AnimationTimeStep)
    
    'Steer
    MoveCloserLinear Vehicles(VehicleIndex).Wheels.GraphicalYAngle, Vehicles(VehicleIndex).Wheels.GoalYAngle, AnimationTimeStep * TurningSpeed
    
    'Get the steering longitude if the wheel is used for steering
    Dim YAngle As Single
    If TheWheelProperty.Steering Then
        YAngle = Vehicles(VehicleIndex).Wheels.GraphicalYAngle
    Else
        YAngle = 0
    End If
    
    'Putting the rotation matrix on the left side creates a rotation in the local axis system
    Dim SteeringAxisSystem As Matrix3
    Dim SpinningAxisSystem As Matrix3
    SteeringAxisSystem = MulMatMat3(RotationMatrixY(YAngle), BodyOrientation)
    SpinningAxisSystem = MulMatMat3(RotationMatrixX(TheWheel.XAngle), SteeringAxisSystem)
    
    'Generate axis system for the wheel
    Dim ScaledAxisSystem As Matrix3
    ScaledAxisSystem = MulMatVec3(SpinningAxisSystem, MakeVector3(Radius, Radius, Radius))
    SetInstanceAxisSystem Instance, ScaledAxisSystem
End Sub

Public Sub Vehicle_ControlAndRun()
    Dim I As Integer
    Static HeavyIndex As Integer
    
    'Get input to the vehicles
    For I = 0 To NumberOfVehicles - 1
        ControlVehicle I, (I = HeavyIndex)
        'A separation between input and bound checks prevent bugs from an AI to break the game's rules by mistake
        ConvertInputToPower I
    Next I
    
    'Making all decisions before executing any of them makes sure that no AI get extra information from being executed last
    For I = 0 To NumberOfVehicles - 1
        RunVehicle I
    Next I
    
    'Let one vehicle at a time have the heavy calculations performed
    HeavyIndex = (HeavyIndex + 1) Mod NumberOfVehicles
End Sub

Public Sub Vehicle_PostPhysics()
    Dim I As Integer
    
    For I = 0 To NumberOfVehicles - 1
        'Store the position for easy access
        frmMain.DGE.RigidBody_GetPosition_OutV3 Vehicles(I).RigidBody: Vehicles(I).LastPosition = GetVector3FromMatrixBuffer: RE
        
        'Clear information about any previous collision
        Vehicles(I).BotD.CollidedWithItemLastStep = False
    Next I
End Sub

Public Sub Vehicle_PrepareForRendering(TimePassed As Single, InterpolationTime As Single)
    Dim I As Integer
    
    ControlPlayerGun
    
    'Prepare the vehicles for rendering
    For I = 0 To NumberOfVehicles - 1
        PrepareVehicleForRendering I, TimePassed, InterpolationTime
    Next I
End Sub

Public Sub Vehicle_Sensor_Collided(VehicleIndex As Integer)
    Vehicles(VehicleIndex).BotD.CollidedWithItemLastStep = True
End Sub

Public Function Vehicle_GetVisualVehicleTransformation(VehicleIndex As Integer) As Matrix4
    frmMain.DGE.Instance_Get4x4System_OutM4 Vehicles(VehicleIndex).Instance_Body: RE
    Vehicle_GetVisualVehicleTransformation = GetMatrix4FromMatrixBuffer
End Function

Public Function Vehicle_GetCopy(VehicleIndex As Integer) As Vehicle
    Vehicle_GetCopy = Vehicles(VehicleIndex)
End Function

Public Function GenerateRandomLocation(ReservedRadius As Single) As Matrix4
    Dim I As Long
    Dim RoadPos As Vector2
    Dim Pos As Vector3
    Dim Orientation As Matrix3
    For I = 0 To 1000
        RoadPos = RubberBand_GetPointFromPath(Rnd * RubberBand_NumberOfPoints)
        Pos = MakeVector3(RoadPos.X, 0, -RoadPos.Y)
        If Item_IsRadiusAvailable(Pos, ReservedRadius) Then
            Pos.Y = Terrain_GetHeightLinear(Pos.X, Pos.Z) + ReservedRadius
            Orientation = MakeAxisSystem_Polar(Rnd * PI * 2, 0)
            GenerateRandomLocation = MakeMatrix4(Vector3ToVector4(Orientation.XAxis, 0), Vector3ToVector4(Orientation.YAxis, 0), Vector3ToVector4(Orientation.ZAxis, 0), Vector3ToVector4(Pos, 1))
            Exit Function
        End If
    Next I
    GenerateRandomLocation = MakeUnitMatrix4
End Function

Private Sub ChangeGear(VehicleIndex As Integer, NewGear As Long)
    If Vehicles(VehicleIndex).Gear <> NewGear And Vehicles(VehicleIndex).ShortGearDelay < 0.0001 Then
        Vehicles(VehicleIndex).Gear = NewGear
        Vehicles(VehicleIndex).ShortGearDelay = 0.15
        Vehicles(VehicleIndex).LongGearDelay = 0.3
        PlaySound_CyclicIndex_3D SoundBuffer_Gear, False, 1, 1, Vehicles(VehicleIndex).LastPosition
    End If
End Sub

'Softly enforce that A * Ratio = B
Private Sub GearboxFriction(ByRef A As Single, ByRef B As Single, Ratio As Single, AffectA As Single, AffectB As Single, TimePerStep As Single)
    Static Diff As Single
    Diff = (A * Ratio) - B
    If Ratio > 0 Then
        A = A - (Diff * AffectA * TimePerStep)
        B = B + (Diff * AffectB * TimePerStep / Ratio)
    ElseIf Ratio < 0 Then
        A = A + (Diff * AffectA * TimePerStep)
        B = B - (Diff * AffectB * TimePerStep / Ratio)
    End If
End Sub

Public Sub RunVehicle(VehicleIndex As Integer)
    Dim I As Long
    Dim VehicleTypeIndex As Long
    Dim RigidBody As Long
    Dim ControlD As ControlData
    Dim PhysicalTransformation As Matrix4
    Dim BodyCenter As Vector3
    Dim BodyOrientation As Matrix3
    Dim LinearVelocity_WorldSpace As Vector3
    Dim AngularVelocity_WorldSpace As Vector3
    Dim LinearVelocity_ObjectSpace As Vector3
    Dim AngularVelocity_ObjectSpace As Vector3
    RigidBody = Vehicles(VehicleIndex).RigidBody
    ControlD = Vehicles(VehicleIndex).ControlD
    frmMain.DGE.RigidBody_GetTransformation_OutM4 RigidBody: PhysicalTransformation = GetMatrix4FromMatrixBuffer: RE
    BodyOrientation = Matrix4ToMatrix3(PhysicalTransformation)
    BodyCenter = Vector4ToVector3(PhysicalTransformation.WAxis)
    frmMain.DGE.RigidBody_GetLinearVelocity_OutV3 RigidBody: LinearVelocity_WorldSpace = GetVector3FromMatrixBuffer: RE
    frmMain.DGE.RigidBody_GetAngularVelocity_OutV3 RigidBody: AngularVelocity_WorldSpace = GetVector3FromMatrixBuffer: RE
    LinearVelocity_ObjectSpace = MulVecMat3(LinearVelocity_WorldSpace, TransposeMatrix3(Matrix4ToMatrix3(PhysicalTransformation)))
    AngularVelocity_ObjectSpace = MulVecMat3(AngularVelocity_WorldSpace, TransposeMatrix3(Matrix4ToMatrix3(PhysicalTransformation)))
    
    'Get the type index
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    
    Dim WheelIR As IntersectionResult
    Dim OffsetRatio As Single
    Dim Pressure As Single
    Dim TotalPressure As Single
    
    If Vehicles(VehicleIndex).Working Then
        'Set the steering goal
        Dim GoStraight As Single
        GoStraight = Max_Float(0, (Abs(LinearVelocity_ObjectSpace.Z) * 0.1) - (Abs(LinearVelocity_ObjectSpace.X) * 0.2))
        Vehicles(VehicleIndex).Wheels.GoalYAngle = Clamp(-0.5, ControlD.Steer * 0.7 / (1 + GoStraight), 0.5)
        
        'Turn the wheels physically
        MoveCloserLinear Vehicles(VehicleIndex).Wheels.PhysicalYAngle, Vehicles(VehicleIndex).Wheels.GoalYAngle, TimeStep * TurningSpeed
        
        TotalPressure = 0
        Vehicles(VehicleIndex).BotD.WheelContact = True
        If BodyOrientation.YAxis.Y > 0 Then
            For I = 0 To VehicleTypes(VehicleTypeIndex).Wheels.NumberOfWheels - 1
                WheelIR = GetWheelOffsetRatio(Vehicles(VehicleIndex).Wheels.Wheels(I), VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I), VehicleTypes(VehicleTypeIndex).Wheels.Radius, VehicleTypes(VehicleTypeIndex).Wheels.MinOffset, BodyCenter, BodyOrientation, VehicleIndex)
                OffsetRatio = (1 - WheelIR.StartEndRatio)
                
                If WheelIR.Collided Then
                    Dim SpringPressure As Single
                    Dim DamperPressure As Single
                    SpringPressure = (OffsetRatio ^ 2) * VehicleTypes(VehicleTypeIndex).Wheels.SpringForce
                    DamperPressure = (SpringPressure - Vehicles(VehicleIndex).Wheels.Wheels(I).PreviousSpringPressure) * VehicleTypes(VehicleTypeIndex).Wheels.Damping / TimeStep
                    Pressure = Min_Float(VehicleTypes(VehicleTypeIndex).Wheels.MaxForce, SpringPressure + DamperPressure)
                    TotalPressure = TotalPressure + Pressure
                    
                    'Make friction
                    Dim RelativeCollisionPoint As Vector3
                    Dim SteeringAxisSystem As Matrix3
                    Dim WheelSpinnVelocity As Vector3
                    Dim WheelPointVelocity As Vector3
                    Dim ForcePosition As Vector3
                    Dim Friction As Vector3
                    Dim FrictionSpeed As Single
                    RelativeCollisionPoint = SubVector3(WheelIR.Position, BodyCenter)
                    Dim YAngle As Single
                    If VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I).Steering Then
                        YAngle = Vehicles(VehicleIndex).Wheels.PhysicalYAngle
                    Else
                        YAngle = 0
                    End If
                    SteeringAxisSystem = MulMatMat3(RotationMatrixY(YAngle), BodyOrientation)
                    
                    WheelSpinnVelocity = MulVecMat3(MakeVector3(0, 0, -Vehicles(VehicleIndex).Wheels.Wheels(I).XAnglularVelocity * VehicleTypes(VehicleTypeIndex).Wheels.Radius), SteeringAxisSystem)
                    WheelPointVelocity = AddVector3(GetVelocityAtPointFromRigidBody(RigidBody, WheelIR.Position), WheelSpinnVelocity)
                    
                    'Project the velocity along the ground to get the speed and velocity's direction
                    Friction = ProjectToPlane(WheelPointVelocity, WheelIR.Normal)
                    
                    'Measure the speed of friction
                    FrictionSpeed = AbsVector3(Friction)
                    
                    'Combine friction from 2 materials
                    Dim WheelFriction As AdvancedFriction
                    Dim GroundFriction As AdvancedFriction
                    Dim CombinedFriction As AdvancedFriction
                    Dim HardSurface As Boolean
                    WheelFriction = VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I).Friction
                    If WheelIR.Type <> IRT_Ground Or Terrain_OnRoad(WheelIR.Position.X, WheelIR.Position.Z) Then
                        'Road
                        GroundFriction = MakeAdvancedFriction(0.9, 100, 8, 2, 80)
                        HardSurface = True
                    Else
                        'Dirt
                        GroundFriction = MakeAdvancedFriction(0.3, 2, 8, 3, 20)
                        HardSurface = False
                        
                        '¤¤¤¤ Test
                        'The road is often detected as mud
                        'Particle_Create WheelIR.Position, MakeVector3(0, 0.2, 0), 0, ParticleType_WaterCircle, VehicleIndex
                    End If
                    CombinedFriction = CombineFriction(WheelFriction, GroundFriction)
                    
                    Dim ParticlePos As Vector3
                    Dim ParticleVel As Vector3
                    ParticlePos = AddVector3(WheelIR.Position, AddVector3(GaussianRandom3D(0.5), MulVector3(WheelIR.Normal, 0.3)))
                    
                    'Particles
                    If FrictionSpeed >= CombinedFriction.Treshold Then
                        If HardSurface Then
                            'Spawn particles from burning rubber
                            If Rnd < 0.5 Then
                                ParticleVel = MakeVector3(0, 0, 0)
                                Particle_Create ParticlePos, ParticleVel, 0, ParticleType_Fog, VehicleIndex
                            End If
                        Else
                            'Spawn particles from the ground
                            If Rnd < 0.5 Then
                                ParticleVel = AddVector3(Friction, AddVector3(GaussianRandom3D(0.5), MulVector3(WheelIR.Normal, FrictionSpeed * 0.4 * Rnd)))
                                Particle_Create ParticlePos, ParticleVel, 0, ParticleType_DirtSplash, VehicleIndex
                            End If
                        End If
                    End If
                    
                    'Sound
                    Const SlideSoundMultiplier As Single = 0.05
                    If FrictionSpeed >= CombinedFriction.Treshold Then
                        If HardSurface Then
                            Sound_UseSoundEffect Vehicles(VehicleIndex).SoundEffects.Slide, (FrictionSpeed - CombinedFriction.Treshold) * SlideSoundMultiplier
                        End If
                    End If
                    
                    Friction = MulVector3(NormalVector3(Friction), -Saturate(OffsetRatio * 30) * FrictionFromSpeed(FrictionSpeed, CombinedFriction, VehicleTypes(VehicleTypeIndex).Mass * WorstFrictionPerMass, TimeStep))
                    
                    'Decide where to place the force
                    ForcePosition = RelativeCollisionPoint
                    
                    'Lift the location of the force to simulate safety features
                    Dim LiftDistance As Single
                    LiftDistance = (VehicleTypes(VehicleTypeIndex).Wheels.Radius - VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I).Location.Y) * VehicleTypes(VehicleTypeIndex).RollPrevention
                    ForcePosition = AddVector3(ForcePosition, MulVector3(BodyOrientation.YAxis, LiftDistance))
                    
                    'Apply the force
                    frmMain.DGE.RigidBody_Dynamic_ApplyForce_Point RigidBody, (WheelIR.Normal.X * Pressure) + Friction.X, (WheelIR.Normal.Y * Pressure) + Friction.Y, (WheelIR.Normal.Z * Pressure) + Friction.Z, ForcePosition.X, ForcePosition.Y, ForcePosition.Z
                    
                    'Feedback from friction to rotation
                    Dim RotationFeedback As Single
                    RotationFeedback = -DotProduct3(Friction, SteeringAxisSystem.ZAxis) / (VehicleTypes(VehicleTypeIndex).Wheels.Radius * VehicleTypes(VehicleTypeIndex).Wheels.Mass)
                    Increase Vehicles(VehicleIndex).Wheels.Wheels(I).XAnglularVelocity, RotationFeedback, TimeStep
                Else
                    'A wheel is not in contact with something to drive on
                    Vehicles(VehicleIndex).BotD.WheelContact = False
                End If
                
                Vehicles(VehicleIndex).Wheels.Wheels(I).PreviousSpringPressure = SpringPressure
            Next I
        End If
        
        'Run the engine when driving forward or backward
        Dim RunEngine As Single
        Dim GasInjection As Single
        GasInjection = Abs(ControlD.Accelerate * VehicleTypes(VehicleTypeIndex).MaxGasInjection)
        
        'Prevent the engine from running backwards
        Vehicles(VehicleIndex).EngineSpeed = Max_Float(0, Vehicles(VehicleIndex).EngineSpeed)
        
        'Calculate the engine's acceleration
        RunEngine = AccelerationFromEngineSpeed(Vehicles(VehicleIndex).EngineSpeed, GasInjection, VehicleTypes(VehicleTypeIndex).EngineVolume)
        
        'Make sounds from fuel injection
        Sound_UseSoundEffect Vehicles(VehicleIndex).SoundEffects.FuelAcceleration, RunEngine
        
        'Use an electric start engine to assist the fuel engine when the engine speed is low
        RunEngine = RunEngine + (VehicleTypes(VehicleTypeIndex).StartEngineAcceleration * Saturate(InverseLerp(21, 19, Vehicles(VehicleIndex).EngineSpeed)))
        
        'Count down gear timers
        CountDown Vehicles(VehicleIndex).ShortGearDelay
        CountDown Vehicles(VehicleIndex).LongGearDelay
        CountDown Vehicles(VehicleIndex).DifferentialLockDelay
        
        'Automatic gearbox
        If ControlD.Accelerate > 0 Then
            If Vehicles(VehicleIndex).LongGearDelay < 0.0001 Or Vehicles(VehicleIndex).Gear = 0 Then
                ChangeGear VehicleIndex, BestForwardGear(VehicleIndex)
            End If
        ElseIf ControlD.Accelerate < 0 Then
            ChangeGear VehicleIndex, 0
        End If
        
        Dim ClutchConnection As Boolean
        ClutchConnection = Not (Abs(ControlD.Brake) > 0.1 Or Abs(ControlD.Accelerate) < 0.1 Or Vehicles(VehicleIndex).ShortGearDelay > 0.0001) 'Disconnect the clutch when breaking, not accelerating or changing gear
        
        'Gas
        Increase Vehicles(VehicleIndex).EngineSpeed, RunEngine, TimeStep
        
        'Engine resistance
        Vehicles(VehicleIndex).EngineSpeed = Vehicles(VehicleIndex).EngineSpeed * ((1 - VehicleTypes(VehicleTypeIndex).EngineSpeedResistance) ^ TimeStep)
        
        'Gearbox resistance
        Vehicles(VehicleIndex).GearboxSpeed = Vehicles(VehicleIndex).GearboxSpeed * ((1 - VehicleTypes(VehicleTypeIndex).GearboxResistance) ^ TimeStep)
        
        'Exponentially enforce the relation (GearBoxSpeed = EngineSpeed * Ratio) in both ways
        'Modifying their relation will falsify the automatic transmission's correctness
        If ClutchConnection Then
            GearboxFriction Vehicles(VehicleIndex).EngineSpeed, Vehicles(VehicleIndex).GearboxSpeed, RatioFromGearBox(Vehicles(VehicleIndex).Gear) * VehicleTypes(VehicleTypeIndex).GearMultiplier, 10, 10, TimeStep
        End If
        
        'Get the difference between gearbox and wheel speeds
        Dim WheelVelocitySum As Single
        Dim NumberOfDrivingWheels As Long
        Dim Diff As Single
        WheelVelocitySum = 0
        NumberOfDrivingWheels = 0
        For I = 0 To VehicleTypes(VehicleTypeIndex).Wheels.NumberOfWheels - 1
            If VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I).Driving Then
                WheelVelocitySum = WheelVelocitySum + Vehicles(VehicleIndex).Wheels.Wheels(I).XAnglularVelocity
                NumberOfDrivingWheels = NumberOfDrivingWheels + 1
            End If
        Next I
        If NumberOfDrivingWheels > 0 Then
            Diff = (Vehicles(VehicleIndex).GearboxSpeed - (WheelVelocitySum / NumberOfDrivingWheels))
        Else
            Diff = 0
        End If
        
        'Connect gearbox to wheels
        Vehicles(VehicleIndex).GearboxSpeed = Vehicles(VehicleIndex).GearboxSpeed - (Diff * 20 * TimeStep)
        For I = 0 To VehicleTypes(VehicleTypeIndex).Wheels.NumberOfWheels - 1
            If VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I).Driving Then
                Increase Vehicles(VehicleIndex).Wheels.Wheels(I).XAnglularVelocity, (Diff * 20) / VehicleTypes(VehicleTypeIndex).Wheels.Mass, TimeStep
            End If
        Next I
        
        'Differential lock
        Dim DifferentialLock As Boolean
        DifferentialLock = VehicleTypes(VehicleTypeIndex).Wheels.DifferentialLock And Abs(ControlD.Steer) < 0.1
        If Vehicles(VehicleIndex).DifferentialLockDelay < 0.0001 And Vehicles(VehicleIndex).DifferentialLocked <> DifferentialLock Then
            Vehicles(VehicleIndex).DifferentialLocked = DifferentialLock
            Vehicles(VehicleIndex).DifferentialLockDelay = 0.2
            PlaySound_CyclicIndex_3D SoundBuffer_DifferentialLock, False, 1, 0.3, BodyCenter
        End If
        If Vehicles(VehicleIndex).DifferentialLocked Then
            Dim WA As Integer
            Dim WB As Integer
            WA = VehicleTypes(VehicleTypeIndex).Wheels.DifferentialLockWheelIndexA
            WB = VehicleTypes(VehicleTypeIndex).Wheels.DifferentialLockWheelIndexB
            Diff = (Vehicles(VehicleIndex).Wheels.Wheels(WA).XAnglularVelocity - Vehicles(VehicleIndex).Wheels.Wheels(WB).XAnglularVelocity) * 0.5 * TimeStep
            Vehicles(VehicleIndex).Wheels.Wheels(WA).XAnglularVelocity = Vehicles(VehicleIndex).Wheels.Wheels(WA).XAnglularVelocity - (Diff / VehicleTypes(VehicleTypeIndex).Wheels.Mass)
            Vehicles(VehicleIndex).Wheels.Wheels(WB).XAnglularVelocity = Vehicles(VehicleIndex).Wheels.Wheels(WB).XAnglularVelocity + (Diff / VehicleTypes(VehicleTypeIndex).Wheels.Mass)
        End If
        
        'Brake
        For I = 0 To VehicleTypes(VehicleTypeIndex).Wheels.NumberOfWheels - 1
            'Linear brake
            If VehicleTypes(VehicleTypeIndex).Wheels.Wheels(I).Braking Then
                MoveCloserLinear Vehicles(VehicleIndex).Wheels.Wheels(I).XAnglularVelocity, 0, ControlD.Brake * 2000 * TimeStep
            End If
        Next I
        
        Dim Contact As Single
        Contact = Saturate(TotalPressure * 5)
        
        'Tilt
        Dim TiltingSideway As Single
        Dim TiltingForward As Single
        TiltingSideway = ControlD.TiltSideway
        TiltingForward = ControlD.TiltForward
        ApplyForce_Angular RigidBody, MulVector3(BodyOrientation.ZAxis, TiltingSideway)
        ApplyForce_Angular RigidBody, MulVector3(BodyOrientation.XAxis, TiltingForward)
        
        'Change weapon
        If ControlD.SelectedWeapon <> Vehicles(VehicleIndex).WeaponIndex And Vehicles(VehicleIndex).SpawnTimer <= 0 Then
            Vehicles(VehicleIndex).SpawnTimer = Vehicles(VehicleIndex).SpawnTimer + ChangeWeaponDelay
            PlaySound_CyclicIndex_3D SoundBuffer_Ch, False, 1, 1, Vehicles(VehicleIndex).LastPosition
            Vehicles(VehicleIndex).WeaponIndex = ControlD.SelectedWeapon
        End If
        
        'Aim and shoot
        If VehicleTypes(VehicleTypeIndex).BD.GunLattitude > -1 Then 'Has a gun
            'Aim
            Vehicles(VehicleIndex).GunLongitude = Vehicles(VehicleIndex).GunLongitude + (ControlD.GunAim.X * TimeStep)
            Vehicles(VehicleIndex).GunLattitude = Clamp(VehicleTypes(VehicleTypeIndex).GunLattitude_Min, Vehicles(VehicleIndex).GunLattitude + (ControlD.GunAim.Y * TimeStep), VehicleTypes(VehicleTypeIndex).GunLattitude_Max)
            
            'Shoot
            Dim GunAxisSystem As Matrix3
            Dim ShootingDirection As Vector3
            Dim ShootingOrigin As Vector3
            GunAxisSystem = MulMatMat3(MakeAxisSystem_Polar(Vehicles(VehicleIndex).GunLongitude, Vehicles(VehicleIndex).GunLattitude), BodyOrientation)
            ShootingOrigin = AddVector3(BodyCenter, MulVecMat3(VehicleTypes(VehicleTypeIndex).BD.GunPivot, BodyOrientation))
            ShootingDirection = NormalVector3(GunAxisSystem.ZAxis)
            Vehicles(VehicleIndex).SpawnTimer = Vehicles(VehicleIndex).SpawnTimer - TimeStep
            Do Until ControlD.Shoot = False Or ControlD.SelectedWeapon <> Vehicles(VehicleIndex).WeaponIndex Or Vehicles(VehicleIndex).SpawnTimer > 0
                Vehicles(VehicleIndex).SpawnTimer = Vehicles(VehicleIndex).SpawnTimer + Weapons(Vehicles(VehicleIndex).WeaponIndex).SpawnDelay(ControlD.FireMode)
                
                'Play spawn sound
                Dim SpawnSound As Long
                SpawnSound = Weapons(Vehicles(VehicleIndex).WeaponIndex).SpawnSound
                If SpawnSound > 0 Then
                    PlaySound_CyclicIndex_3D SpawnSound, False, (Rnd * 0.2) + 0.8, (Rnd * 0.2) + 0.8, ShootingOrigin
                End If
                
                'Apply impulse from recoil
                Dim Recoil As Vector3
                Recoil = MulVector3(ShootingDirection, -Weapons(Vehicles(VehicleIndex).WeaponIndex).Recoil)
                frmMain.DGE.RigidBody_Dynamic_ApplyImpulse_Linear Vehicles(VehicleIndex).RigidBody, Recoil.X, Recoil.Y, Recoil.Z: RE
                
                'Create a number of projectiles
                For I = 1 To Weapons(Vehicles(VehicleIndex).WeaponIndex).SpawnCount
                    Particle_Create ShootingOrigin, MulVector3(AddVector3(ShootingDirection, GaussianRandom3D(Weapons(Vehicles(VehicleIndex).WeaponIndex).ParticleDirectionDeviation(ControlD.FireMode))), Weapons(Vehicles(VehicleIndex).WeaponIndex).ParticleSpeed), 0, Weapons(Vehicles(VehicleIndex).WeaponIndex).ParticleType, VehicleIndex
                Next I
            Loop
        End If
        If ControlD.Shoot = False Then
            Vehicles(VehicleIndex).SpawnTimer = Max_Float(0, Vehicles(VehicleIndex).SpawnTimer)
        End If
    End If
    
    'Float on water
    Dim MinHeight As Single
    Dim MaxHeight As Single
    Dim UnderWaterRatio As Single
    frmMain.DGE.RigidBody_GetBoundingBoxMinimum_OutV3 RigidBody: MinHeight = frmMain.DGE.GetY1: RE
    frmMain.DGE.RigidBody_GetBoundingBoxMaximum_OutV3 RigidBody: MaxHeight = frmMain.DGE.GetY1: RE
    If Terrain_WaterLevel > MinHeight Then
        UnderWaterRatio = Saturate((Terrain_WaterLevel - MinHeight) / (MaxHeight - MinHeight))
        frmMain.DGE.RigidBody_Dynamic_ApplyForce_Linear RigidBody, 0, UnderWaterRatio * VehicleTypes(VehicleTypeIndex).FloatForce, 0
    Else
        UnderWaterRatio = 0
    End If
    
    If Vehicles(VehicleIndex).Working Then
        'Avoid spinning out of control
        Dim MaximumStrength As Single
        Dim AntiSpinnForce As Vector3
        
        'Only avoid spinning when the driver try to go straight or spinn fast and have contact with the ground
        MaximumStrength = VehicleTypes(VehicleTypeIndex).MaxAntiSpinnForce * Max_Float(Saturate(1 - (Abs(ControlD.Steer) * 10)), Saturate(Abs(AngularVelocity_ObjectSpace.Y) - VehicleTypes(VehicleTypeIndex).MaxSpinn)) * Contact
        
        'Calculate the force
        AntiSpinnForce = MulVector3(BodyOrientation.YAxis, Clamp(-MaximumStrength, AngularVelocity_ObjectSpace.Y * -30, MaximumStrength))
        
        'Apply the force
        ApplyForce_Angular RigidBody, AntiSpinnForce
    End If
    
    'Down force
    Dim DownForce As Vector3
    DownForce = MulVector3(BodyOrientation.YAxis, -(Abs(LinearVelocity_ObjectSpace.Z) * VehicleTypes(VehicleTypeIndex).DownForce))
    ApplyForce_Linear RigidBody, DownForce
    
    'Set damping
    frmMain.DGE.RigidBody_SetLinearDamping RigidBody, Lerp(VehicleTypes(VehicleTypeIndex).LinearAirResistance, 0.8, Saturate(UnderWaterRatio * 10)): RE
    frmMain.DGE.RigidBody_SetAngularDamping RigidBody, Lerp(VehicleTypes(VehicleTypeIndex).AngularAirResistance, 0.8, Saturate(UnderWaterRatio * 10)): RE
    
    '¤¤¤¤ Temporary for debugging
    If frmMain.frmTest.Visible Then
        frmMain.lblEngineSpeed = "Engine speed = " & Vehicles(VehicleIndex).EngineSpeed
        frmMain.lblGearboxSpeed = "Gearbox = " & Vehicles(VehicleIndex).GearboxSpeed
        frmMain.lblGear = "Gear = " & Vehicles(VehicleIndex).Gear
        frmMain.lblLongDelay = "Long delay = " & Vehicles(VehicleIndex).LongGearDelay
        DebugPrint VehicleIndex
    End If
    
End Sub

Public Sub Vehicle_HandleSoundEffects_StepTime()
    Sound_CountedPhysicsSteps = Sound_CountedPhysicsSteps + 1
End Sub

Public Sub Vehicle_HandleSoundEffects_RenderTime(ByVal TimePassed As Single)
    Dim VehicleIndex As Integer
    For VehicleIndex = 0 To NumberOfVehicles - 1
        Sound_HandleVehicle Vehicles(VehicleIndex), VehicleIndex, TimePassed
        
        'Handle looped sounds that are not event driven
        HandleEngineSound SoundBuffer_EngineSpeed, VehicleIndex, Vehicles(VehicleIndex).EngineSpeed * 0.004, Lerp(0.2, 0.4, Saturate(Abs(Vehicles(VehicleIndex).ControlD.Accelerate)))
    Next VehicleIndex
    
    Sound_HandleVehicles
End Sub

Private Sub HandleEngineSound(SoundBuffer As Long, VehicleIndex As Integer, Speed As Single, Volume As Single)
    frmMain.DGE.Sound_Buffer_SetSpeed SoundBuffer, VehicleIndex, Speed
    frmMain.DGE.Sound_Buffer_SetVolume SoundBuffer, VehicleIndex, Saturate(Volume)
    frmMain.DGE.Sound_Buffer_3D_SetPosition SoundBuffer, VehicleIndex, Vehicles(VehicleIndex).LastPosition.X, Vehicles(VehicleIndex).LastPosition.Y, Vehicles(VehicleIndex).LastPosition.Z
End Sub

'¤¤¤¤
Private Sub DebugPrint(VehicleIndex As Integer)
    Dim VehicleTypeIndex As Integer, G As Long
    Dim Ratio As Single
    Dim NewResult As Single
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    For G = 1 To VehicleTypes(VehicleTypeIndex).HighestGear
        NewResult = GearUtility(G, VehicleIndex, 1)
        frmMain.frmGear(G).Width = Max_Float(1, NewResult) * 15
    Next G
    For G = 0 To VehicleTypes(VehicleTypeIndex).HighestGear
        If G = Vehicles(VehicleIndex).Gear Then
            frmMain.frmGear(G).BackColor = RGB(0, 255, 255)
        Else
            frmMain.frmGear(G).BackColor = RGB(0, 0, 255)
        End If
    Next G
End Sub

'GasInjection is the maximum amount of energy
'EngineVolume is the maximum amount of energy per engine speed
Private Function AccelerationFromEngineSpeed(EngineSpeed As Single, GasInjection As Single, EngineVolume As Single) As Single
    'The gas injection is limited by the engine volume
    AccelerationFromEngineSpeed = Clamp(0, Max_Float(0, EngineSpeed * EngineVolume), GasInjection)
End Function

Private Function GearUtility(G As Long, VehicleIndex As Integer, BonusMultiplier As Single)
    Dim Ratio As Single
    Dim R1 As Single, R2 As Single
    Dim PostRatioSpeed As Single
    Dim VehicleTypeIndex As Integer
    Dim NewResistance As Single, NewForce As Single, NewResult As Single
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    PostRatioSpeed = Vehicles(VehicleIndex).GearboxSpeed
    R1 = VehicleTypes(VehicleTypeIndex).EngineSpeedResistance
    R2 = VehicleTypes(VehicleTypeIndex).GearboxResistance
    Ratio = RatioFromGearBox(G) * VehicleTypes(VehicleTypeIndex).GearMultiplier
    NewResistance = PostRatioSpeed * ((R1 / Ratio ^ 2) + R2)
    NewForce = AccelerationFromEngineSpeed(PostRatioSpeed / Ratio, VehicleTypes(VehicleTypeIndex).MaxGasInjection, VehicleTypes(VehicleTypeIndex).EngineVolume) / Ratio
    If G = Vehicles(VehicleIndex).Gear Then
        GearUtility = (NewForce * BonusMultiplier) - NewResistance
    Else
        GearUtility = NewForce - NewResistance
    End If
    
    '¤¤¤¤ Plot for testing
    If BonusMultiplier = 1 Then
        Dim X As Single
        Dim Y As Single
        X = PostRatioSpeed * 5 * 15
        Y = frmMain.picDebug.Height - (GearUtility * 15)
        Select Case G
        Case 0
            Exit Function
        Case 1
            frmMain.picDebug.ForeColor = RGB(0, 255, 0)
        Case 2
            frmMain.picDebug.ForeColor = RGB(0, 192, 64)
        Case 3
            frmMain.picDebug.ForeColor = RGB(0, 128, 128)
        Case 4
            frmMain.picDebug.ForeColor = RGB(0, 64, 192)
        Case 5
            frmMain.picDebug.ForeColor = RGB(0, 0, 255)
        End Select
        
        frmMain.picDebug.Line (X, Y)-(X + 15, Y)
    End If
End Function

Private Function BestForwardGear(VehicleIndex As Integer) As Long
    Dim VehicleTypeIndex As Integer, G As Long
    Dim Ratio As Single
    Dim BestResult As Single, NewResult As Single
    VehicleTypeIndex = Vehicles(VehicleIndex).VehicleTypeIndex
    BestResult = -1000000
    BestForwardGear = 1
    For G = 1 To VehicleTypes(VehicleTypeIndex).HighestGear
        NewResult = GearUtility(G, VehicleIndex, CurrentGearBonusMultiplier)
        If NewResult > BestResult Then
            BestResult = NewResult
            BestForwardGear = G
        End If
    Next G
End Function
