Refactored vehicle components

This commit is contained in:
Nikita Kruglickiy 2022-07-28 15:44:31 +03:00
parent 7cdb222580
commit b4dccd73a6
11 changed files with 485 additions and 523 deletions

View File

@ -1,72 +1,67 @@
-- @include ./wire_component.txt --@include ./powertrain_component.txt
--@include ./enums/gearbox.txt --@include ./enums/gearbox.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
require('./wire_component.txt') require('./powertrain_component.txt')
local gearboxTypes = require('./enums/gearbox.txt') local gearboxTypes = require('./enums/gearbox.txt')
Clutch = class('Clutch', WireComponent) Clutch = class('Clutch', PowertrainComponent)
function Clutch:initialize(config) function Clutch:initialize(config)
self.stiffness = config.Stiffness PowertrainComponent.initialize(self, 'Clutch')
self.damping = config.Damping
self.maxTorque = config.MaxTorque
self.press = 0 self.wireInputs = {
self.slip = 0
self.targetTorque = 0
self.torque = 0
self.engine = nil
self.gearbox = nil
end
function Clutch:linkEngine(eng)
self.engine = eng
end
function Clutch:linkGearbox(gbox)
self.gearbox = gbox
end
function Clutch:getInputs()
if self.gearbox ~= nil and self.gearbox.type == gearboxTypes.MANUAL then
return {
Clutch = 'number' Clutch = 'number'
} }
else self.wireOutputs = {
return {} Clutch_Torque = 'number'
end
end
function Clutch:getOutputs()
return {
Clutch_Torque = 'number',
Clutch_Slip = 'number'
} }
self.inertia = config.Inertia or 0.1
self.slipTorque = config.SlipTorque or 1000
end end
function Clutch:updateOutputs() function Clutch:updateWireOutputs()
wire.ports.Clutch_Torque = self.torque wire.ports.Clutch_Torque = self.torque
wire.ports.Clutch_Slip = self.slip
end end
function Clutch:getPress() function Clutch:getPress()
if self.gearbox ~= nil and self.gearbox.type == gearboxTypes.MANUAL then return 1 - wire.ports.Clutch
return wire.ports.Clutch
else
return self.press
end
end end
function Clutch:update() function Clutch:queryInertia()
local engRPM = self.engine and self.engine.rpm or 0 if self.output == nil then
local gboxRPM = self.gearbox and self.gearbox.rpm or 0 return self.inertia
local gboxRatio = self.gearbox and self.gearbox.ratio or 0 end
local gboxRatioNotZero = gboxRatio ~= 0 and 1 or 0
return self.inertia + self.output:queryInertia() * self:getPress()
self.slip = ((engRPM - gboxRPM) * RPM_TO_RAD) * gboxRatioNotZero / 2 end
self.targetTorque = math.clamp(self.slip * self.stiffness * (1 - self:getPress()), -self.maxTorque, self.maxTorque)
function Clutch:queryAngularVelocity(angularVelocity)
self.torque = math.lerp(self.damping, self.torque, self.targetTorque) self.angularVelocity = angularVelocity
if self.output == nil then
return angularVelocity
end
local outputW = self.output:queryAngularVelocity(angularVelocity) * self:getPress()
local inputW = angularVelocity * (1 - self:getPress())
return outputW + inputW
end
function Clutch:forwardStep(torque, inertia)
if self.output == nil then
return torque
end
local press = self:getPress()
self.torque = math.clamp(torque, -self.slipTorque, self.slipTorque)
self.torque = self.torque * (1 - (1 - math.pow(press, 0.3)))
local returnTorque = self.output:forwardStep(self.torque, inertia * press + self.inertia) * press
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
end end

View File

