в каком порядке происходит выполение разных блоков кода
принципы работы и основные функции этого кода? – This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
– If a copy of the bCDDL was not distributed with this
– file, You can obtain one at http://beamng.com/bCDDL-1.1.txt

local M = {}

M.torque = 0
M.gear = 0
M.rpm = 0
M.torqueTransmission = 0
M.brake = 0
M.throttle = 0
M.wheels = {}
M.wheelInfo = {}
M.engineDisabled = false
M.engineIgnition = true
M.absPulse = 0
M.fuelLeakRate = 0 – L/sec
M.fuel = 100
M.fuelCapacity = 100

– shifterMode = 0 : full manual
– shifterMode = 1 : manual with automatic clutch
– shifterMode = 2 : full automatic
M.shifterMode = 2 – initial value needs to fit to the two modes below
M.differentialMode = 0
M.autoClutch = true
M.manualShift = false
M.manualGear = 0
M.wheelCount = 0

local diffs = {}
local diffCount = 1
local diffTypeMap = {open = 0, lsd = 1}
local diffStateMap = {open = 0, closed = 1}
local avgDiffAV = 0
local invDiffCount = 1
local invWheelCount = 0
local propWheelsCount = 0
local engInertia = 0.1
local invEngInertia = 10
local halfInvEngInertia = invEngInertia * 0.5
local engAV = 0
local avgAV = 0
local axleAV = 0
local engFriction = 0
local brakingCoef = 0
local axleFriction = 50
local clutchTimer = 0
local clutchDuration = 0.5
local clutchRatio = 0
local invClutchDuration = 2
local viscousCoupling
local viscousCouplingOrig
local engIdleAV = 0
local engStallAV = 0
local engineWorkPerUpdate = 0
local invBurnEfficiencyFuelDensity = 0
local torsionCoef = 0
local twist = 0
local diffRatio = 2
local invDiffRatio = 0.5
local diffLimit = 0.75
local avgPropAV = 0
local minPropAV = 0
local maxPropAV = 0
local ABSduration = 1/15
local ABShalfDuration = ABSduration * 0.5
local ABStimer = 0
local maxGearRatio = 0
local idleTorque = 0
local maxTorque = 0

local function nop()
end

local function updateEngineState(dt)
local engine = v.data.engine
local torquediff
local torque = nil
local n, wd

--Calc wheel sided engine AV
local wheelBasedEngAV = avgDiffAV * engine.gears[M.gear]

– manual vs automatic clutch
if M.autoClutch then
– auto clutch
if clutchTimer > 0 then
clutchTimer = math.max(0, clutchTimer - dt)
end

clutchRatio = (clutchDuration - clutchTimer) * invClutchDuration
end

--Calc torque from engine rpm
torque = engine.torqueCurve[math.floor(engAV * 9.5493)] or 0
torque = torque * M.throttle

--Idle torque
if engAV < engIdleAV then
torque = math.max(torque, idleTorque)
end

– rpm limiter
if engAV > engine.maxAV then torque = 0 end

– Calculate engine’s work
local dtT = dt * torque
engineWorkPerUpdate = engineWorkPerUpdate + dtT * (dtT * halfInvEngInertia + engAV)

torquediff = (engAV - wheelBasedEngAV) * viscousCoupling
if math.abs(torquediff) > clutchTorque then
torquediff = sign(torquediff) * clutchTorque
end
torquediff = torquediff * clutchRatio

– decouple when in neutral
if M.gear == 0 then
torquediff = 0
M.torque = 0
M.torqueTransmission = 0
engAV = math.max(engAV + dt * (torque - engFriction - brakingCoef * engAV) * invEngInertia, 0)
axleAV = avgDiffAV
else
M.torque = torquediff
M.torqueTransmission = torquediff * engine.gears[M.gear]
engAV = math.max(engAV + dt * (torque - torquediff - engFriction - brakingCoef * engAV) * invEngInertia, 0)
axleAV = engAV / engine.gears[M.gear]
end
end

local function updateWheels(dt)
local n, wd

– Disable ABS for half the ABS duration time
ABStimer = ABStimer + dt
if ABStimer > ABSduration then ABStimer = ABSduration - ABStimer end
local ABSavgAV
if ABStimer < ABShalfDuration then
ABSavgAV = -1
else
ABSavgAV = math.abs(avgAV)
end

avgAV = 0
M.absPulse = 0

for n,wd in pairs(M.wheels) do
local w = wd.obj

