From b4dccd73a63a9b043d1958f4f28a097735b2808e Mon Sep 17 00:00:00 2001 From: Nikita Kruglickiy Date: Thu, 28 Jul 2022 15:44:31 +0300 Subject: [PATCH] Refactored vehicle components --- koptilnya/engine_remastered/clutch.txt | 101 +++--- koptilnya/engine_remastered/differential.txt | 327 +++++++----------- koptilnya/engine_remastered/engine.txt | 109 +++--- .../engine_remastered/gearboxes/base.txt | 90 ++--- .../engine_remastered/gearboxes/manual.txt | 50 ++- .../engine_remastered/novojiloff_diff.txt | 99 ------ .../powertrain_component.txt | 49 +++ koptilnya/engine_remastered/vehicle.txt | 101 +++--- koptilnya/engine_remastered/wheel.txt | 61 ++++ .../engine_remastered/wire_component.txt | 11 +- koptilnya/libs/entity.txt | 10 + 11 files changed, 485 insertions(+), 523 deletions(-) delete mode 100644 koptilnya/engine_remastered/novojiloff_diff.txt create mode 100644 koptilnya/engine_remastered/powertrain_component.txt create mode 100644 koptilnya/engine_remastered/wheel.txt diff --git a/koptilnya/engine_remastered/clutch.txt b/koptilnya/engine_remastered/clutch.txt index 9bb9ec4..e02ec6e 100644 --- a/koptilnya/engine_remastered/clutch.txt +++ b/koptilnya/engine_remastered/clutch.txt @@ -1,72 +1,67 @@ --- @include ./wire_component.txt --- @include ./enums/gearbox.txt --- @include /koptilnya/libs/constants.txt +--@include ./powertrain_component.txt +--@include ./enums/gearbox.txt +--@include /koptilnya/libs/constants.txt + require('/koptilnya/libs/constants.txt') -require('./wire_component.txt') +require('./powertrain_component.txt') local gearboxTypes = require('./enums/gearbox.txt') -Clutch = class('Clutch', WireComponent) +Clutch = class('Clutch', PowertrainComponent) function Clutch:initialize(config) - self.stiffness = config.Stiffness - self.damping = config.Damping - self.maxTorque = config.MaxTorque + PowertrainComponent.initialize(self, 'Clutch') - self.press = 0 - 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' - } - else - return {} - end -end - -function Clutch:getOutputs() - return { - Clutch_Torque = 'number', - Clutch_Slip = 'number' + self.wireInputs = { + Clutch = 'number' } + self.wireOutputs = { + Clutch_Torque = 'number' + } + + self.inertia = config.Inertia or 0.1 + self.slipTorque = config.SlipTorque or 1000 end -function Clutch:updateOutputs() +function Clutch:updateWireOutputs() wire.ports.Clutch_Torque = self.torque - wire.ports.Clutch_Slip = self.slip end function Clutch:getPress() - if self.gearbox ~= nil and self.gearbox.type == gearboxTypes.MANUAL then - return wire.ports.Clutch - else - return self.press + return 1 - wire.ports.Clutch +end + +function Clutch:queryInertia() + if self.output == nil then + return self.inertia end + + return self.inertia + self.output:queryInertia() * self:getPress() end -function Clutch:update() - local engRPM = self.engine and self.engine.rpm or 0 - local gboxRPM = self.gearbox and self.gearbox.rpm or 0 - local gboxRatio = self.gearbox and self.gearbox.ratio or 0 - local gboxRatioNotZero = gboxRatio ~= 0 and 1 or 0 +function Clutch:queryAngularVelocity(angularVelocity) + self.angularVelocity = angularVelocity - self.slip = ((engRPM - gboxRPM) * RPM_TO_RAD) * gboxRatioNotZero / 2 - self.targetTorque = math.clamp(self.slip * self.stiffness * (1 - self:getPress()), -self.maxTorque, self.maxTorque) + if self.output == nil then + return angularVelocity + end - self.torque = math.lerp(self.damping, self.torque, self.targetTorque) + 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 diff --git a/koptilnya/engine_remastered/differential.txt b/koptilnya/engine_remastered/differential.txt index d92361f..e1d8dea 100644 --- a/koptilnya/engine_remastered/differential.txt +++ b/koptilnya/engine_remastered/differential.txt @@ -1,207 +1,148 @@ --- @server --- @include /koptilnya/libs/constants.txt --- @include ./wire_component.txt -require('/koptilnya/libs/constants.txt') -require('./wire_component.txt') +--@include /koptilnya/libs/constants.txt +--@include ./powertrain_component.txt +--@include ./wheel.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) - self.power = config.Power or 1 - self.coast = config.Coast or 1 - self.finalDrive = config.FinalDrive or 1 + PowertrainComponent.initialize(self, 'Axle') + + 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.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 - self.maxCorrectingTorque = config.MaxCorrectingTorque or 200 - - self.avgRPM = 0 - - 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 - - if key == self.prefix .. 'RightWheel' then - self.rightWheel = val - end - end) + table.merge(self.wireInputs, self.outputA.wireInputs) + table.merge(self.wireInputs, self.outputB.wireInputs) + table.merge(self.wireOutputs, self.outputA.wireOutputs) + table.merge(self.wireOutputs, self.outputB.wireOutputs) end -function Differential:getInputs() - local inputs = {} +function Differential:updateWireOutputs() + if self.outputA ~= nil then + self.outputA:updateWireOutputs() + end - 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) + if self.outputB ~= nil then + self.outputB:updateWireOutputs() end end -function Differential:update() - 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]) * -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 +function Differential:queryInertia() + if self.outputA == nil or self.outputB == nil then + return self.inertia 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 diff --git a/koptilnya/engine_remastered/engine.txt b/koptilnya/engine_remastered/engine.txt index 134c919..ff990a2 100644 --- a/koptilnya/engine_remastered/engine.txt +++ b/koptilnya/engine_remastered/engine.txt @@ -1,53 +1,40 @@ --- @include ./wire_component.txt --- @include /koptilnya/libs/constants.txt -require('./wire_component.txt') +--@include ./powertrain_component.txt +--@include /koptilnya/libs/constants.txt + +require('./powertrain_component.txt') require('/koptilnya/libs/constants.txt') -Engine = class('Engine', WireComponent) +Engine = class('Engine', PowertrainComponent) function Engine:initialize(config, clutch) - self.idleRPM = config.IdleRPM - self.maxRPM = config.MaxRPM + PowertrainComponent.initialize(self, 'Engine') - self.flywheelMass = config.FlywheelMass - 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 { + self.wireInputs = { Throttle = 'number' } -end - -function Engine:getOutputs() - return { + self.wireOutputs = { Engine_RPM = 'number', Engine_Torque = '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 -function Engine:updateOutputs() - wire.ports.Engine_RPM = self.rpm +function Engine:updateWireOutputs() + wire.ports.Engine_RPM = self:getRPM() wire.ports.Engine_Torque = self.torque wire.ports.Engine_MasterThrottle = self.masterThrottle end @@ -56,35 +43,53 @@ function Engine:getThrottle() return wire.ports.Throttle end -function Engine:update() +function Engine:_generateTorque() local throttle = self:getThrottle() + local rpm = self:getRPM() - self.rpmFrac = math.clamp((self.rpm - self.idleRPM) / (self.maxRPM - self.idleRPM), 0, 1) - self.friction = self.startFriction - self.rpm * self.frictionCoeff + local rpmFrac = math.clamp((rpm - self.idleRPM) / (self.maxRPM - self.idleRPM), 0, 1) + 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 idleFadeStart = math.clamp(math.remap(self.rpm, self.idleRPM - 300, self.idleRPM, 1, 0), 0, 1) - local idleFadeEnd = math.clamp(math.remap(self.rpm, self.idleRPM, self.idleRPM + 600, 1, 0), 0, 1) - + local idleFadeStart = math.clamp(math.remap(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 additionalEnergySupply = idleFadeEnd * (-self.friction / maxInitialTorque) + idleFadeStart - if self.rpm > self.maxRPM then + if rpm > self.maxRPM then throttle = 0 - self._limiterTime = timer.systime() + self.limiterTimer = timer.systime() else - throttle = timer.systime() >= self._limiterTime + self.limiterDuration and throttle or 0 + throttle = timer.systime() >= self.limiterTimer + self.limiterDuration and throttle or 0 end self.masterThrottle = math.clamp(additionalEnergySupply + throttle, 0, 1) local realInitialTorque = maxInitialTorque * self.masterThrottle - local loadTorque = self.clutch and self.clutch.torque or 0 - self.torque = realInitialTorque + self.friction - self.rpm = self.rpm + (self.torque - loadTorque) / self._fwInertia * RAD_TO_RPM * TICK_INTERVAL - self.rpm = math.max(self.rpm, 0) + return self.torque +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 diff --git a/koptilnya/engine_remastered/gearboxes/base.txt b/koptilnya/engine_remastered/gearboxes/base.txt index cbb4e2c..795c4c1 100644 --- a/koptilnya/engine_remastered/gearboxes/base.txt +++ b/koptilnya/engine_remastered/gearboxes/base.txt @@ -1,82 +1,64 @@ --- @include ../wire_component.txt -require('../wire_component.txt') +--@include ../powertrain_component.txt -Gearbox = class('Gearbox', WireComponent) +require('../powertrain_component.txt') -function Gearbox:initialize(config, clutch, axles) - self.ratios = config.Ratios - self.reverse = config.Reverse +Gearbox = class('Gearbox', PowertrainComponent) - self.rpm = 0 - self.gear = 0 - self.torque = 0 +function Gearbox:initialize(config) + PowertrainComponent.initialize(self, 'Gearbox') - self.axles = axles or {} - 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 { + self.wireInputs = { Upshift = 'number', Downshift = 'number' } -end - -function Gearbox:getOutputs() - return { + self.wireOutputs = { Gearbox_RPM = 'number', Gearbox_Torque = 'number', - Gearbox_Gear = 'number', Gearbox_Ratio = 'number' } + + self.type = config.Type or 'MANUAL' + self.inertia = config.Inertia or 1000 + + self.ratio = 0 end -function Gearbox:updateOutputs() - wire.ports.Gearbox_RPM = self.rpm +function Gearbox:updateWireOutputs() + wire.ports.Gearbox_RPM = self:getRPM() wire.ports.Gearbox_Torque = self.torque - wire.ports.Gearbox_Gear = self.gear wire.ports.Gearbox_Ratio = self.ratio end -function Gearbox:setGear(gear) - if gear >= -1 and gear <= #self.ratios then - self.gear = gear - self:recalcRatio() +function Gearbox:queryInertia() + if self.output == nil or self.ratio == 0 then + return self.inertia end + + return self.inertia + self.output:queryInertia() / math.pow(self.ratio, 2) end -function Gearbox:recalcRatio() - if self.gear == -1 then - self.ratio = -self.reverse - elseif self.gear == 0 then - self.ratio = 0 - else - self.ratio = self.ratios[self.gear] +function Gearbox:queryAngularVelocity(angularVelocity) + self.angularVelocity = angularVelocity + + if self.output == nil or self.ratio == 0 then + return angularVelocity end + + return self.output:queryAngularVelocity(angularVelocity) * self.ratio end -function Gearbox:update() - if self.clutch ~= nil then - self.torque = self.clutch.torque * self.ratio +function Gearbox:forwardStep(torque, inertia) + if self.output == nil then + return torque end - local maxAxlesRPM = 0 + if self.ratio == 0 then + self.output:forwardStep(0, inertia) - if #self.axles > 0 then - maxAxlesRPM = math.max(unpack(table.map(self.axles, function(diff) - return diff.avgRPM - end))) + return torque end - self.rpm = maxAxlesRPM * self.ratio -end \ No newline at end of file + self.torque = torque * self.ratio + + return self.output:forwardStep(self.torque, (inertia + self.inertia) * math.pow(self.ratio, 2)) / self.ratio +end diff --git a/koptilnya/engine_remastered/gearboxes/manual.txt b/koptilnya/engine_remastered/gearboxes/manual.txt index 5688eed..1a5e3be 100644 --- a/koptilnya/engine_remastered/gearboxes/manual.txt +++ b/koptilnya/engine_remastered/gearboxes/manual.txt @@ -1,12 +1,22 @@ --- @include /koptilnya/libs/watcher.txt --- @include ./base.txt +--@include /koptilnya/libs/watcher.txt +--@include ./base.txt + require('/koptilnya/libs/watcher.txt') require('./base.txt') ManualGearbox = class('ManualGearbox', Gearbox) -function ManualGearbox:initialize(...) - Gearbox.initialize(self, ...) +function ManualGearbox:initialize(config) + 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() local upshift = wire.ports.Upshift or 0 @@ -20,6 +30,21 @@ function ManualGearbox:initialize(...) self:shift(val) 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 function ManualGearbox:recalcRatio() @@ -36,19 +61,10 @@ function ManualGearbox:shift(dir) self:setGear(self.gear + dir) end --- function ManualGearbox:getOutputs() --- return { --- Gearbox_RPM = 'number', --- Gearbox_Torque = 'number' --- } --- end - --- function ManualGearbox:updateOutputs() - --- end - -function ManualGearbox:update() - Gearbox.update(self) +function ManualGearbox:forwardStep(torque, inertia) + local result = Gearbox.forwardStep(self, torque, inertia) self.shiftWatcher() + + return result end diff --git a/koptilnya/engine_remastered/novojiloff_diff.txt b/koptilnya/engine_remastered/novojiloff_diff.txt deleted file mode 100644 index 80c01c6..0000000 --- a/koptilnya/engine_remastered/novojiloff_diff.txt +++ /dev/null @@ -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 diff --git a/koptilnya/engine_remastered/powertrain_component.txt b/koptilnya/engine_remastered/powertrain_component.txt new file mode 100644 index 0000000..7580a77 --- /dev/null +++ b/koptilnya/engine_remastered/powertrain_component.txt @@ -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 diff --git a/koptilnya/engine_remastered/vehicle.txt b/koptilnya/engine_remastered/vehicle.txt index 9fd7daa..9816c21 100644 --- a/koptilnya/engine_remastered/vehicle.txt +++ b/koptilnya/engine_remastered/vehicle.txt @@ -1,75 +1,80 @@ --- Core components --- --- @include ./engine.txt --- @include ./clutch.txt --- @include ./differential.txt --- @include ./novojiloff_diff.txt --- --- Helpers & stuff --- @include ./factories/gearbox.txt --- @include /koptilnya/libs/table.txt --- @include /koptilnya/libs/constants.txt +--@server +--@include ./engine.txt +--@include ./clutch.txt +--@include ./differential.txt +--@include ./factories/gearbox.txt +--@include /koptilnya/libs/table.txt +--@include /koptilnya/libs/constants.txt + require('./engine.txt') require('./clutch.txt') ---require('./differential.txt') -require('./novojiloff_diff.txt') +require('./differential.txt') require('./factories/gearbox.txt') - require('/koptilnya/libs/table.txt') require('/koptilnya/libs/constants.txt') Vehicle = class('Vehicle') function Vehicle:initialize(config) - -- should probably validate config here if config == nil then throw('Vehicle config not provided') end + self.engine = Engine:new(config.Engine) self.clutch = Clutch:new(config.Clutch) - self.engine = Engine:new(config.Engine, self.clutch) - - local axleOrder = 0 - self.axles = table.map(config.Axles, function(config) - axleOrder = axleOrder + 1 - return Differential:new(config, axleOrder) + self.gearbox = GearboxFactory.create(config.Gearbox) + self.axles = table.map(config.Axles, function(config, idx) + return Differential:new(config, idx) 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) - -- return SystemsFactory.create(config) - -- 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) + self:linkComponents() + self:createIO() + hook.add('tick', 'vehicle_update', function() + self:update() + end) +end + +function Vehicle:createIO() local inputs = { Base = "entity" } local outputs = {} - for _, comp in ipairs(self.components) do - inputs = table.merge(inputs, comp:getInputs()) - outputs = table.merge(outputs, comp:getOutputs()) + for _, component in ipairs(self.components) do + inputs = table.merge(inputs, component.wireInputs) + outputs = table.merge(outputs, component.wireOutputs) end wire.adjustPorts(inputs, outputs) - - hook.add('tick', 'vehicle_update', function() - local base = wire.ports.Base - - for _, comp in pairs(self.components) do - comp:update() - comp:updateOutputs() - end - - if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then - local tiltForce = self.engine.torque * (-1 + self.engine.masterThrottle * 2) - - 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 + +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 + + self.engine:integrateDownwards() + + for _, component in pairs(self.components) do + component:updateWireOutputs() + end + + if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then + local tiltForce = self.engine.torque * (-1 + self.engine.masterThrottle * 2) + + 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 diff --git a/koptilnya/engine_remastered/wheel.txt b/koptilnya/engine_remastered/wheel.txt new file mode 100644 index 0000000..8be36fd --- /dev/null +++ b/koptilnya/engine_remastered/wheel.txt @@ -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 diff --git a/koptilnya/engine_remastered/wire_component.txt b/koptilnya/engine_remastered/wire_component.txt index 9f442c8..67e9aae 100644 --- a/koptilnya/engine_remastered/wire_component.txt +++ b/koptilnya/engine_remastered/wire_component.txt @@ -1,12 +1,9 @@ WireComponent = class('WireComponent') -function WireComponent:getInputs() - return {} +function WireComponent:initialize() + self.wireInputs = {} + self.wireOutputs = {} end -function WireComponent:getOutputs() - return {} -end - -function WireComponent:updateOutputs() +function WireComponent:updateWireOutputs() end diff --git a/koptilnya/libs/entity.txt b/koptilnya/libs/entity.txt index 2db67fe..b7a08db 100644 --- a/koptilnya/libs/entity.txt +++ b/koptilnya/libs/entity.txt @@ -5,3 +5,13 @@ function getLocalVelocity(entity) return entity:worldToLocal(entity:getVelocity() + entity:getPos()) end + +function calculateWheelInertia(entity) + if not isValid(entity) then + return Vector(0) + end + + local originalInertia = entity:getInertia() + + return Vector(originalInertia) +end