@ -1,207 +1,148 @@
-- @server
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
-- @include ./wire_component.txt --@include ./powertrain_component.txt
require('/koptilnya/libs/constants.txt') --@include ./wheel.txt
require('./wire_component.txt')
Differential = class('Differential', WireComponent) require('/koptilnya/libs/constants.txt')
require('./powertrain_component.txt')
require('./wheel.txt')
Differential = class('Differential', PowertrainComponent)
function Differential:initialize(config, order) function Differential:initialize(config, order)
self.power = config.Power or 1 PowertrainComponent.initialize(self, 'Axle')
self.coast = config.Coast or 1
self.finalDrive = config.FinalDrive or 1 local prefix = 'Axle' .. (order or '') .. '_'
self.finalDrive = config.FinalDrive or 4
self.biasAB = config.Bias or 0.5
self.coastRamp = config.CoastRamp or 0.5
self.powerRamp = config.PowerRamp or 1
self.preload = config.Preload or 10 self.preload = config.Preload or 10
self.order = order or 1 self.slipTorque = config.SlipTorque or 1000
self.viscousCoeff = config.ViscousCoeff or 0.9 local wheelConfig = {
CalculateInertia = true
}
self.outputA = Wheel:new(wheelConfig, prefix .. 'Left')
self.outputB = Wheel:new(wheelConfig, prefix .. 'Right')
self.distributionCoeff = config.DistributionCoeff or 1 table.merge(self.wireInputs, self.outputA.wireInputs)
self.maxCorrectingTorque = config.MaxCorrectingTorque or 200 table.merge(self.wireInputs, self.outputB.wireInputs)
table.merge(self.wireOutputs, self.outputA.wireOutputs)
self.avgRPM = 0 table.merge(self.wireOutputs, self.outputB.wireOutputs)
self.prefix = 'Axle' .. self.order .. '_'
self.leftWheel = wire.ports[self.prefix .. 'LeftWheel']
self.rightWheel = wire.ports[self.prefix .. 'RightWheel']
self.lwPowerCoeff = 0
self.rwPowerCoeff = 0
self.lwtq_div_rwtq = 0
self.rwtq_div_lwtq = 0
self.lwtq = 0
self.rwtq = 0
self.lwav = 0
self.rwav = 0
hook.add('input', 'wire_axle_update' .. self.order, function(key, val)
if key == self.prefix .. 'LeftWheel' then
self.leftWheel = val
end end
if key == self.prefix .. 'RightWheel' then function Differential:updateWireOutputs()
self.rightWheel = val if self.outputA ~= nil then
end self.outputA:updateWireOutputs()
end)
end end
function Differential:getInputs() if self.outputB ~= nil then
local inputs = {} self.outputB:updateWireOutputs()
inputs[self.prefix .. 'LeftWheel'] = 'entity'
inputs[self.prefix .. 'RightWheel'] = 'entity'
return inputs
end
function Differential:getOutputs()
local outputs = {}
outputs[self.prefix .. 'LWPowerCoeff'] = 'number'
outputs[self.prefix .. 'RWPowerCoeff'] = 'number'
outputs[self.prefix .. 'LWTq'] = 'number'
outputs[self.prefix .. 'RWTq'] = 'number'
outputs[self.prefix .. 'LWAV'] = 'number'
outputs[self.prefix .. 'RWAV'] = 'number'
outputs[self.prefix .. 'AvgRPM'] = 'number'
outputs[self.prefix .. 'LW_div_RW'] = 'number'
outputs[self.prefix .. 'RW_div_LW'] = 'number'
-- outputs[self.prefix .. 'SGAV'] = 'number'
return outputs
end
function Differential:updateOutputs()
wire.ports[self.prefix .. 'LWPowerCoeff'] = self.lwPowerCoeff
wire.ports[self.prefix .. 'RWPowerCoeff'] = self.rwPowerCoeff
wire.ports[self.prefix .. 'LWTq'] = self.lwtq
wire.ports[self.prefix .. 'RWTq'] = self.rwtq
wire.ports[self.prefix .. 'RWTq'] = self.rwtq
wire.ports[self.prefix .. 'LWAV'] = self.lwav
wire.ports[self.prefix .. 'RWAV'] = self.rwav
wire.ports[self.prefix .. 'AvgRPM'] = self.avgRPM
wire.ports[self.prefix .. 'LW_div_RW'] = self.lwtq_div_rwtq
wire.ports[self.prefix .. 'RW_div_LW'] = self.rwtq_div_lwtq
-- wire.ports[self.prefix .. 'SGAV'] = self.sgav
end
function Differential:linkGearbox(gbox)
self.gearbox = gbox
end
-- this is a test function that can spin wheels in different directions
function Differential:testUpdate()
if isValid(self.leftWheel) and isValid(self.rightWheel) then
local lwav = math.rad(self.leftWheel:getAngleVelocity()[2])
local rwav = math.rad(self.rightWheel:getAngleVelocity()[2])
self.avgRPM = (lwav - rwav) / 2 * RAD_TO_RPM * self.finalDrive
self.lwav = lwav
self.rwav = rwav
-- spider gear angular velocity
local sgav = (lwav + rwav) / 2
self.sgav = sgav
-- RAV = relative angular velocity
local lwrav = lwav - sgav
local rwrav = rwav - sgav
local lwInertia = self.leftWheel:getInertia():getLength()
local rwInertia = self.rightWheel:getInertia():getLength()
local lwtq = lwrav * lwInertia * 2
local rwtq = rwrav * rwInertia * 2
self.lwtq = lwtq
self.rwtq = rwtq
local lwDampingTorque = self.viscousCoeff * lwtq
local rwDampingTorque = self.viscousCoeff * rwtq
local gboxTorque = self.gearbox and self.gearbox.torque / 10 or 0
local usedCoeff = gboxTorque > self.preload and self.power or self.coast
local lwCorrectingTorque = math.clamp(lwDampingTorque * usedCoeff, -self.maxCorrectingTorque,
self.maxCorrectingTorque)
local rwCorrectingTorque = math.clamp(rwDampingTorque * usedCoeff, -self.maxCorrectingTorque,
self.maxCorrectingTorque)
local driveTorque = gboxTorque * self.distributionCoeff * 52.49 * self.finalDrive
local lwCorrectedTorque = lwCorrectingTorque * 600 - driveTorque
local rwCorrectedTorque = rwCorrectingTorque * 600 + driveTorque
self.leftWheel:applyTorque(lwCorrectedTorque * self.leftWheel:getRight() * TICK_INTERVAL)
self.rightWheel:applyTorque(rwCorrectedTorque * self.rightWheel:getRight() * TICK_INTERVAL)
end end
end end
function Differential:update() function Differential:queryInertia()
if isValid(self.leftWheel) and isValid(self.rightWheel) then if self.outputA == nil or self.outputB == nil then
local lwav = math.rad(self.leftWheel:getAngleVelocity()[2]) return self.inertia
local rwav = math.rad(self.rightWheel:getAngleVelocity()[2]) * -1
self.lwav = lwav
self.rwav = rwav
-- RAV = relative anglar velocity
local lwrav = rwav - lwav
local rwrav = lwav - rwav
local lwInertia = self.leftWheel:getInertia()[2]
local rwInertia = self.rightWheel:getInertia()[2]
local lwtq = lwrav * lwInertia
local rwtq = rwrav * rwInertia
self.lwtq = lwtq
self.rwtq = rwtq
-- Whether use diff power or diff coast
local usePower = self.gearbox and self.gearbox.torque > self.preload and false
local usedCoeff = usePower and self.power or self.coast
-- Calculating damping torque here
local lwDampingTorque = self.viscousCoeff * lwtq * usedCoeff
local rwDampingTorque = self.viscousCoeff * rwtq * usedCoeff
local lwav_abs = math.abs(lwav)
local rwav_abs = math.abs(rwav)
local avg_abs = lwav_abs + rwav_abs
local lwav_to_avg = avg_abs ~= 0 and 2 * lwav_abs / avg_abs or 0
local rwav_to_avg = avg_abs ~= 0 and 2 * rwav_abs / avg_abs or 0
self.lwtq_div_rwtq = lwav_to_avg
self.rwtq_div_lwtq = rwav_to_avg
local lwPowerCoeff = math.clamp(lwav_to_avg, usedCoeff, 1 + (1 - usedCoeff))
local rwPowerCoeff = math.clamp(rwav_to_avg, usedCoeff, 1 + (1 - usedCoeff))
self.lwPowerCoeff = lwPowerCoeff
self.rwPowerCoeff = rwPowerCoeff
local lwCorrectingTorque = math.clamp(lwDampingTorque, -self.maxCorrectingTorque, self.maxCorrectingTorque)
local rwCorrectingTorque = math.clamp(rwDampingTorque, -self.maxCorrectingTorque, self.maxCorrectingTorque)
local gboxTorque = self.gearbox and self.gearbox.torque or 0
local driveTorque = gboxTorque / 2 * self.distributionCoeff * self.finalDrive
local lwCorrectedTorque = lwCorrectingTorque * 20 + driveTorque * lwPowerCoeff -- * lwPowerCoeff
local rwCorrectedTorque = rwCorrectingTorque * 20 + driveTorque * rwPowerCoeff -- * rwPowerCoeff
self.leftWheel:applyTorque(lwCorrectedTorque * -self.leftWheel:getRight())
self.rightWheel:applyTorque(rwCorrectedTorque * self.rightWheel:getRight())
self.avgRPM = (lwav + rwav) / 2 * RAD_TO_RPM * self.finalDrive
end end
return self.inertia + self.outputA:queryInertia() + self.outputB:queryInertia()
end
function Differential:queryAngularVelocity(angularVelocity)
self.angularVelocity = angularVelocity
if self.outputA == nil or self.outputB == nil then
return angularVelocity
end
local aW = self.outputA:queryAngularVelocity(angularVelocity)
local bW = self.outputB:queryAngularVelocity(angularVelocity)
return (aW - bW) * self.finalDrive * 0.5
end
function Differential:forwardStep(torque, inertia)
if self.outputA == nil or self.outputB == nil then
return torque
end
local aW = self.outputA:queryAngularVelocity(self.angularVelocity)
local bW = -self.outputB:queryAngularVelocity(self.angularVelocity)
local aI = self.outputA:queryInertia()
local bI = self.outputB:queryInertia()
local tqA, tqB = self:_openDiffTorqueSplit(
torque * self.finalDrive,
aW,
bW,
aI,
bI,
self.biasAB,
self.preload,
self.stiffness,
self.powerRamp,
self.coastRamp,
self.slipTorque
)
--tqA = tqA * aI * TICK_INTERVAL
--tqB = tqB * bI * TICK_INTERVAL
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 + aI)
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 + bI)
return tqA + tqB
end
function Differential:_openDiffTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stiffness, powerRamp, coastRamp, slipTorque)
return tq * (1 - biasAB), tq * biasAB;
end
function Differential:_lockingDiffTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stiffness, powerRamp, coastRamp, slipTorque)
local sumI = aI + bI
local w = aI / sumI * aW + bI / sumI * bW
local aTqCorr = (w - aW) * aI -- / dt
aTqCorr = aTqCorr * stiffness
local bTqCorr = (w - bW) * bI -- / dt
bTqCorr = bTqCorr * stiffness
local biasA = clamp(0.5 + (bW - aW) * 0.1 * stiffness, 0, 1)
return tq * biasA + aTqCorr, tq * (1 - biasA) * bTqCorr
end
function Differential:_VLSDTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stiffness, powerRamp, coastRamp, slipTorque)
if aW < 0 or bW < 0 then
return tq * (1 - biasAB), tq * biasAB
end
local c = tq > 0 and powerRamp or coastRamp
local totalW = math.abs(aW) + math.abs(bW)
local slip = 0
if aW > 0 or bW > 0 then
slip = (aW - bW) / totalW
end
local dTq = slip * stiffness * c * slipTorque
return tq * (1 - biasAB) - dTq, tq * biasAB + dTq
end
function Differential:_HLSDTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stiffness, powerRamp, coastRamp, slipTorque)
if aW < 0 or bW < 0 then
return tq * (1 - biasAB), tq * biasAB
end
local c = tq > 0 and powerRamp or coastRamp
local tqFactor = math.clamp(math.abs(tq) / slipTorque, -1, 1)
local totalW = math.abs(aW) + math.abs(bW)
local slip = 0
if aW > 0 or bW > 0 then
slip = (aW - bW) / totalW
end
local dTq = slip * stiffness * c * slipTorque * tqFactor
return tq * (1 - biasAB) - dTq, tq * biasAB + dTq
end end