avgAV = avgAV + w.angularVelocity * wd.wheelDir

– braking
if M.brake > 0 then
w.brakeState = 1
wd.lastTorqueMode = 1
w.brakingTorque = wd.brakeTorque * M.brake
--anti-lock braking system (ABS)
if wd.enableABS and math.abs(w.angularVelocity) < ABSavgAV - wd.ABSthreshold then
w.brakeState = 0
wd.lastTorqueMode = 0
w.brakingTorque = 0
M.absPulse = bit.bor(M.absPulse, 1)
end
else
w.brakeState = 0
wd.lastTorqueMode = 0
w.brakingTorque = 0
end

– and the parking brake
if input.parkingbrake ~= 0 then
w.brakeState = 2
wd.lastTorqueMode = 2
w.brakingTorque = w.brakingTorque + wd.parkingTorque * input.parkingbrake
end

w.torque = 0 – prepare torque for propulsion
end

avgAV = avgAV * invWheelCount

– propulsion
local torque
local diff
local absTorqueTransmission = math.abs(M.torqueTransmission)
local absAxleAV = math.abs(axleAV)

avgDiffAV = 0

for n,diff in pairs(diffs) do
local w1 = diff.w1
local w2 = diff.w2
local w1AV = w1.obj.angularVelocity * w1.wheelDir
local w2AV = w2.obj.angularVelocity * w2.wheelDir

local absW1AV = math.abs(w1AV)
local absW2AV = math.abs(w2AV)
local w1torque
local w2torque

if absW2AV < absW1AV then
w1AV, w2AV = w2AV, w1AV
absW1AV, absW2AV = absW2AV, absW1AV
w1, w2 = w2, w1
end

avgDiffAV = avgDiffAV + w2AV – Use max wheel speed as the axle’s speed

– Open diffs
if absW2AV > absAxleAV then
if absW2AV > absAxleAV + 1 then
torque = math.min(absW2AV - (absAxleAV + 1), 1) * absTorqueTransmission * diff.engineTorqueCoef
w1torque = sign(M.torqueTransmission) * torque
w2torque = sign(axleAV - w2AV) * torque
else
torque = (1 - (absW2AV - absAxleAV)) * M.torqueTransmission * diff.engineTorqueCoef
w1torque = torque
w2torque = torque
end
else
torque = M.torqueTransmission * diff.engineTorqueCoef
w1torque = torque
w2torque = torque
end

– LSD diffs
if diff.state == 1 then
local wAVdiff = w1AV - w2AV
local wAVdiffabs = math.min(math.abs(wAVdiff), 1)
local ltorque = sign(wAVdiff) * wAVdiffabs * diff.closedTorque
w1torque = w1torque - ltorque
w2torque = w2torque + ltorque
end

w1.obj.torque = w1.obj.torque + w1torque * w1.wheelDir - sign(w1.obj.angularVelocity) * axleFriction
w2.obj.torque = w2.obj.torque + w2torque * w2.wheelDir - sign(w2.obj.angularVelocity) * axleFriction
end

avgDiffAV = avgDiffAV * invDiffCount

if v.data.engine then
updateEngineState(dt)
end
end


local function updateEngine(dt)
local engine = v.data.engine

--Update GUI engine RPM
M.rpm = engAV * 9.5493

M.throttle = input.throttle
M.brake = input.brake
clutchRatio = 1 - input.clutch

– Fuel consumption
M.fuel = M.fuel - engineWorkPerUpdate * invBurnEfficiencyFuelDensity - M.fuelLeakRate * dt
engineWorkPerUpdate = 0

if M.engineDisabled and M.throttle > 0.5 then
M.engineDisabled = false
M.engineIgnition = true
end

if engAV > engStallAV then
M.engineIgnition = false
end

– driving backwards? - only with automatic shift - for obvious reasons ;)
if not M.manualShift then
if M.gear <= 0 and avgAV < 0 then
M.throttle = input.brake
M.brake = input.throttle
end
end

– engine dies when fuel is up or it stalls
– if (engAV < engStallAV and not M.engineIgnition) or M.fuel <= 0 then
if M.fuel <= 0 then
M.fuel = math.max(M.fuel, 0)
M.engineDisabled = true
engIdleAV = 0
end

if M.engineDisabled then
M.throttle = 0
M.rpm = 0
return
end

if clutchTimer > 0 then
return
end

