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

@@ -1,39 +1,65 @@
-- @name SX 240
-- @author Koptilnya1337
-- @server
-- @include /koptilnya/engine_remastered/vehicle.txt
require('/koptilnya/engine_remastered/vehicle.txt')
--@name SX 240
--@author Koptilnya
--@server
--@include /koptilnya/engine_remastered/vehicle.txt
local Vehicle, POWERTRAIN_COMPONENT = unpack(require('/koptilnya/engine_remastered/vehicle.txt'))
local WheelConfig = {
BrakePower = 800,
CustomWheel = {}
}
local FrontWheelsConfig = table.merge(table.copy(WheelConfig), { SteerLock = 10 })
local RearWheelsConfig = table.merge(table.copy(WheelConfig), { HandbrakePower = 2000 })
Vehicle:new({
Engine = {
IdleRPM = 900,
MaxRPM = 7500,
Inertia = 0.151,
StartFriction = -30,
FrictionCoeff = 0.02,
LimiterDuration = 0.08,
TorqueMap = {
118.89918444138, 122.0751393736, 125.25109430583, 128.42704923806, 131.60300417029, 134.77895910251, 137.95491403474, 141.13086896697, 144.3068238992, 147.48277883143, 150.65873376365, 153.83468869588, 157.01064362811, 160.18659856034, 163.36255349256,
166.53850842479, 169.71446335702, 172.89041828925, 176.06637322148, 179.2423281537, 182.41828308593, 185.59423801816, 188.77019295039, 191.94614788261, 195.12210281484, 198.29805774707, 201.4740126793, 204.64996761153, 207.82592254375, 211.00187747598,
214.17783240821, 217.35378734044, 220.52974227266, 223.70569720489, 226.88165213712, 227.9621903219, 229.04272850669, 230.12326669147, 231.20380487625, 232.28434306104, 233.36488124582, 234.4454194306, 235.52595761538, 236.60649580017, 237.68703398495,
238.76757216973, 239.84811035452, 240.9286485393, 242.00918672408, 243.08972490886, 244.17026309365, 245.25080127843, 246.33133946321, 247.411877648, 248.49241583278, 249.57295401756, 250.65349220234, 251.73403038713, 252.81456857191, 253.89510675669, 254.97564494148,
256.05618312626, 257.13672131104, 258.21725949582, 259.29779768061, 260.37833586539, 261.29278066787, 262.20722547034, 263.12167027282, 264.0361150753, 264.95055987777, 265.86500468025, 266.77944948273, 267.6938942852, 268.60833908768, 269.52278389016,
270.43722869263, 271.35167349511, 272.26611829758, 273.18056310006, 274.09500790254, 275.00945270501, 275.92389750749, 276.83834230997, 275.50329427594, 274.16824624192, 272.8331982079, 271.49815017388, 270.16310213985, 268.82805410583, 267.49300607181,
266.15795803778, 264.82291000376, 263.48786196974, 262.15281393572, 260.81776590169, 259.48271786767, 250.23203287321, 240.98134787874, 231.73066288428, 222.47997788981
{
Name = 'Engine',
Type = POWERTRAIN_COMPONENT.Engine,
Config = {
IdleRPM = 900,
MaxRPM = 7500,
Inertia = 0.151,
StartFriction = -10,
FrictionCoeff = 0.01,
LimiterDuration = 0.08,
TorqueMap = {
118.89918444138, 122.0751393736, 125.25109430583, 128.42704923806, 131.60300417029, 134.77895910251, 137.95491403474, 141.13086896697, 144.3068238992, 147.48277883143, 150.65873376365, 153.83468869588, 157.01064362811, 160.18659856034, 163.36255349256,
166.53850842479, 169.71446335702, 172.89041828925, 176.06637322148, 179.2423281537, 182.41828308593, 185.59423801816, 188.77019295039, 191.94614788261, 195.12210281484, 198.29805774707, 201.4740126793, 204.64996761153, 207.82592254375, 211.00187747598,
214.17783240821, 217.35378734044, 220.52974227266, 223.70569720489, 226.88165213712, 227.9621903219, 229.04272850669, 230.12326669147, 231.20380487625, 232.28434306104, 233.36488124582, 234.4454194306, 235.52595761538, 236.60649580017, 237.68703398495,
238.76757216973, 239.84811035452, 240.9286485393, 242.00918672408, 243.08972490886, 244.17026309365, 245.25080127843, 246.33133946321, 247.411877648, 248.49241583278, 249.57295401756, 250.65349220234, 251.73403038713, 252.81456857191, 253.89510675669, 254.97564494148,
256.05618312626, 257.13672131104, 258.21725949582, 259.29779768061, 260.37833586539, 261.29278066787, 262.20722547034, 263.12167027282, 264.0361150753, 264.95055987777, 265.86500468025, 266.77944948273, 267.6938942852, 268.60833908768, 269.52278389016,
270.43722869263, 271.35167349511, 272.26611829758, 273.18056310006, 274.09500790254, 275.00945270501, 275.92389750749, 276.83834230997, 275.50329427594, 274.16824624192, 272.8331982079, 271.49815017388, 270.16310213985, 268.82805410583, 267.49300607181,
266.15795803778, 264.82291000376, 263.48786196974, 262.15281393572, 260.81776590169, 259.48271786767, 250.23203287321, 240.98134787874, 231.73066288428, 222.47997788981
}
}
},
Clutch = {
Inertia = 0.002,
SlipTorque = 1000
{
Name = 'Clutch',
Input = 'Engine',
Type = POWERTRAIN_COMPONENT.Clutch,
Config = {
Inertia = 0.002,
SlipTorque = 1000
}
},
Gearbox = {
Type = 'MANUAL',
Inertia = 0.01,
Ratios = { 3.626, 2.200, 1.541, 1.213, 1.000, 0.767 },
Reverse = 3.437
{
Name = 'Gearbox',
Input = 'Clutch',
Type = POWERTRAIN_COMPONENT.Gearbox,
Config = {
Type = 'MANUAL',
Inertia = 0.01,
Ratios = { 3.626, 2.200, 1.541, 1.213, 1.000, 0.767 },
Reverse = 3.437
}
},
Axles = {
{
{
Name = 'Axle1',
Input = 'Gearbox',
Type = POWERTRAIN_COMPONENT.Differential,
Config = {
FinalDrive = 3.392,
Inertia = 0.01,
BiasAB = 0.5,
@@ -42,5 +68,27 @@ Vehicle:new({
Stiffness = 0.1,
SlipTorque = 1000
}
},
{
Name = 'WheelFL',
Input = 'Axle1',
Type = POWERTRAIN_COMPONENT.Wheel,
Config = FrontWheelsConfig
},
{
Name = 'WheelFR',
Input = 'Axle1',
Type = POWERTRAIN_COMPONENT.Wheel,
Config = FrontWheelsConfig
},
{
Name = 'WheelRL',
Type = POWERTRAIN_COMPONENT.Wheel,
Config = RearWheelsConfig
},
{
Name = 'WheelRR',
Type = POWERTRAIN_COMPONENT.Wheel,
Config = RearWheelsConfig
}
})

View File

@@ -1,7 +1,7 @@
types = {
local Types = {
MANUAL = 'MANUAL',
AUTO = 'AUTOMATIC/ROBOT',
CVT = 'CVT'
}
return types
return Types

View File

@@ -0,0 +1,7 @@
return {
Engine = 1,
Clutch = 2,
Gearbox = 3,
Differential = 4,
Wheel = 5
}

View File

@@ -1,10 +1,11 @@
-- @include ../gearboxes/manual.txt
require('../gearboxes/manual.txt')
-- require('../gearboxes/auto.txt')
-- require('../gearboxes/cvt.txt')
-- @include ../powertrain/gearboxes/manual.txt
GearboxFactory = class('GearboxFactory')
local ManualGearbox = require('../powertrain/gearboxes/manual.txt')
function GearboxFactory.create(...)
local GearboxFactory = class('GearboxFactory')
function GearboxFactory.static:create(...)
return ManualGearbox:new(...)
end
return GearboxFactory

View File

@@ -0,0 +1,34 @@
--@include ../powertrain/engine.txt
--@include ../powertrain/clutch.txt
--@include ../powertrain/differential.txt
--@include ../powertrain/wheel.txt
--@include ./gearbox.txt
--@include ../enums/powertrain_component.txt
local Engine = require('../powertrain/engine.txt')
local Clutch = require('../powertrain/clutch.txt')
local Differential = require('../powertrain/differential.txt')
local Wheel = require('../powertrain/wheel.txt')
local GearboxFactory = require('./gearbox.txt')
local POWERTRAIN_COMPONENT = require('../enums/powertrain_component.txt')
local PowertrainComponentFactory = class('PowertrainComponentFactory')
function PowertrainComponentFactory.static:create(vehicle, type, name, config)
local args = { vehicle, name, config }
if type == POWERTRAIN_COMPONENT.Engine then
return Engine:new(unpack(args))
elseif type == POWERTRAIN_COMPONENT.Clutch then
return Clutch:new(unpack(args))
elseif type == POWERTRAIN_COMPONENT.Gearbox then
return GearboxFactory:create(unpack(args))
elseif type == POWERTRAIN_COMPONENT.Differential then
return Differential:new(unpack(args))
elseif type == POWERTRAIN_COMPONENT.Wheel then
return Wheel:new(unpack(args))
end
end
return PowertrainComponentFactory

View File

@@ -1,15 +1,14 @@
--@include ./powertrain_component.txt
--@include ./enums/gearbox.txt
--@include /koptilnya/libs/constants.txt
local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt')
require('./powertrain_component.txt')
local gearboxTypes = require('./enums/gearbox.txt')
Clutch = class('Clutch', PowertrainComponent)
local Clutch = class('Clutch', PowertrainComponent)
function Clutch:initialize(config)
PowertrainComponent.initialize(self, 'Clutch')
function Clutch:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = {
Clutch = 'number'
@@ -65,3 +64,5 @@ function Clutch:forwardStep(torque, inertia)
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
end
return Clutch

View File

@@ -1,22 +1,21 @@
--@include /koptilnya/libs/constants.txt
--@include ./powertrain_component.txt
--@include ./wheel.txt
local PowertrainComponent = require('./powertrain_component.txt')
local WheelComponent = require('./wheel.txt')
require('/koptilnya/libs/constants.txt')
require('./powertrain_component.txt')
require('./wheel.txt')
Differential = class('Differential', PowertrainComponent)
local Differential = class('Differential', PowertrainComponent)
function Differential:initialize(config, order)
PowertrainComponent.initialize(self, 'Axle')
function Differential:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireOutputs = {
Diff_Torque = 'number'
}
local prefix = 'Axle' .. (order or '') .. '_'
self.finalDrive = config.FinalDrive or 4
self.inertia = config.Inertia or 0.2
self.biasAB = config.Bias or 0.5
@@ -25,20 +24,20 @@ function Differential:initialize(config, order)
self.preload = config.Preload or 10
self.stiffness = config.Stiffness or 0.1
self.slipTorque = config.SlipTorque or 1000
end
local wheelConfig = {
CalculateInertia = true
}
self.outputA = Wheel:new(wheelConfig, prefix .. 'Left')
self.outputB = Wheel:new(table.merge(wheelConfig, { Direction = -1 }), prefix .. 'Right')
function Differential:linkComponent(component)
if not component:isInstanceOf(PowertrainComponent) then
return
end
self.outputA.input = self
self.outputB.input = self
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)
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()
@@ -58,7 +57,7 @@ function Differential:queryInertia()
return self.inertia
end
return self.inertia + self.outputA:queryInertia() + self.outputB:queryInertia()
return self.inertia + (self.outputA:queryInertia() + self.outputB:queryInertia()) / math.pow(self.finalDrive, 2)
end
function Differential:queryAngularVelocity(angularVelocity)
@@ -100,8 +99,8 @@ function Differential:forwardStep(torque, inertia)
self.slipTorque
)
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 + aI)
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 + bI)
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
@@ -156,3 +155,5 @@ function Differential:_HLSDTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stif
return tq * (1 - biasAB) - dTq, tq * biasAB + dTq
end
return Differential

View File

@@ -1,13 +1,14 @@
--@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt
require('./powertrain_component.txt')
local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt')
Engine = class('Engine', PowertrainComponent)
local Engine = class('Engine', PowertrainComponent)
function Engine:initialize(config, clutch)
PowertrainComponent.initialize(self, 'Engine')
function Engine:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = {
Throttle = 'number'
@@ -15,7 +16,10 @@ function Engine:initialize(config, clutch)
self.wireOutputs = {
Engine_RPM = 'number',
Engine_Torque = 'number',
Engine_MasterThrottle = 'number'
Engine_MasterThrottle = 'number',
Engine_ReactTorque = 'number',
Engine_ReturnedTorque = 'number',
Engine_FinalTorque = 'number'
}
self.idleRPM = config.IdleRPM or 900
@@ -31,12 +35,18 @@ function Engine:initialize(config, clutch)
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()
@@ -72,7 +82,7 @@ function Engine:_generateTorque()
return self.torque
end
function Engine:integrateDownwards()
function Engine:forwardStep()
if self.output == nil then
local generatedTorque = self:_generateTorque()
@@ -88,8 +98,16 @@ function Engine:integrateDownwards()
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.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

@@ -1,11 +1,11 @@
--@include ../powertrain_component.txt
require('../powertrain_component.txt')
local PowertrainComponent = require('../powertrain_component.txt')
Gearbox = class('Gearbox', PowertrainComponent)
local Gearbox = class('Gearbox', PowertrainComponent)
function Gearbox:initialize(config)
PowertrainComponent.initialize(self, 'Gearbox')
function Gearbox:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = {
Upshift = 'number',
@@ -53,7 +53,7 @@ function Gearbox:forwardStep(torque, inertia)
end
if self.ratio == 0 then
self.output:forwardStep(0, inertia)
self.output:forwardStep(0, self.inertia)
return torque
end
@@ -62,3 +62,5 @@ function Gearbox:forwardStep(torque, inertia)
return self.output:forwardStep(self.torque, (inertia + self.inertia) * math.pow(self.ratio, 2)) / self.ratio
end
return Gearbox

View File

@@ -1,13 +1,14 @@
--@include /koptilnya/libs/watcher.txt
--@include ./base.txt
local BaseGearbox = require('./base.txt')
require('/koptilnya/libs/watcher.txt')
require('./base.txt')
ManualGearbox = class('ManualGearbox', Gearbox)
local ManualGearbox = class('ManualGearbox', BaseGearbox)
function ManualGearbox:initialize(config)
Gearbox.initialize(self, config)
function ManualGearbox:initialize(vehicle, name, config)
BaseGearbox.initialize(self, vehicle, name, config)
table.merge(self.wireOutputs, {
Gearbox_Gear = 'number'
@@ -35,7 +36,7 @@ function ManualGearbox:initialize(config)
end
function ManualGearbox:updateWireOutputs()
Gearbox.updateWireOutputs(self)
BaseGearbox.updateWireOutputs(self)
wire.ports.Gearbox_Gear = self.gear
end
@@ -62,9 +63,11 @@ function ManualGearbox:shift(dir)
end
function ManualGearbox:forwardStep(torque, inertia)
local result = Gearbox.forwardStep(self, torque, inertia)
local result = BaseGearbox.forwardStep(self, torque, inertia)
self.shiftWatcher()
return result
end
return ManualGearbox

View File

@@ -1,15 +1,20 @@
--@include /koptilnya/libs/constants.txt
--@include ./wire_component.txt
--@include ../wire_component.txt
local WireComponent = require('../wire_component.txt')
require('/koptilnya/libs/constants.txt')
require('./wire_component.txt')
PowertrainComponent = class('PowertrainComponent', WireComponent)
local PowertrainComponent = class('PowertrainComponent', WireComponent)
function PowertrainComponent:initialize(vehicle, name, config)
config = config or {}
function PowertrainComponent:initialize(name)
WireComponent.initialize(self)
self.vehicle = vehicle
self.name = name or 'PowertrainComponent'
self.CONFIG = config
self.input = nil
self.output = nil
@@ -18,6 +23,20 @@ function PowertrainComponent:initialize(name)
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
@@ -47,3 +66,5 @@ function PowertrainComponent:forwardStep(torque, inertia)
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

View File

@@ -1,46 +1,86 @@
--@server
--@include ./engine.txt
--@include ./clutch.txt
--@include ./differential.txt
--@include ./factories/gearbox.txt
--@include ./enums/powertrain_component.txt
--@include ./factories/powertrain_component.txt
--@include /koptilnya/libs/table.txt
--@include /koptilnya/libs/constants.txt
require('./engine.txt')
require('./clutch.txt')
require('./differential.txt')
require('./factories/gearbox.txt')
local PowertrainComponentFactory = require('./factories/powertrain_component.txt')
local CustomWheel = require('./wheel/wheel.txt')
local POWERTRAIN_COMPONENT = require('./enums/powertrain_component.txt')
require('/koptilnya/libs/table.txt')
require('/koptilnya/libs/constants.txt')
Vehicle = class('Vehicle')
local Vehicle = class('Vehicle')
function Vehicle:initialize(config)
if config == nil then
throw('Vehicle config not provided')
if not Vehicle:validateConfig(config) then
throw('Please check your vehicle configuration!')
end
self.engine = Engine:new(config.Engine)
self.clutch = Clutch:new(config.Clutch)
self.gearbox = GearboxFactory.create(config.Gearbox)
self.axles = table.map(config.Axles, function(config, idx)
return Differential:new(config, idx)
end)
self.components = { self.engine, self.clutch, self.gearbox }
table.add(self.components, self.axles)
self.config = config
self.components = {}
self.independentComponents = {}
self:createComponents()
self:linkComponents()
self:createIO()
self.rootComponent = self:getRootComponent()
self.steer = 0
self.brake = 0
self.handbrake = 0
hook.add('input', 'vehicle_wire_input', function(name, value)
self:handleWireInput(name, value)
end)
hook.add('tick', 'vehicle_update', function()
self:update()
end)
end
function Vehicle.static:validateConfig(config)
return type(config) == 'table'
end
function Vehicle:getComponentByName(name)
return table.find(self.components, function(component)
return component.name == name
end)
end
function Vehicle:createComponents()
for _, componentConfig in pairs(self.config) do
local component = PowertrainComponentFactory:create(self, componentConfig.Type, componentConfig.Name, componentConfig.Config)
table.insert(self.components, component)
end
end
function Vehicle:linkComponents()
for _, componentConfig in pairs(self.config) do
local component = self:getComponentByName(componentConfig.Name)
if componentConfig.Type == POWERTRAIN_COMPONENT.Wheel and componentConfig.Input == nil then
table.insert(self.independentComponents, component)
else
local inputComponent = self:getComponentByName(componentConfig.Input)
if inputComponent ~= nil then
inputComponent:linkComponent(component)
end
end
end
end
function Vehicle:createIO()
local inputs = {
Base = "entity"
Base = 'entity',
Steer = 'number',
Brake = 'number',
Handbrake = 'number'
}
local outputs = {}
@@ -52,29 +92,47 @@ function Vehicle:createIO()
wire.adjustPorts(inputs, outputs)
end
function Vehicle:linkComponents()
self.engine.output = self.clutch
self.clutch.output = self.gearbox
self.gearbox.output = self.axles[1]
function Vehicle:getRootComponent()
return table.find(self.components, function(component)
return component.input == nil
end)
end
self.axles[1].input = self.gearbox
self.gearbox.input = self.clutch
self.clutch.input = self.engine
function Vehicle:handleWireInput(name, value)
if name == 'Base' then
self.base = value
self.basePhysObject = isValid(value) and value:getPhysicsObject() or nil
elseif name == 'Steer' then
self.steer = value
elseif name == 'Brake' then
self.brake = value
elseif name == 'Handbrake' then
self.handbrake = value
end
end
function Vehicle:update()
local base = wire.ports.Base
self.engine:integrateDownwards()
self.rootComponent:forwardStep()
for _, component in pairs(self.independentComponents) do
component:forwardStep(0, component:queryInertia())
end
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
--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
return {
Vehicle,
POWERTRAIN_COMPONENT
}

View File

@@ -1,97 +0,0 @@
--@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.rollingResistance = config.RollingResistance or -50
self.direction = config.Direction or 1
self.wireInputs = {
[prefix .. 'Wheel'] = 'entity'
}
self.wireOutputs = {
[prefix .. 'WheelRPM'] = 'number',
[prefix .. 'WheelTorque'] = '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(Vector(7.7))
--self.entity:setInertia(calculateWheelInertia(val))
end
self.inertia = self.entity:getInertia().y
end
end)
end
function Wheel:getRadius()
if not isValid(self.entity) then
return 0
end
return self.entity:getModelRadius() * UNITS_TO_METERS
end
function Wheel:updateWireOutputs()
wire.ports[self.prefix .. 'WheelRPM'] = self:getRPM()
wire.ports[self.prefix .. 'WheelTorque'] = self.torque
end
function Wheel:queryAngularVelocity()
return self.angularVelocity
end
function Wheel:forwardStep(torque, inertia)
if not isValid(self.entity) then
return 0
end
local initAngularVelocity = self.angularVelocity
self.entity:setInertia(Vector(inertia))
self.torque = math.deg(torque) * 1.33 * -self.direction
self.entity:applyTorque(self.torque * TICK_INTERVAL * self.entity:getRight())
self.angularVelocity = math.rad(self.entity:getAngleVelocity().y) * self.direction
local tq = (self.angularVelocity - initAngularVelocity) * inertia * TICK_INTERVAL
return self.rollingResistance - (tq - torque)
end
--local physObj = self.entity:getPhysicsObject()
--local load = physObj:getStress()
--local frictionSnapshot = physObj:getFrictionSnapshot()
--local forwardFrictionSpeed = 0
--local contactVelocity = physObj:getVelocityAtPoint(self.entity:getPos() + Vector(0, 0, self.entity:getModelRadius())) / 39.37
--
--if #table.getKeys(frictionSnapshot) > 0 then
-- local data = frictionSnapshot[1]
-- local forwardDir = data['Normal']:cross(self.entity:getRight() * self.direction):getNormalized()
--
-- forwardFrictionSpeed = contactVelocity:dot(forwardDir)
--end
--
--local errorTorque = (self.angularVelocity - forwardFrictionSpeed / self:getRadius()) * inertia / TICK_INTERVAL
--local rollingResistance = -40
--local deltaOmegaTorque = (self.angularVelocity - initialAngularVelocity) * self.inertia / TICK_INTERVAL
--
--return rollingResistance - deltaOmegaTorque

View File

@@ -0,0 +1,14 @@
local Friction = class('Friction')
function Friction:initialize(config)
config = config or {}
self.slipCoefficient = config.SlipCoefficient or 0.8
self.forceCoefficient = config.ForceCoefficient or 1.2
self.force = 0
self.slip = 0
self.speed = 0
end
return Friction

View File

@@ -0,0 +1,18 @@
local FrictionPreset = class('FrictionPreset')
function FrictionPreset:initialize(config)
config = config or {}
self.B = config.B or 10.86
self.C = config.C or 2.15
self.D = config.D or 0.933
self.E = config.E or 0.992
end
function FrictionPreset:evaluate(slip)
local t = math.abs(slip)
return self.D * math.sin(self.C * math.atan(self.B * t - self.E * (self.B * t - math.atan(self.B * t))))
end
return FrictionPreset

View File

@@ -0,0 +1,313 @@
--@include ./friction.txt
--@include ./friction_preset.txt
--@include /koptilnya/libs/constants.txt
local Friction = require('./friction.txt')
local FrictionPreset = require('./friction_preset.txt')
require('/koptilnya/libs/constants.txt')
local Wheel = class('Wheel')
function Wheel:initialize(config)
config = config or {}
self.mass = config.Mass or 20
self.radius = config.Radius or 0.27
self.rollingResistance = config.RollingResistance or 40
self.squat = config.Squat or 0.1
self.slipCircleShape = config.SlipCircleShape or 1.05
self.forwardFriction = Friction:new(config.ForwardFriction)
self.sideFriction = Friction:new(config.SideFriction)
self.frictionPreset = FrictionPreset:new(config.FrictionPreset)
self.satFrictionPreset = FrictionPreset:new({ B = 13, C = 2.4, D = 1, E = 0.48 })
self.motorTorque = 0
self.brakeTorque = 0
self.steerAngle = 0
self.hasHit = false
self.counterTorque = 0
self.inertia = 0
self.load = 0
self.angularVelocity = 0
self.mz = 0
self.forward = Vector(0)
self.right = Vector(0)
self.entity = NULL_ENTITY
self.physObj = nil
self.baseInertia = 0.5 * self.mass * math.pow(self.radius, 2)
self.inertia = self.baseInertia
end
function Wheel:setEntity(entity)
self.entity = entity
self.entity:setNoDraw(false)
self.physObj = isValid(entity) and entity:getPhysicsObject() or nil
if isValid(self.physObj) then
self.physObj:setMaterial('friction_00')
self.physObj:setMass(self.mass)
self.physObj:enableDrag(false)
self.physObj:setDragCoefficient(0)
self.physObj:setAngleDragCoefficient(0)
end
end
function Wheel:setInertia(inertia)
if isValid(self.physObj) then
self.physObj:setInertia(Vector(inertia))
self.inertia = inertia
end
end
function Wheel:isValid()
return isValid(self.entity) and isValid(self.physObj)
end
function Wheel:getRPM()
return self.angularVelocity * RAD_TO_RPM
end
function Wheel:getLongitudinalLoadCoefficient(load)
return 11000 * (1 - math.exp(-0.00014 * load));
end
function Wheel:getLateralLoadCoefficient(load)
return 18000 * (1 - math.exp(-0.0001 * load));
end
function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I, kFx, kSx)
local Winit = W
local VxAbs = math.abs(Vx)
local Sx = 0
if VxAbs >= 0.1 then
Sx = (Vx - W * R) / VxAbs
else
Sx = (Vx - W * R) * 0.6
end
Sx = Sx * kSx;
Sx = math.clamp(Sx, -1, 1)
W = W + Tm / I * TICK_INTERVAL
Tb = Tb * (W > 0 and -1 or 1)
local TbCap = math.abs(W) * I / TICK_INTERVAL
local Tbr = math.abs(Tb) - math.abs(TbCap)
Tbr = math.max(Tbr, 0)
Tb = math.clamp(Tb, -TbCap, TbCap)
W = W + Tb / I * TICK_INTERVAL
local maxTorque = self.frictionPreset:evaluate(math.abs(Sx)) * Lc * kFx * R
local errorTorque = (W - Vx / R) * I / TICK_INTERVAL
local surfaceTorque = math.clamp(errorTorque, -maxTorque, maxTorque)
W = W - surfaceTorque / I * TICK_INTERVAL
local Fx = surfaceTorque / R
Tbr = Tbr * (W > 0 and -1 or 1)
local TbCap2 = math.abs(W) * I / TICK_INTERVAL
local Tb2 = math.clamp(Tbr, -TbCap2, TbCap2)
W = W + Tb2 / I * TICK_INTERVAL
local deltaOmegaTorque = (W - Winit) * I / TICK_INTERVAL
local Tcnt = -surfaceTorque + Tb + Tb2 - deltaOmegaTorque
if Lc < 0.001 then
Sx = 0
end
return W, Sx, Fx, Tcnt
end
function Wheel:stepLateral(Vx, Vy, Lc, kFy, kSy)
local VxAbs = math.abs(Vx)
local Sy = 0
if VxAbs > 0.3 then
Sy = math.deg(math.atan(Vy / VxAbs))
Sy = Sy / 50
else
Sy = Vy * (0.003 / TICK_INTERVAL)
end
Sy = Sy * kSy * 0.95
Sy = math.clamp(Sy, -1, 1)
local slipSign = Sy < 0 and -1 or 1
local Fy = -slipSign * self.frictionPreset:evaluate(math.abs(Sy)) * Lc * kFy
if Lc < 0.0001 then
Sy = 0
end
return Sy, Fy
end
function Wheel:slipCircle(Sx, Sy, Fx, Fy, slipCircleShape)
local SxAbs = math.abs(Sx)
if SxAbs > 0.01 then
local SxClamped = math.clamp(Sx, -1, 1)
local SyClamped = math.clamp(Sy, -1, 1)
local combinedSlip = Vector2(
SxClamped * slipCircleShape,
SyClamped
)
local slipDir = combinedSlip:getNormalized()
local F = math.sqrt(Fx * Fx + Fy * Fy)
local absSlipDirY = math.abs(slipDir.y)
Fy = F * absSlipDirY * (Fy < 0 and -1 or 1)
end
return Sx, Sy, Fx, Fy
end
function Wheel:selfAligningTorque(Vx, Vy, Lc)
local VxAbs = math.abs(Vx)
local Sy = 0
if VxAbs > 0.3 then
Sy = math.deg(math.atan(Vy / VxAbs))
else
Sy = Vy * (0.003 / TICK_INTERVAL)
end
Sy = math.clamp(Sy, -90, 90)
--local phi = (1 - self.E) * Sy + (self.E / self.B) * math.atan(self.B * Sy)
--local Mz = -slipSign * self.satFrictionPreset:evaluate(math.abs(phi), self.B, C, self.D, 0) * Lc
return self.satFrictionPreset:evaluate(math.abs(Sy)) * -math.sign(Sy)
end
function Wheel:selfAligningTorque2(Vx, Vy, Fz)
if Fz == 0 then
return 0
end
local VxAbs = math.abs(Vx)
local Sy = 0
if VxAbs > 0.3 then
Sy = math.deg(math.atan(Vy / VxAbs))
else
Sy = Vy * (0.003 / TICK_INTERVAL)
end
Sy = math.clamp(Sy, -1, 1)
local a = {
-2.72,
-2.28,
-1.86,
-2.73,
0.110,
-0.070,
0.643,
-4.04,
0.015,
-0.066,
0.945,
0.030,
0.070
}
local FzSqr = math.pow(Fz, 2)
local C = 2.4
local D = a[1] * FzSqr + a[2] * Fz
local BCD = (a[3] * FzSqr + a[4] * Fz) / math.pow(math.exp(1), a[5] * Fz)
local B = BCD / (C * D)
local E = a[6] * FzSqr + a[7] * Fz + a[8]
local phi = (1 - E) * Sy + (E / B) * math.atan(B * Sy)
self.phi = phi
return D * math.sin(C * math.atan(B * phi))
end
function Wheel:selfAligningTorque3(Mx, My, Sx, Sy)
local M = Mx * math.cos(Sy) + My * math.cos(Sx)
return self.radius * M
end
function Wheel:update()
if not isValid(self.entity) or not isValid(self.physObj) then
return
end
local externalStress = self.physObj:getStress()
local velocity = self.physObj:getVelocity() * UNITS_TO_METERS
self.hasHit = externalStress ~= 0
self.load = externalStress * KG_TO_KN
self.longitudinalLoadCoefficient = self:getLongitudinalLoadCoefficient(self.load * 1000)
self.lateralLoadCoefficient = self:getLateralLoadCoefficient(self.load * 1000)
--self.steerAngle = self.steerAngle + self.mz / self.inertia
self.forward = self.entity:getForward():rotateAroundAxis(self.entity:getUp(), -self.steerAngle)
self.right = self.entity:getRight():rotateAroundAxis(self.entity:getUp(), -self.steerAngle)
local forwardSpeed = 0
local sideSpeed = 0
if self.hasHit then
forwardSpeed = velocity:dot(self.forward)
sideSpeed = velocity:dot(self.right)
end
local W, Sx, Fx, CounterTq = self:stepLongitudinal(
self.motorTorque,
self.brakeTorque + self.rollingResistance,
forwardSpeed,
self.angularVelocity,
self.longitudinalLoadCoefficient,
self.radius,
self.inertia,
0.95, -- Force coeff
0.9 -- Slip coeff
)
local Sy, Fy = self:stepLateral(
forwardSpeed,
sideSpeed,
self.lateralLoadCoefficient,
0.95, -- Force coeff
0.9 -- Slip coeff
)
Sx, Sy, Fx, Fy = self:slipCircle(
Sx,
Sy,
Fx,
Fy,
1.05 -- Shape of the slip circle / ellipse.
)
--local Mx = Fy * (math.cos(Sx) - 1) + Fx * math.sin(Sx)
--local My = Fx * (math.cos(Sy) - 1) + Fy * math.sin(Sy)
--local Mz = self:selfAligningTorque3(Mx, My, Sx, Sy)
local Mz = self:selfAligningTorque2(forwardSpeed, sideSpeed, self.load)
self.mz = Mz
self.angularVelocity = W
self.counterTorque = CounterTq
self.forwardFriction.slip = Sx
self.forwardFriction.force = Fx
self.forwardFriction.speed = forwardSpeed
self.sideFriction.slip = Sy
self.sideFriction.force = Fy
self.sideFriction.speed = sideSpeed
end
return Wheel

View File

@@ -1,4 +1,4 @@
WireComponent = class('WireComponent')
local WireComponent = class('WireComponent')
function WireComponent:initialize()
self.wireInputs = {}
@@ -7,3 +7,5 @@ end
function WireComponent:updateWireOutputs()
end
return WireComponent