View File

@ -1,53 +1,40 @@
-- @include ./wire_component.txt --@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
require('./wire_component.txt')
require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
Engine = class('Engine', WireComponent) Engine = class('Engine', PowertrainComponent)
function Engine:initialize(config, clutch) function Engine:initialize(config, clutch)
self.idleRPM = config.IdleRPM PowertrainComponent.initialize(self, 'Engine')
self.maxRPM = config.MaxRPM
self.flywheelMass = config.FlywheelMass self.wireInputs = {
self.flywheelRadius = config.FlywheelRadius
self.startFriction = config.StartFriction
self.frictionCoeff = config.FrictionCoeff
self.torque = 0
self.rpmFrac = 0
self.rpm = self.idleRPM
self.friction = 0
self.masterThrottle = 0
self.limiterDuration = config.LimiterDuration
self.torqueMap = config.TorqueMap or {}
self._fwInertia = self.flywheelMass * self.flywheelRadius ^ 2 / 2
self._limiterTime = 0
self.clutch = clutch
clutch:linkEngine(self)
end
function Engine:getInputs()
return {
Throttle = 'number' Throttle = 'number'
} }
end self.wireOutputs = {
function Engine:getOutputs()
return {
Engine_RPM = 'number', Engine_RPM = 'number',
Engine_Torque = 'number', Engine_Torque = 'number',
Engine_MasterThrottle = 'number' Engine_MasterThrottle = 'number'
} }
self.idleRPM = config.IdleRPM or 900
self.maxRPM = config.MaxRPM or 7000
self.inertia = config.Inertia or 0.4
self.startFriction = config.StartFriction or -50
self.frictionCoeff = config.FrictionCoeff or 0.02
self.limiterDuration = config.LimiterDuration or 0.05
self.torqueMap = config.TorqueMap or {}
self.rpmFrac = 0
self.friction = 0
self.masterThrottle = 0
self.limiterTimer = 0
end end
function Engine:updateOutputs() function Engine:updateWireOutputs()
wire.ports.Engine_RPM = self.rpm wire.ports.Engine_RPM = self:getRPM()
wire.ports.Engine_Torque = self.torque wire.ports.Engine_Torque = self.torque
wire.ports.Engine_MasterThrottle = self.masterThrottle wire.ports.Engine_MasterThrottle = self.masterThrottle
end end
@ -56,35 +43,53 @@ function Engine:getThrottle()
return wire.ports.Throttle return wire.ports.Throttle
end end
function Engine:update() function Engine:_generateTorque()
local throttle = self:getThrottle() local throttle = self:getThrottle()
local rpm = self:getRPM()
self.rpmFrac = math.clamp((self.rpm - self.idleRPM) / (self.maxRPM - self.idleRPM), 0, 1) local rpmFrac = math.clamp((rpm - self.idleRPM) / (self.maxRPM - self.idleRPM), 0, 1)
self.friction = self.startFriction - self.rpm * self.frictionCoeff self.friction = self.startFriction - self:getRPM() * self.frictionCoeff
local tqIdx = math.clamp(math.floor(self.rpmFrac * #self.torqueMap), 1, #self.torqueMap) local tqIdx = math.clamp(math.floor(rpmFrac * #self.torqueMap), 1, #self.torqueMap)
local maxInitialTorque = self.torqueMap[tqIdx] - self.friction local maxInitialTorque = self.torqueMap[tqIdx] - self.friction
local idleFadeStart = math.clamp(math.remap(rpm, self.idleRPM - 300, self.idleRPM, 1, 0), 0, 1)
local idleFadeStart = math.clamp(math.remap(self.rpm, self.idleRPM - 300, self.idleRPM, 1, 0), 0, 1) local idleFadeEnd = math.clamp(math.remap(rpm, self.idleRPM, self.idleRPM + 600, 1, 0), 0, 1)
local idleFadeEnd = math.clamp(math.remap(self.rpm, self.idleRPM, self.idleRPM + 600, 1, 0), 0, 1)
local additionalEnergySupply = idleFadeEnd * (-self.friction / maxInitialTorque) + idleFadeStart local additionalEnergySupply = idleFadeEnd * (-self.friction / maxInitialTorque) + idleFadeStart
if self.rpm > self.maxRPM then if rpm > self.maxRPM then
throttle = 0 throttle = 0
self._limiterTime = timer.systime() self.limiterTimer = timer.systime()
else else
throttle = timer.systime() >= self._limiterTime + self.limiterDuration and throttle or 0 throttle = timer.systime() >= self.limiterTimer + self.limiterDuration and throttle or 0
end end
self.masterThrottle = math.clamp(additionalEnergySupply + throttle, 0, 1) self.masterThrottle = math.clamp(additionalEnergySupply + throttle, 0, 1)
local realInitialTorque = maxInitialTorque * self.masterThrottle local realInitialTorque = maxInitialTorque * self.masterThrottle
local loadTorque = self.clutch and self.clutch.torque or 0
self.torque = realInitialTorque + self.friction self.torque = realInitialTorque + self.friction
self.rpm = self.rpm + (self.torque - loadTorque) / self._fwInertia * RAD_TO_RPM * TICK_INTERVAL return self.torque
self.rpm = math.max(self.rpm, 0) end
function Engine:integrateDownwards()
if self.output == nil then
local generatedTorque = self:_generateTorque()
self.angularVelocity = self.angularVelocity + generatedTorque / self.inertia * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0)
end
local outputInertia = self.output:queryInertia()
local inertiaSum = self.inertia + outputInertia
local outputW = self.output:queryAngularVelocity(self.angularVelocity)
local targetW = self.inertia / inertiaSum * self.angularVelocity + outputInertia / inertiaSum * outputW
local generatedTorque = self:_generateTorque()
local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL
local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, 0) -- ??? 0 -> self.inertia
local finalTorque = generatedTorque + returnedTorque + reactTorque
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0)
end end

