This commit is contained in:
Nikita Kruglickiy
2023-05-22 19:06:26 +03:00
parent 345e60cf1d
commit 09be0ea795
35 changed files with 1423 additions and 336 deletions

View File

@@ -0,0 +1,68 @@
--@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt
local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt')
local Clutch = class('Clutch', PowertrainComponent)
function Clutch:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
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:updateWireOutputs()
wire.ports.Clutch_Torque = self.torque
end
function Clutch:getPress()
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:queryAngularVelocity(angularVelocity)
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
return Clutch

View File

@@ -0,0 +1,159 @@
--@include /koptilnya/libs/constants.txt
--@include ./powertrain_component.txt
local PowertrainComponent = require('./powertrain_component.txt')
local WheelComponent = require('./wheel.txt')
require('/koptilnya/libs/constants.txt')
local Differential = class('Differential', PowertrainComponent)
function Differential:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireOutputs = {
Diff_Torque = 'number'
}
self.finalDrive = config.FinalDrive or 4
self.inertia = config.Inertia or 0.2
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.stiffness = config.Stiffness or 0.1
self.slipTorque = config.SlipTorque or 1000
end
function Differential:linkComponent(component)
if not component:isInstanceOf(PowertrainComponent) then
return
end
if self.outputA == nil then
self.outputA = component
component.input = self
elseif self.outputB == nil then
self.outputB = component
component.input = self
end
end
function Differential:updateWireOutputs()
wire.ports.Diff_Torque = self.torque
if self.outputA ~= nil then
self.outputA:updateWireOutputs()
end
if self.outputB ~= nil then
self.outputB:updateWireOutputs()
end
end
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()) / math.pow(self.finalDrive, 2)
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()
self.torque = torque * self.finalDrive
local tqA, tqB = self:_openDiffTorqueSplit(
self.torque,
aW,
bW,
aI,
bI,
self.biasAB,
self.preload,
self.stiffness,
self.powerRamp,
self.coastRamp,
self.slipTorque
)
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 * math.pow(self.finalDrive, 2) + aI) / self.finalDrive
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 * math.pow(self.finalDrive, 2) + bI) / self.finalDrive
return (tqA + tqB) / self.finalDrive
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 = math.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
return Differential

View File