--transmission logic
local tmpCurGear = M.gear
if M.manualShift then
– manual
– limits
M.gear = math.min( math.max(M.manualGear, -engine.revGearCount), engine.fwdGearCount )
else
– automatic
--interpolate based on throttle between high/low ranges
local throttleCubed = M.throttle * M.throttle * M.throttle
local shiftDown = engine.lowShiftDownAV + (engine.highShiftDownAV - engine.lowShiftDownAV) * throttleCubed
local shiftUp = engine.lowShiftUpAV + (engine.highShiftUpAV - engine.lowShiftUpAV) * throttleCubed

if M.gear > 0 then
– forward gears
if engAV < shiftDown then
if M.gear > -engine.revGearCount then
M.gear = M.gear - 1
end
end
if engAV > shiftUp then
if M.gear < engine.fwdGearCount then
M.gear = M.gear + 1
end
end
elseif M.gear < 0 then
– reverse gears
if engAV < shiftDown then
if M.gear < engine.fwdGearCount then
M.gear = M.gear + 1
end
end
if engAV > shiftUp then
if M.gear > -engine.revGearCount then
M.gear = M.gear - 1
end
end
end

– neutral gear handling
if M.gear == 0 then
if input.throttle > 0 and avgAV > -10 and tmpCurGear >=0 then
M.gear = 1
end

if input.brake > 0 and avgAV < 10 and tmpCurGear <=0 and engine.revGearCount > 0 then
M.gear = -1
end
end
end

if tmpCurGear ~= M.gear then
--print("gear changed: “…M.gear)
--sounds.playSoundOnceAtNode(“ShiftTestSound”, 0, 0.6)
if autoClutch then
clutchTimer = clutchDuration
end
end

– fix up manualGear: to keep the value in sync of what we just selected :)
M.manualGear = M.gear
end

local function updateCounts()
local n,wd

propWheelsCount = 0
M.wheelCount = 0
for n,wd in pairs(M.wheels) do
if wd.propulsed ~= 0 then
propWheelsCount = propWheelsCount + 1
end
M.wheelCount = M.wheelCount + 1
end

if M.wheelCount ~= 0 then
invWheelCount = 1 / M.wheelCount
else
invWheelCount = 0
end
end

local function updateDifferentials()
local i = 0
local wheelNames = {}

– Clear old diffs config state
for _,diff in pairs(diffs) do
diff.w1.obj.torque = 0
diff.w2.obj.torque = 0
end

diffs = {}

for _,wd in pairs(M.wheelInfo) do
wheelNames[wd.name] = M.wheels[wd.wheelID]
end


local totalEngCoef = 0

– Sum engine torque coefs
for _, diff in pairs(v.data.differentials) do
if wheelNames[diff.wheelName1] and wheelNames[diff.wheelName2] then
diff.engineTorqueCoef = diff.engineTorqueCoef or 1
totalEngCoef = totalEngCoef + diff.engineTorqueCoef
end
end

– Update diffs
for _,diff in pairs(v.data.differentials) do
if wheelNames[diff.wheelName1] and wheelNames[diff.wheelName2] then
diffs[i] = { w1 = wheelNames[diff.wheelName1] ,
w2 = wheelNames[diff.wheelName2] ,
dtype = diffTypeMap[string.lower(diff.type or “open”)] ,
closedTorque = diff.closedTorque ,
state = diffStateMap[string.lower(diff.state or “open”)] ,
engineTorqueCoef = 0.5 * diff.engineTorqueCoef / totalEngCoef --per diff wheel (0.5)
}
i = i + 1
end
end

diffCount = i
invDiffCount = 1 / diffCount
end

local function beamBroke(id)
if not v.data.beams[id].name then return end
local k,ab,n,wd
local doUpdate = false
local brokenBeamName = v.data.beams[id].name

for n,wd in pairs(M.wheels) do
if wd.axleBeams then
for k,ab in pairs(wd.axleBeams) do
if ab == brokenBeamName then
doUpdate = true
local w = wd.obj
w.torque = 0
w.brakingTorque = 0
w.brakeState = 0
M.wheels[n] = nil
break
end
end
end
end

if v.data.engine then
if v.data.engine.onBeamBreakDisableEngine then
for k,ab in pairs(v.data.engine.onBeamBreakDisableEngine) do
if ab == brokenBeamName then
M.engineDisabled = true
engIdleAV = 0
break
end
end
end