View File

@ -1,82 +1,64 @@
-- @include ../wire_component.txt --@include ../powertrain_component.txt
require('../wire_component.txt')
Gearbox = class('Gearbox', WireComponent) require('../powertrain_component.txt')
function Gearbox:initialize(config, clutch, axles) Gearbox = class('Gearbox', PowertrainComponent)
self.ratios = config.Ratios
self.reverse = config.Reverse
self.rpm = 0 function Gearbox:initialize(config)
self.gear = 0 PowertrainComponent.initialize(self, 'Gearbox')
self.torque = 0
self.axles = axles or {} self.wireInputs = {
self.clutch = clutch
self.type = config.Type
clutch:linkGearbox(self)
for _, axle in pairs(axles) do
axle:linkGearbox(self)
end
self:recalcRatio()
end
function Gearbox:getInputs()
return {
Upshift = 'number', Upshift = 'number',
Downshift = 'number' Downshift = 'number'
} }
end self.wireOutputs = {
function Gearbox:getOutputs()
return {
Gearbox_RPM = 'number', Gearbox_RPM = 'number',
Gearbox_Torque = 'number', Gearbox_Torque = 'number',
Gearbox_Gear = 'number',
Gearbox_Ratio = 'number' Gearbox_Ratio = 'number'
} }
self.type = config.Type or 'MANUAL'
self.inertia = config.Inertia or 1000
self.ratio = 0
end end
function Gearbox:updateOutputs() function Gearbox:updateWireOutputs()
wire.ports.Gearbox_RPM = self.rpm wire.ports.Gearbox_RPM = self:getRPM()
wire.ports.Gearbox_Torque = self.torque wire.ports.Gearbox_Torque = self.torque
wire.ports.Gearbox_Gear = self.gear
wire.ports.Gearbox_Ratio = self.ratio wire.ports.Gearbox_Ratio = self.ratio
end end
function Gearbox:setGear(gear) function Gearbox:queryInertia()
if gear >= -1 and gear <= #self.ratios then if self.output == nil or self.ratio == 0 then
self.gear = gear return self.inertia
self:recalcRatio()
end
end end
function Gearbox:recalcRatio() return self.inertia + self.output:queryInertia() / math.pow(self.ratio, 2)
if self.gear == -1 then
self.ratio = -self.reverse
elseif self.gear == 0 then
self.ratio = 0
else
self.ratio = self.ratios[self.gear]
end
end end
function Gearbox:update() function Gearbox:queryAngularVelocity(angularVelocity)
if self.clutch ~= nil then self.angularVelocity = angularVelocity
self.torque = self.clutch.torque * self.ratio
if self.output == nil or self.ratio == 0 then
return angularVelocity
end end
local maxAxlesRPM = 0 return self.output:queryAngularVelocity(angularVelocity) * self.ratio
if #self.axles > 0 then
maxAxlesRPM = math.max(unpack(table.map(self.axles, function(diff)
return diff.avgRPM
end)))
end end
self.rpm = maxAxlesRPM * self.ratio function Gearbox:forwardStep(torque, inertia)
if self.output == nil then
return torque
end
if self.ratio == 0 then
self.output:forwardStep(0, inertia)
return torque
end
self.torque = torque * self.ratio
return self.output:forwardStep(self.torque, (inertia + self.inertia) * math.pow(self.ratio, 2)) / self.ratio
end end