@@ -0,0 +1,113 @@
--@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt
local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt')
local Engine = class('Engine', PowertrainComponent)
function Engine:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = {
Throttle = 'number'
}
self.wireOutputs = {
Engine_RPM = 'number',
Engine_Torque = 'number',
Engine_MasterThrottle = 'number',
Engine_ReactTorque = 'number',
Engine_ReturnedTorque = 'number',
Engine_FinalTorque = '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
self.reactTorque = 0
self.returnedTorque = 0
end
function Engine:updateWireOutputs()
wire.ports.Engine_RPM = self:getRPM()
wire.ports.Engine_Torque = self.torque
wire.ports.Engine_MasterThrottle = self.masterThrottle
wire.ports.Engine_ReactTorque = self.reactTorque
wire.ports.Engine_ReturnedTorque = self.returnedTorque
wire.ports.Engine_FinalTorque = self.finalTorque
end
function Engine:getThrottle()
return wire.ports.Throttle
end
function Engine:_generateTorque()
local throttle = self:getThrottle()
local rpm = self:getRPM()
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(rpmFrac * #self.torqueMap), 1, #self.torqueMap)
local maxInitialTorque = self.torqueMap[tqIdx] - self.friction
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 rpm > self.maxRPM then
throttle = 0
self.limiterTimer = timer.systime()
else
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
self.torque = realInitialTorque + self.friction
return self.torque
end
function Engine:forwardStep()
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
self.reactTorque = reactTorque
self.returnedTorque = returnedTorque
local finalTorque = generatedTorque + reactTorque + returnedTorque
self.finalTorque = finalTorque
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0)
end
return Engine

View File

@@ -0,0 +1,66 @@
--@include ../powertrain_component.txt
local PowertrainComponent = require('../powertrain_component.txt')
local Gearbox = class('Gearbox', PowertrainComponent)
function Gearbox:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = {
Upshift = 'number',
Downshift = 'number'
}
self.wireOutputs = {
Gearbox_RPM = 'number',
Gearbox_Torque = 'number',
Gearbox_Ratio = 'number'
}
self.type = config.Type or 'MANUAL'
self.inertia = config.Inertia or 1000
self.ratio = 0
end
function Gearbox:updateWireOutputs()
wire.ports.Gearbox_RPM = self:getRPM()
wire.ports.Gearbox_Torque = self.torque
wire.ports.Gearbox_Ratio = self.ratio
end
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: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:forwardStep(torque, inertia)
if self.output == nil then
return torque
end
if self.ratio == 0 then
self.output:forwardStep(0, self.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
return Gearbox

View File

@@ -0,0 +1,73 @@
--@include /koptilnya/libs/watcher.txt
--@include ./base.txt
local BaseGearbox = require('./base.txt')
require('/koptilnya/libs/watcher.txt')
local ManualGearbox = class('ManualGearbox', BaseGearbox)
function ManualGearbox:initialize(vehicle, name, config)
BaseGearbox.initialize(self, vehicle, name, 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
local downshift = wire.ports.Downshift or 0
return upshift - downshift
end
self.shiftWatcher = watcher(shiftFunc, function(val)
if val ~= 0 then
self:shift(val)
end
end)
self:recalcRatio()
end
function ManualGearbox:updateWireOutputs()
BaseGearbox.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()
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
function ManualGearbox:shift(dir)
self:setGear(self.gear + dir)
end
function ManualGearbox:forwardStep(torque, inertia)
local result = BaseGearbox.forwardStep(self, torque, inertia)
self.shiftWatcher()
return result
end
return ManualGearbox

View File

@@ -0,0 +1,70 @@
--@include /koptilnya/libs/constants.txt
--@include ../wire_component.txt
local WireComponent = require('../wire_component.txt')
require('/koptilnya/libs/constants.txt')
local PowertrainComponent = class('PowertrainComponent', WireComponent)
function PowertrainComponent:initialize(vehicle, name, config)
config = config or {}
WireComponent.initialize(self)
self.vehicle = vehicle
self.name = name or 'PowertrainComponent'
self.CONFIG = config
self.input = nil
self.output = nil
self.inertia = 0.02
self.angularVelocity = 0
self.torque = 0
end
function PowertrainComponent:start()
end
function PowertrainComponent:linkComponent(component)
if not component:isInstanceOf(PowertrainComponent) then
return
end
if self.output == nil then
self.output = component
component.input = self
end
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
return PowertrainComponent

View File

@@ -0,0 +1,122 @@
--@include ./powertrain_component.txt
--@include ../wheel/wheel.txt
--@include /koptilnya/libs/constants.txt
--@include /koptilnya/libs/entity.txt
local PowertrainComponent = require('./powertrain_component.txt')
local CustomWheel = require('../wheel/wheel.txt')
require('/koptilnya/libs/constants.txt')
require('/koptilnya/libs/entity.txt')
local Wheel = class('Wheel', PowertrainComponent)
function Wheel:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.steerLock = config.SteerLock or 0
self.brakePower = config.BrakePower or 0
self.handbrakePower = config.HandbrakePower or 0
self.wireInputs = {
[self.name] = 'entity'
}
self.wireOutputs = {
[string.format('%s_RPM', self.name)] = 'number',
[string.format('%s_Mz', self.name)] = 'number',
[string.format('%s_Fz', self.name)] = 'number'
}
self.entity = config.Entity or NULL_ENTITY
self.holo = self:createHolo(self.entity)
self.customWheel = CustomWheel:new(config.CustomWheel)
hook.add('input', 'vehicle_wheel_update' .. self.name, function(name, value)
if name == self.name then
self.entity = value
self.customWheel:setEntity(value)
if isValid(self.holo) then
self.holo:remove()
end
self.holo = self:createHolo(self.entity)
if not config.Radius then
self.customWheel.radius = self:getEntityRadius()
end
end
end)
end
function Wheel:getEntityRadius()
if not isValid(self.entity) then
return 0
end
return self.entity:getModelRadius() * UNITS_TO_METERS
end
function Wheel:createHolo(entity)
if not isValid(entity) then
return NULL_ENTITY
end
local holo = holograms.create(entity:getPos(), entity:getAngles(), 'models/props_c17/pulleywheels_small01.mdl')
holo:setParent(entity)
return holo
end
function Wheel:updateWireOutputs()
wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM()
wire.ports[string.format('%s_Mz', self.name)] = self.customWheel.mz
wire.ports[string.format('%s_Fz', self.name)] = self.customWheel.load
end
function Wheel:queryInertia()
return self.customWheel.inertia
end
function Wheel:queryAngularVelocity()
return self.angularVelocity
end
function Wheel:forwardStep(torque, inertia)
if not isValid(self.customWheel) then
return 0
end
self.customWheel.steerAngle = self.vehicle.steer * self.steerLock
--self.customWheel.inertia = inertia
--self.customWheel:setInertia(inertia)
self.customWheel.motorTorque = torque
self.customWheel.brakeTorque = math.max(self.brakePower * self.vehicle.brake, self.handbrakePower * self.vehicle.handbrake)
self.customWheel:update()
self.angularVelocity = self.customWheel.angularVelocity
if self.customWheel.hasHit and isValid(self.vehicle.basePhysObject) then
local surfaceForceVector = self.customWheel.right * self.customWheel.sideFriction.force + self.customWheel.forward * self.customWheel.forwardFriction.force
self.vehicle.basePhysObject:applyForceOffset(surfaceForceVector, self.entity:getPos() - Vector(0, 0, self.customWheel.radius))
end
if isValid(self.holo) then
local angles = self.holo:getAngles()
--local rot = angles:rotateAroundAxis(self.entity:getForward(), nil, 1)
local rot = angles:rotateAroundAxis(self.entity:getRight(), nil, -self.angularVelocity * TICK_INTERVAL)
--local steer = Angle():rotateAroundAxis(self.entity:getUp(), self.customWheel.steerAngle)
--local rot = self.entity:getRight():getQuaternionFromAxis(-self.angularVelocity * TICK_INTERVAL)
--local steer = self.entity:getUp():getQuaternionFromAxis(self.steerAngle)
--self.holo:setAngles(self.entity:localToWorldAngles(Angle(0, 90 - self.customWheel.steerAngle, 0)))
self.holo:setAngles(rot)
end
return self.customWheel.counterTorque
end
return Wheel