if v.data.engine.onBeamBreakDisableDriveshaft then
for k,ab in pairs(v.data.engine.onBeamBreakDisableDriveshaft) do
if ab == brokenBeamName then
axleFriction = 0
viscousCoupling = 0
break
end
end
end

if v.data.engine.fuelTankBeams then
for k,ab in pairs(v.data.engine.fuelTankBeams) do
if ab == brokenBeamName then
M.fuelLeakRate = M.fuelLeakRate + 1 / #v.data.engine.fuelTankBeams
break
end
end
end
end

if doUpdate then
updateCounts()
updateDifferentials()
end
end

local function updateWheelSlip§
local wheelID = v.data.nodes[p.id1].wheelID
if wheelID then
M.wheelInfo[wheelID].lastSlip = math.max(p.slipVel, M.wheelInfo[wheelID].lastSlip)
– Smoothed instant energy (E/dt)
M.wheelInfo[wheelID].slipEnergy = 0.5 *(p.slipForce * p.slipVel + M.wheelInfo[wheelID].slipEnergy)
end
end

local function wheelSlipGFXreset()
for n,wd in pairs(M.wheelInfo) do
if wd.lastSlip == 0 then
wd.slipEnergy = 0
end
wd.lastSlip = 0

end
end

local function shiftUp()
if M.shifterMode == 2 then
– TODO: add automatic logic
end
M.manualGear = M.manualGear + 1
end

local function shiftDown()
if M.shifterMode == 2 then
– TODO: add automatic logic
end
M.manualGear = M.manualGear - 1
end

local function shiftToGear(val)
– when in full automatic, shifting only to -1, 1, 2 and N is allowed
– TODO: lock -1 when moving forwards
if M.shifterMode == 2 and val > 2 then
return
end
M.manualGear = val
end

local function setShifterMode(v)
v = v or 0

M.shifterMode = v
– limits
if M.shifterMode > 2 then
M.shifterMode = 0
end
if M.shifterMode < 0 then
M.shifterMode = 0
end
– now do the important things
if M.shifterMode == 0 then
– full manual
logInfo(“Manual Transmission: switched to full manual”)
M.autoClutch = false
M.manualShift = true
elseif M.shifterMode == 1 then
– manual with automatic clutch
logInfo(“Manual Transmission: switched to manual with auto clutch”)
M.autoClutch = true
M.manualShift = true
elseif M.shifterMode == 2 then
– full automatic
logInfo(“Automatic transmission: switched to automatic”)
M.autoClutch = true
M.manualShift = false
viscousCoupling = viscousCouplingOrig
end

– if not automatic
if M.shifterMode ~= 2 then
viscousCoupling = clutchTorque / maxGearRatio
end
end

local function toggleShifterMode()
setShifterMode(M.shifterMode + 1)
end

M.updateWheels = updateWheels
local function setDifferentialMode(v)
v = v or 0

M.differentialMode = v
– limits
if M.differentialMode > 1 then
M.differentialMode = 0
end
if M.differentialMode < 0 then
M.differentialMode = 0
end
end

local function toggleDifferentialMode()
setDifferentialMode(M.differentialMode + 1)
end

local function genDiffs()
diffs = {}

if propWheelsCount ~= 2 then
return
end

local diffWheels = {}
local wn = 0

for n,wd in pairs(M.wheels) do
if wd.propulsed ~= 0 then
diffWheels[wn] = wd
wn = wn + 1
end
end

if wn == 2 then
logInfo(“Found 2 propulsed wheels: generating Open Differential between them”)
diffs[0] = {w1 = diffWheels[0], w2 = diffWheels[1], state = 0, engineTorqueCoef = 1}
end
end

M.updateEngine = updateEngine
M.updateWheelSlip = updateWheelSlip
M.wheelSlipGFXreset = wheelSlipGFXreset
M.beamBroke = beamBroke

local function init()
engAV = 0
axleAV = 0
clutchTimer = 0
M.engineDisabled = false
M.engineIgnition = true
engineWorkPerUpdate = 0
M.gear = 0
M.manualGear = 0
M.fuelLeakRate = 0
M.torqueTransmission = 0
avgPropAV = 0
avgAV = 0

M.wheels = {}
M.wheelInfo = {}
local wheelNames = {}

M.updateWheels = updateWheels

if not v or not v.data then
M.updateEngine = nop
M.updateWheels = nop
M.updateWheelSlip = nop
M.wheelSlipGFXreset = nop
M.beamBroke = nop
return
end