View File

@ -1,12 +1,22 @@
--@include /koptilnya/libs/watcher.txt --@include /koptilnya/libs/watcher.txt
--@include ./base.txt --@include ./base.txt
require('/koptilnya/libs/watcher.txt') require('/koptilnya/libs/watcher.txt')
require('./base.txt') require('./base.txt')
ManualGearbox = class('ManualGearbox', Gearbox) ManualGearbox = class('ManualGearbox', Gearbox)
function ManualGearbox:initialize(...) function ManualGearbox:initialize(config)
Gearbox.initialize(self, ...) Gearbox.initialize(self, config)
table.merge(self.wireOutputs, {
Gearbox_Gear = 'number'
})
self.ratios = config.Ratios or { 3.6, 2.2, 1.5, 1.2, 1.0, 0.8}
self.reverse = config.Reverse or 3.4
self.gear = 0
function shiftFunc() function shiftFunc()
local upshift = wire.ports.Upshift or 0 local upshift = wire.ports.Upshift or 0
@ -20,6 +30,21 @@ function ManualGearbox:initialize(...)
self:shift(val) self:shift(val)
end end
end) end)
self:recalcRatio()
end
function ManualGearbox:updateWireOutputs()
Gearbox.updateWireOutputs(self)
wire.ports.Gearbox_Gear = self.gear
end
function ManualGearbox:setGear(gear)
if gear >= -1 and gear <= #self.ratios then
self.gear = gear
self:recalcRatio()
end
end end
function ManualGearbox:recalcRatio() function ManualGearbox:recalcRatio()
@ -36,19 +61,10 @@ function ManualGearbox:shift(dir)
self:setGear(self.gear + dir) self:setGear(self.gear + dir)
end end
-- function ManualGearbox:getOutputs() function ManualGearbox:forwardStep(torque, inertia)
-- return { local result = Gearbox.forwardStep(self, torque, inertia)
-- Gearbox_RPM = 'number',
-- Gearbox_Torque = 'number'
-- }
-- end
-- function ManualGearbox:updateOutputs()
-- end
function ManualGearbox:update()
Gearbox.update(self)
self.shiftWatcher() self.shiftWatcher()
return result
end end

View File

@ -1,99 +0,0 @@
-- @server
-- @include /koptilnya/libs/constants.txt
-- @include ./wire_component.txt
require('/koptilnya/libs/constants.txt')
require('./wire_component.txt')
Differential = class('Differential', WireComponent)
function Differential:initialize(config, order)
self.finalDrive = config.FinalDrive or 1
self.distributionCoeff = config.DistributionCoeff or 1
self.axle = config.Axle or Vector(1, 0, 0)
self.order = order
self.avgRPM = 0
self.prefix = 'Axle' .. self.order .. '_'
self.leftWheel = wire.ports[self.prefix .. 'LeftWheel']
self.rightWheel = wire.ports[self.prefix .. 'RightWheel']
self.lwav = 0
self.rwav = 0
hook.add('input', 'wire_axle_update' .. self.order, function(key, val)
if key == self.prefix .. 'LeftWheel' then
self.leftWheel = val
end
if key == self.prefix .. 'RightWheel' then
self.rightWheel = val
end
end)
end
function Differential:getInputs()
local inputs = {}
inputs[self.prefix .. 'LeftWheel'] = 'entity'
inputs[self.prefix .. 'RightWheel'] = 'entity'
return inputs
end
function Differential:getOutputs()
local outputs = {}
outputs[self.prefix .. 'LWAV'] = 'number'
outputs[self.prefix .. 'RWAV'] = 'number'
outputs[self.prefix .. 'AvgRPM'] = 'number'
outputs[self.prefix .. 'Test'] = 'number'
return outputs
end
function Differential:updateOutputs()
wire.ports[self.prefix .. 'LWAV'] = self.lwav
wire.ports[self.prefix .. 'RWAV'] = self.rwav
wire.ports[self.prefix .. 'AvgRPM'] = self.avgRPM
end
function Differential:linkGearbox(gbox)
self.gearbox = gbox
end
function Differential:getAngleVelocity()
local lwav = math.rad(self.leftWheel:getAngleVelocity()[2])
local rwav = math.rad(-self.rightWheel:getAngleVelocity()[2])
local awav = (lwav + rwav) / 2
return lwav, rwav, awav
end
function Differential:getRPM()
local lwav, rwav, awav = self:getAngleVelocity()
return lwav * RAD_TO_RPM, rwav * RAD_TO_RPM, awav * RAD_TO_RPM
end
function Differential:update()
if isValid(self.leftWheel) and isValid(self.rightWheel) then
local lwav, rwav = self:getAngleVelocity()
local inertia = self.leftWheel:getInertia().y + self.rightWheel:getInertia().y
local gboxTorque = self.gearbox and self.gearbox.torque or 0
local simmetric = gboxTorque * self.distributionCoeff * self.finalDrive / 2
local lock100 = (lwav - rwav) / 2 * inertia * TICK_INTERVAL
wire.ports[self.prefix .. 'Test'] = (gboxTorque * self.distributionCoeff * self.finalDrive) - ((simmetric - lock100) + (simmetric + lock100))
self.leftWheel:applyTorque((simmetric - lock100) * 1.33 * -self.leftWheel:getRight())
self.rightWheel:applyTorque((simmetric + lock100) * 1.33 * self.rightWheel:getRight())
self.lwav, self.rwav = self:getAngleVelocity()
_, _, self.avgRPM = self:getRPM()
self.avgRPM = self.avgRPM * self.finalDrive
end
end