if v.data.wheels then
M.wheels = shallowcopy(v.data.wheels)
M.wheelInfo = shallowcopy(v.data.wheels)
end

local wobj
for n,wd in pairs(M.wheelInfo) do
wd.lastTorqueMode = 0
M.wheelInfo[wd.wheelID].lastSlip = 0
M.wheelInfo[wd.wheelID].slipEnergy = 0
wobj = obj:getWheel(wd.wheelID)
if wobj then
M.wheels[wd.wheelID].obj = wobj
M.wheels[wd.wheelID].enableABS = M.wheels[wd.wheelID].enableABS or false
M.wheels[wd.wheelID].ABSthreshold = M.wheels[wd.wheelID].ABSthreshold or 5
M.wheels[wd.wheelID].brakeTorque = M.wheels[wd.wheelID].brakeTorque or 0
M.wheels[wd.wheelID].parkingTorque = M.wheels[wd.wheelID].parkingTorque or 0
else
M.wheels[wd.wheelID] = nil
logError(‘Wheel "’…M.wheels[wd.wheelID].name…'” could not be added to drivetrain’)
end
end

updateCounts()

if v.data.differentials == nil then
genDiffs()
else
updateDifferentials()
end

if v.data.engine then
clutchDuration = v.data.engine.clutchDuration or 0.5
invClutchDuration = 1 / clutchDuration
engIdleAV = v.data.engine.idleAV
engStallAV = v.data.engine.stallAV
torsionCoef = v.data.engine.torsionCoef or 40
engInertia = v.data.engine.inertia or 0.1
ABSduration = 1/(v.data.engine.ABSrate or 15)
ABShalfDuration = ABSduration * 0.5
ABStimer = 0

if engInertia ~= 0 then
invEngInertia = 1 / engInertia
else
invEngInertia = 10
end
halfInvEngInertia = invEngInertia * 0.5
– fuel
M.fuelCapacity = v.data.engine.fuelCapacity or 100
M.fuel = M.fuelCapacity
local fuelEnergyDensity = (v.data.engine.fuelEnergyDensity or 32) * 1000000 – MJ/liter
local burnEfficiency = v.data.engine.burnEfficiency or 1
if fuelEnergyDensity * burnEfficiency ~= 0 then
invBurnEfficiencyFuelDensity = 1 / (fuelEnergyDensity * burnEfficiency)
else
invBurnEfficiencyFuelDensity = 1
end
diffRatio = v.data.engine.diffRatio or 2
invDiffRatio = 1 / diffRatio
engFriction = v.data.engine.friction or v.data.engine.engineFriction or 1
brakingCoef = v.data.engine.brakingCoef or 0
if v.data.engine.brakingCoefRPS ~= nil then
brakingCoef = v.data.engine.brakingCoefRPS / ( 2 * math.pi )
end
axleFriction = v.data.engine.axleFriction or v.data.engine.axelFriction or 0

if v.data.engine.torqueCurve == nil then
v.data.engine.torqueCurve = {}
end
idleTorque = v.data.engine.torqueCurve[v.data.engine.idleRPM] or 0

maxGearRatio = v.data.engine.maxGearRatio or 0
maxTorque = v.data.engine.maxTorque or 0

clutchTorque = v.data.engine.clutchTorque or v.data.engine.torqueLimit
if not clutchTorque then
clutchTorque = v.data.engine.maxTorque
logInfo("clutchTorque not set. Set clutchTorque = "…clutchTorque)
end

viscousCoupling = v.data.engine.viscousCoupling or viscousCouplingOrig
if not viscousCoupling then
viscousCoupling = (clutchTorque / maxGearRatio) or 3
logInfo("viscousCoupling not set. Set viscousCoupling = "…viscousCoupling)
end

viscousCouplingOrig = viscousCoupling

setShifterMode(M.shifterMode)
else
M.updateEngine = nop
viscousCoupling = viscousCoupling or 3
viscousCouplingOrig = viscousCoupling
end
end

local function reset()
– for now: call init again, as it does no harm
init()
end

local function refill()
if M.fuel < M.fuelCapacity then
M.fuel = math.min(M.fuel + 2, M.fuelCapacity)
end
end

– public interface
M.reset = reset
M.init = init
M.refill = refill

M.shiftUp = shiftUp
M.shiftDown = shiftDown
M.shiftToGear = shiftToGear
M.toggleShifterMode = toggleShifterMode
M.setShifterMode = setShifterMode
return M