View File

@ -0,0 +1,49 @@
--@include /koptilnya/libs/constants.txt
--@include ./wire_component.txt
require('/koptilnya/libs/constants.txt')
require('./wire_component.txt')
PowertrainComponent = class('PowertrainComponent', WireComponent)
function PowertrainComponent:initialize(name)
WireComponent.initialize(self)
self.name = name or 'PowertrainComponent'
self.input = nil
self.output = nil
self.inertia = 0.02
self.angularVelocity = 0
self.torque = 0
end
function PowertrainComponent:getRPM()
return self.angularVelocity * RAD_TO_RPM
end
function PowertrainComponent:queryInertia()
if self.output == nil then
return self.inertia
end
return self.inertia + self.output:queryInertia()
end
function PowertrainComponent:queryAngularVelocity(angularVelocity)
self.angularVelocity = angularVelocity
if self.output == nil then
return 0
end
return self.output:queryAngularVelocity(angularVelocity)
end
function PowertrainComponent:forwardStep(torque, inertia)
if self.output == nil then
return self.torque
end
return self.output:forwardStep(self.torque, self.inertia + inertia)
end

View File

@ -1,68 +1,74 @@
-- Core components --@server
--
--@include ./engine.txt --@include ./engine.txt
--@include ./clutch.txt --@include ./clutch.txt
--@include ./differential.txt --@include ./differential.txt
-- @include ./novojiloff_diff.txt
--
-- Helpers & stuff
--@include ./factories/gearbox.txt --@include ./factories/gearbox.txt
--@include /koptilnya/libs/table.txt --@include /koptilnya/libs/table.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
require('./engine.txt') require('./engine.txt')
require('./clutch.txt') require('./clutch.txt')
--require('./differential.txt') require('./differential.txt')
require('./novojiloff_diff.txt')
require('./factories/gearbox.txt') require('./factories/gearbox.txt')
require('/koptilnya/libs/table.txt') require('/koptilnya/libs/table.txt')
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
Vehicle = class('Vehicle') Vehicle = class('Vehicle')
function Vehicle:initialize(config) function Vehicle:initialize(config)
-- should probably validate config here
if config == nil then if config == nil then
throw('Vehicle config not provided') throw('Vehicle config not provided')
end end
self.engine = Engine:new(config.Engine)
self.clutch = Clutch:new(config.Clutch) self.clutch = Clutch:new(config.Clutch)
self.engine = Engine:new(config.Engine, self.clutch) self.gearbox = GearboxFactory.create(config.Gearbox)
self.axles = table.map(config.Axles, function(config, idx)
local axleOrder = 0 return Differential:new(config, idx)
self.axles = table.map(config.Axles, function(config)
axleOrder = axleOrder + 1
return Differential:new(config, axleOrder)
end) end)
self.gearbox = GearboxFactory.create(config.Gearbox, self.clutch, self.axles) self.components = { self.engine, self.clutch, self.gearbox }
table.add(self.components, self.axles)
-- self.systems = table.map(config.Systems, function(config) self:linkComponents()
-- return SystemsFactory.create(config) self:createIO()
-- end)s
self.components = {self.clutch, self.gearbox}
self.components = table.add(self.components, self.axles)
self.components = table.add(self.components, {self.gearbox, self.clutch, self.engine})
-- self.components = table.add(self.components, self.systems)
hook.add('tick', 'vehicle_update', function()
self:update()
end)
end
function Vehicle:createIO()
local inputs = { local inputs = {
Base = "entity" Base = "entity"
} }
local outputs = {} local outputs = {}
for _, comp in ipairs(self.components) do for _, component in ipairs(self.components) do
inputs = table.merge(inputs, comp:getInputs()) inputs = table.merge(inputs, component.wireInputs)
outputs = table.merge(outputs, comp:getOutputs()) outputs = table.merge(outputs, component.wireOutputs)
end end
wire.adjustPorts(inputs, outputs) wire.adjustPorts(inputs, outputs)
end
hook.add('tick', 'vehicle_update', function() function Vehicle:linkComponents()
self.engine.output = self.clutch
self.clutch.output = self.gearbox
self.gearbox.output = self.axles[1]
self.axles[1].input = self.gearbox
self.gearbox.input = self.clutch
self.clutch.input = self.engine
end
function Vehicle:update()
local base = wire.ports.Base local base = wire.ports.Base
for _, comp in pairs(self.components) do self.engine:integrateDownwards()
comp:update()
comp:updateOutputs() for _, component in pairs(self.components) do
component:updateWireOutputs()
end end
if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then
@ -71,5 +77,4 @@ function Vehicle:initialize(config)
base:applyForceOffset(base:getUp() * tiltForce, base:getMassCenter() + base:getRight() * UNITS_PER_METER) base:applyForceOffset(base:getUp() * tiltForce, base:getMassCenter() + base:getRight() * UNITS_PER_METER)
base:applyForceOffset(-base:getUp() * tiltForce, base:getMassCenter() - base:getRight() * UNITS_PER_METER) base:applyForceOffset(-base:getUp() * tiltForce, base:getMassCenter() - base:getRight() * UNITS_PER_METER)
end end
end)
end end

View File

@ -0,0 +1,61 @@
--@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt
--@include /koptilnya/libs/entity.txt
require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt')
require('/koptilnya/libs/entity.txt')
Wheel = class('Wheel', PowertrainComponent)
function Wheel:initialize(config, prefix)
PowertrainComponent.initialize(self)
self.prefix = prefix
self.wireInputs = {
[prefix .. 'Wheel'] = 'entity'
}
self.wireOutputs = {
[prefix .. 'RPM'] = 'number'
}
self.entity = NULL_ENTITY
hook.add('input', 'vehicle_wheel_update' .. prefix, function(key, val)
if key == prefix .. 'Wheel' then
self.entity = val
if not isValid(val) then
return
end
if config.CalculateInertia then
--self.entity:setInertia(calculateWheelInertia(val))
--print(calculateWheelInertia(val))
end
self.inertia = self.entity:getInertia().y
end
end)
end
function Wheel:updateWireOutputs()
wire.ports[self.prefix .. 'RPM'] = self:selfRPM()
end
function Wheel:queryAngularVelocity()
return self.angularVelocity
end
function Wheel:forwardStep(torque, inertia)
if not isValid(self.entity) then
return 0
end
--self.entity:setInertia(self.entity:getInertia():setY(inertia))
self.entity:applyTorque(torque * self.entity:getRight())
self.angularVelocity = self.entity:getAngleVelocity().y
return self.angularVelocity * self.inertia
end

View File

@ -1,12 +1,9 @@
WireComponent = class('WireComponent') WireComponent = class('WireComponent')
function WireComponent:getInputs() function WireComponent:initialize()
return {} self.wireInputs = {}
self.wireOutputs = {}
end end
function WireComponent:getOutputs() function WireComponent:updateWireOutputs()
return {}
end
function WireComponent:updateOutputs()
end end

View File

@ -5,3 +5,13 @@ function getLocalVelocity(entity)
return entity:worldToLocal(entity:getVelocity() + entity:getPos()) return entity:worldToLocal(entity:getVelocity() + entity:getPos())
end end
function calculateWheelInertia(entity)
if not isValid(entity) then
return Vector(0)
end
local originalInertia = entity:getInertia()
return Vector(originalInertia)
end