types
This commit is contained in:
parent
84f591db59
commit
0850d7798b
@ -8,11 +8,10 @@ local Differential = require('/koptilnya/engine_remastered/powertrain/differenti
|
|||||||
|
|
||||||
-- Michelin Pilot Sport 4S (High-Performance Road Tire)
|
-- Michelin Pilot Sport 4S (High-Performance Road Tire)
|
||||||
local WheelConfig = {
|
local WheelConfig = {
|
||||||
DEBUG = true,
|
DEBUG = false,
|
||||||
BrakePower = 1200,
|
BrakePower = 1200,
|
||||||
CustomWheel = {
|
CustomWheel = {
|
||||||
Mass = 30,
|
Mass = 30,
|
||||||
|
|
||||||
LateralFrictionPreset = {
|
LateralFrictionPreset = {
|
||||||
B = 12.0,
|
B = 12.0,
|
||||||
C = 1.3,
|
C = 1.3,
|
||||||
@ -49,16 +48,24 @@ local FrontWheelsConfig = table.merge(table.copy(WheelConfig), {
|
|||||||
})
|
})
|
||||||
|
|
||||||
local RearWheelsConfig = table.merge(table.copy(WheelConfig), {
|
local RearWheelsConfig = table.merge(table.copy(WheelConfig), {
|
||||||
BrakePower = 600,
|
BrakePower = 0,
|
||||||
HandbrakePower = 2200,
|
HandbrakePower = 2200,
|
||||||
CustomWheel = {
|
CustomWheel = {
|
||||||
CamberAngle = -2,
|
CamberAngle = -2,
|
||||||
|
LongitudinalFrictionPreset = nil,
|
||||||
|
LateralFrictionPreset = nil,
|
||||||
-- LongitudinalFrictionPreset = {
|
-- LongitudinalFrictionPreset = {
|
||||||
-- B = 0.1,
|
-- B = 0.1,
|
||||||
-- C = 1,
|
-- C = 1,
|
||||||
-- D = 0.9,
|
-- D = 0.9,
|
||||||
-- E = 0.9,
|
-- E = 0.9,
|
||||||
-- },
|
-- },
|
||||||
|
-- LateralFrictionPreset = {
|
||||||
|
-- B = 0.1,
|
||||||
|
-- C = 1,
|
||||||
|
-- D = 0.9,
|
||||||
|
-- E = 0.9,
|
||||||
|
-- },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -201,7 +208,7 @@ Vehicle:new({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = 'AxleFront',
|
Name = 'AxleFront',
|
||||||
-- Input = 'Axle',
|
Input = 'Axle',
|
||||||
Type = POWERTRAIN_COMPONENT.Differential,
|
Type = POWERTRAIN_COMPONENT.Differential,
|
||||||
Config = {
|
Config = {
|
||||||
Type = Differential.TYPES.Open,
|
Type = Differential.TYPES.Open,
|
||||||
@ -230,7 +237,7 @@ Vehicle:new({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = 'AxleBack',
|
Name = 'AxleBack',
|
||||||
Input = 'Gearbox',
|
Input = 'Axle',
|
||||||
Type = POWERTRAIN_COMPONENT.Differential,
|
Type = POWERTRAIN_COMPONENT.Differential,
|
||||||
Config = {
|
Config = {
|
||||||
Type = Differential.TYPES.Open,
|
Type = Differential.TYPES.Open,
|
||||||
@ -239,17 +246,17 @@ Vehicle:new({
|
|||||||
BiasAB = 0.5,
|
BiasAB = 0.5,
|
||||||
CoastRamp = 1,
|
CoastRamp = 1,
|
||||||
PowerRamp = 1,
|
PowerRamp = 1,
|
||||||
Stiffness = 0.9,
|
Stiffness = 1,
|
||||||
SlipTorque = 1000,
|
SlipTorque = 1000,
|
||||||
ToeAngle = 0.5,
|
ToeAngle = 0.5,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name = 'Axle',
|
Name = 'Axle',
|
||||||
-- Input = 'Gearbox',
|
Input = 'Gearbox',
|
||||||
Type = POWERTRAIN_COMPONENT.Differential,
|
Type = POWERTRAIN_COMPONENT.Differential,
|
||||||
Config = {
|
Config = {
|
||||||
Type = Differential.TYPES.Open,
|
Type = Differential.TYPES.Locking,
|
||||||
FinalDrive = 1,
|
FinalDrive = 1,
|
||||||
Inertia = 0.01,
|
Inertia = 0.01,
|
||||||
BiasAB = 0.5,
|
BiasAB = 0.5,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
--@include ./powertrain_component.txt
|
--@include ./powertrain_component.txt
|
||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
|
|
||||||
|
---@type PowertrainComponent
|
||||||
local PowertrainComponent = require('./powertrain_component.txt')
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
@ -88,4 +89,7 @@ function Clutch:forwardStep(torque, inertia)
|
|||||||
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
|
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type fun(vehicle: KPTLVehicle, name?: string, config?: ClutchConfig): Clutch
|
||||||
|
Clutch.new = Clutch.new
|
||||||
|
|
||||||
return Clutch
|
return Clutch
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
--@include ./powertrain_component.txt
|
--@include ./powertrain_component.txt
|
||||||
|
|
||||||
|
---@type PowertrainComponent
|
||||||
local PowertrainComponent = require('./powertrain_component.txt')
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
@ -47,8 +48,7 @@ function Differential:initialize(vehicle, name, config)
|
|||||||
|
|
||||||
self.wireOutputs = {
|
self.wireOutputs = {
|
||||||
[string.format('%s_Torque', self.name)] = 'number',
|
[string.format('%s_Torque', self.name)] = 'number',
|
||||||
[string.format('%s_W', self.name)] = 'number',
|
[string.format('%s_RPMDiff', self.name)] = 'number',
|
||||||
[string.format('%s_MzDiff', self.name)] = 'number',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finalDrive = config.FinalDrive or 4
|
self.finalDrive = config.FinalDrive or 4
|
||||||
@ -66,6 +66,7 @@ function Differential:initialize(vehicle, name, config)
|
|||||||
self.steerAngle = 0
|
self.steerAngle = 0
|
||||||
|
|
||||||
self.mzDiff = 0
|
self.mzDiff = 0
|
||||||
|
self.rpmDiff = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param component PowertrainComponent
|
---@param component PowertrainComponent
|
||||||
@ -90,8 +91,7 @@ function Differential:updateWireOutputs()
|
|||||||
PowertrainComponent.updateWireOutputs(self)
|
PowertrainComponent.updateWireOutputs(self)
|
||||||
|
|
||||||
wire.ports[string.format('%s_Torque', self.name)] = self.torque
|
wire.ports[string.format('%s_Torque', self.name)] = self.torque
|
||||||
wire.ports[string.format('%s_W', self.name)] = self.angularVelocity
|
wire.ports[string.format('%s_RPMDiff', self.name)] = self.rpmDiff
|
||||||
wire.ports[string.format('%s_MzDiff', self.name)] = self.mzDiff
|
|
||||||
|
|
||||||
if self.outputA ~= nil then
|
if self.outputA ~= nil then
|
||||||
self.outputA:updateWireOutputs()
|
self.outputA:updateWireOutputs()
|
||||||
@ -157,33 +157,43 @@ function Differential:forwardStep(torque, inertia)
|
|||||||
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 * math.pow(self.finalDrive, 2) + aI)
|
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 * math.pow(self.finalDrive, 2) + aI)
|
||||||
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 * math.pow(self.finalDrive, 2) + bI)
|
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 * math.pow(self.finalDrive, 2) + bI)
|
||||||
|
|
||||||
|
if self.CONFIG.Type == DIFFERENTIAL_TYPES.Locking then
|
||||||
|
local avg = (self.outputA.angularVelocity + self.outputB.angularVelocity) / 2
|
||||||
|
|
||||||
|
self.outputA:queryAngularVelocity(avg)
|
||||||
|
self.outputB:queryAngularVelocity(avg)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.rpmDiff = self.outputA:getRPM() - self.outputB:getRPM()
|
||||||
|
|
||||||
-- // REFACTOR
|
-- // REFACTOR
|
||||||
|
if self.outputA.customWheel ~= nil and self.outputB.customWheel ~= nil then
|
||||||
if self.steerLock ~= 0 then
|
if self.steerLock ~= 0 then
|
||||||
local outputA = self.outputA --[[@as Wheel]]
|
local outputA = self.outputA --[[@as Wheel]]
|
||||||
local outputB = self.outputB --[[@as Wheel]]
|
local outputB = self.outputB --[[@as Wheel]]
|
||||||
|
|
||||||
local steerInertia = (aI + bI) / 2
|
local steerInertia = (aI + bI)
|
||||||
local driverInput = 15
|
local driverInput = 3
|
||||||
|
|
||||||
local localVelocityLength = chip():getVelocity():getLength()
|
local localVelocityLength = chip():getVelocity():getLength()
|
||||||
local MPH = localVelocityLength * (15 / 352)
|
local MPH = localVelocityLength * (15 / 352)
|
||||||
local KPH = MPH * 1.609
|
local KPH = MPH * 1.609
|
||||||
|
|
||||||
local assist = math.clamp(10.0 / math.sqrt(KPH / 3), 2.0, 10.0)
|
local assist = math.clamp(10.0 / math.sqrt(KPH / 3), 2.0, 10.0) / 2
|
||||||
|
|
||||||
local inputForce = driverInput * assist
|
local inputForce = driverInput * assist
|
||||||
|
|
||||||
local inputTorque = self.vehicle.steer * inputForce
|
local inputTorque = self.vehicle.steer * inputForce
|
||||||
|
|
||||||
local mzDiff = outputA.customWheel.mz - outputB.customWheel.mz
|
local mzDiff = (outputB.customWheel.mz - outputA.customWheel.mz) * TICK_INTERVAL / 2
|
||||||
|
|
||||||
self.mzDiff = mzDiff
|
self.mzDiff = math.lerp(0.1, self.mzDiff, mzDiff)
|
||||||
|
|
||||||
local steerTorque = mzDiff * -1 + inputTorque
|
local steerTorque = self.mzDiff * 1 + inputTorque
|
||||||
|
|
||||||
local steerAngularAccel = steerTorque / steerInertia
|
local steerAngularAccel = steerTorque / steerInertia
|
||||||
|
|
||||||
self.steerAngle = math.clamp(self.steerAngle + steerAngularAccel * TICK_INTERVAL, -self.steerLock, self.steerLock)
|
self.steerAngle = math.clamp(self.steerAngle + math.deg(steerAngularAccel) * TICK_INTERVAL, -self.steerLock, self.steerLock)
|
||||||
|
|
||||||
local wheelbase = 2.05
|
local wheelbase = 2.05
|
||||||
local trackWidth = 1.124
|
local trackWidth = 1.124
|
||||||
@ -201,6 +211,7 @@ function Differential:forwardStep(torque, inertia)
|
|||||||
outputA.customWheel.steerAngle = self.toeAngle * outputA.customWheel.direction
|
outputA.customWheel.steerAngle = self.toeAngle * outputA.customWheel.direction
|
||||||
outputB.customWheel.steerAngle = self.toeAngle * outputB.customWheel.direction
|
outputB.customWheel.steerAngle = self.toeAngle * outputB.customWheel.direction
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return tqA + tqB
|
return tqA + tqB
|
||||||
end
|
end
|
||||||
@ -212,21 +223,63 @@ end
|
|||||||
|
|
||||||
---@return number, number
|
---@return number, number
|
||||||
local function _lockingDiffTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stiffness, powerRamp, coastRamp, slipTorque)
|
local function _lockingDiffTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stiffness, powerRamp, coastRamp, slipTorque)
|
||||||
aTq = tq * 0.5
|
return tq * 0.5, tq * 0.5
|
||||||
bTq = tq * 0.5
|
-- -- Разность скоростей между выходами
|
||||||
|
-- local deltaW = aW - bW
|
||||||
|
|
||||||
local syncTorque = (aW - bW) * stiffness * (aI + bI) * 0.5 / TICK_INTERVAL
|
-- -- Расчет "проскальзывающего" момента между сторонами
|
||||||
|
-- local slip = math.abs(deltaW)
|
||||||
|
|
||||||
aTq = aTq - syncTorque
|
-- -- Применяем преднагрузку и жесткость, чтобы определить базовый момент блокировки
|
||||||
bTq = bTq + syncTorque
|
-- local lockingTorque = preload + stiffness * deltaW
|
||||||
|
|
||||||
|
-- -- Определение направления нагрузки: ускорение или торможение
|
||||||
|
-- local ramp = tq >= 0 and powerRamp or coastRamp
|
||||||
|
|
||||||
|
-- -- Применяем ramp к разности скоростей, усиливая/ослабляя блокировку
|
||||||
|
-- local rampTorque = slip * ramp
|
||||||
|
|
||||||
|
-- -- Итоговый момент передачи между сторонами
|
||||||
|
-- local transferTorque = lockingTorque + rampTorque
|
||||||
|
|
||||||
|
-- -- Ограничиваем передачу момента порогом проскальзывания
|
||||||
|
-- if transferTorque > slipTorque then
|
||||||
|
-- transferTorque = slipTorque
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- -- Учитываем биас (смещение баланса)
|
||||||
|
-- local maxBias = biasAB
|
||||||
|
-- local biasLimit = tq * (maxBias - 1) / (maxBias + 1)
|
||||||
|
|
||||||
|
-- -- Финальный момент, передаваемый между сторонами, с учетом направления
|
||||||
|
-- local direction = deltaW > 0 and -1 or 1
|
||||||
|
-- local finalTorque = direction * transferTorque
|
||||||
|
|
||||||
|
-- -- Применяем финальный момент к каждой стороне, ограничивая его bias'ом
|
||||||
|
-- local aTorque = tq * 0.5 + math.min(finalTorque, biasLimit)
|
||||||
|
-- local bTorque = tq * 0.5 - math.min(finalTorque, biasLimit)
|
||||||
|
|
||||||
|
-- -- Возвращаем обновленные значения угловых скоростей
|
||||||
|
-- return aTorque, bTorque
|
||||||
|
|
||||||
|
-- local aTq = tq * 0.5
|
||||||
|
-- local bTq = tq * 0.5
|
||||||
|
|
||||||
|
-- local syncTorque = (aW - bW) * 1e6 * (aI + bI) / TICK_INTERVAL
|
||||||
|
|
||||||
|
-- aTq = aTq - syncTorque
|
||||||
|
-- bTq = bTq + syncTorque
|
||||||
|
|
||||||
|
-- -- print(aTq, bTq)
|
||||||
|
|
||||||
|
-- return aTq, bTq
|
||||||
|
|
||||||
return aTq, bTq
|
|
||||||
-- local sumI = aI + bI
|
-- local sumI = aI + bI
|
||||||
-- local w = aI / sumI * aW + bI / sumI * bW
|
-- local w = aI / sumI * aW + bI / sumI * bW
|
||||||
-- local aTqCorr = (w - aW) * aI -- / dt
|
-- local aTqCorr = (w - aW) * aI / TICK_INTERVAL
|
||||||
-- aTqCorr = aTqCorr * stiffness
|
-- aTqCorr = aTqCorr * stiffness
|
||||||
|
|
||||||
-- local bTqCorr = (w - bW) * bI -- / dt
|
-- local bTqCorr = (w - bW) * bI / TICK_INTERVAL
|
||||||
-- bTqCorr = bTqCorr * stiffness
|
-- bTqCorr = bTqCorr * stiffness
|
||||||
|
|
||||||
-- local biasA = math.clamp(0.5 + (bW - aW) * 0.1 * stiffness, 0, 1)
|
-- local biasA = math.clamp(0.5 + (bW - aW) * 0.1 * stiffness, 0, 1)
|
||||||
@ -293,4 +346,7 @@ end
|
|||||||
|
|
||||||
Differential.TYPES = DIFFERENTIAL_TYPES
|
Differential.TYPES = DIFFERENTIAL_TYPES
|
||||||
|
|
||||||
|
---@type fun(vehicle: KPTLVehicle, name?: string, config?: DifferentialConfig): Differential
|
||||||
|
Differential.new = Differential.new
|
||||||
|
|
||||||
return Differential
|
return Differential
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
--@include ./powertrain_component.txt
|
--@include ./powertrain_component.txt
|
||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
|
|
||||||
|
---@type PowertrainComponent
|
||||||
local PowertrainComponent = require('./powertrain_component.txt')
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
@ -65,10 +66,10 @@ function Engine:initialize(vehicle, name, config)
|
|||||||
self.returnedTorque = 0
|
self.returnedTorque = 0
|
||||||
|
|
||||||
if CLIENT then
|
if CLIENT then
|
||||||
-- --@include /koptilnya/engine_sound_2.txt
|
--@include /koptilnya/engine_sound_2.txt
|
||||||
-- local Sound = require('/koptilnya/engine_sound_2.txt')
|
local Sound = require('/koptilnya/engine_sound_2.txt')
|
||||||
|
|
||||||
-- Sound(self.maxRPM, chip())
|
Sound(self.maxRPM, chip())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -152,16 +153,19 @@ function Engine:forwardStep(torque, inertia)
|
|||||||
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
|
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
|
||||||
self.angularVelocity = math.max(self.angularVelocity, 0)
|
self.angularVelocity = math.max(self.angularVelocity, 0)
|
||||||
|
|
||||||
-- net.start('ENGINE_RPM')
|
net.start('ENGINE_RPM')
|
||||||
-- net.writeFloat(self:getRPM() / self.maxRPM)
|
net.writeFloat(self:getRPM() / self.maxRPM)
|
||||||
-- net.send(self.vehicle.playersConnectedToHUD, true)
|
net.send(self.vehicle.playersConnectedToHUD, true)
|
||||||
|
|
||||||
-- net.start('ENGINE_FULLRPM')
|
net.start('ENGINE_FULLRPM')
|
||||||
-- net.writeUInt(self:getRPM(), 16)
|
net.writeUInt(self:getRPM(), 16)
|
||||||
-- net.writeFloat(self.masterThrottle)
|
net.writeFloat(self.masterThrottle)
|
||||||
-- net.send(nil, true)
|
net.send(nil, true)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type fun(vehicle: KPTLVehicle, name?: string, config?: EngineConfig): Engine
|
||||||
|
Engine.new = Engine.new
|
||||||
|
|
||||||
return Engine
|
return Engine
|
||||||
|
|||||||
@ -129,4 +129,7 @@ function PowertrainComponent:serializeDebugData() end
|
|||||||
---@return nil
|
---@return nil
|
||||||
function PowertrainComponent:deserializeDebugData(result) end
|
function PowertrainComponent:deserializeDebugData(result) end
|
||||||
|
|
||||||
|
---@type fun(vehicle: KPTLVehicle, name?: string, config?: PowertrainComponentConfig): PowertrainComponent
|
||||||
|
PowertrainComponent.new = PowertrainComponent.new
|
||||||
|
|
||||||
return PowertrainComponent
|
return PowertrainComponent
|
||||||
|
|||||||
@ -3,7 +3,10 @@
|
|||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
--@include /koptilnya/libs/entity.txt
|
--@include /koptilnya/libs/entity.txt
|
||||||
|
|
||||||
|
---@type PowertrainComponent
|
||||||
local PowertrainComponent = require('./powertrain_component.txt')
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
|
||||||
|
---@type CustomWheel
|
||||||
local CustomWheel = require('../wheel/wheel.txt')
|
local CustomWheel = require('../wheel/wheel.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
@ -18,13 +21,11 @@ require('/koptilnya/libs/entity.txt')
|
|||||||
---@field CustomWheel? CustomWheelConfig
|
---@field CustomWheel? CustomWheelConfig
|
||||||
|
|
||||||
---@class Wheel: PowertrainComponent
|
---@class Wheel: PowertrainComponent
|
||||||
---@field entity Entity
|
|
||||||
---@field brakePower number
|
---@field brakePower number
|
||||||
---@field handbrakePower number
|
---@field handbrakePower number
|
||||||
---@field direction integer
|
---@field direction integer
|
||||||
---@field private rot number
|
---@field private rot number
|
||||||
---@field model string
|
---@field model string
|
||||||
---@field holo Hologram | Entity
|
|
||||||
---@field customWheel CustomWheel
|
---@field customWheel CustomWheel
|
||||||
local Wheel = class('Wheel', PowertrainComponent)
|
local Wheel = class('Wheel', PowertrainComponent)
|
||||||
|
|
||||||
@ -55,11 +56,11 @@ function Wheel:initialize(vehicle, name, config)
|
|||||||
render.setMaterial(mat)
|
render.setMaterial(mat)
|
||||||
render.setColor(Color(255, 0, 0, 200))
|
render.setColor(Color(255, 0, 0, 200))
|
||||||
lerpForwardFrictionForce = math.lerp(0.1, lerpForwardFrictionForce, self.DEBUG_DATA.forwardFrictionForce)
|
lerpForwardFrictionForce = math.lerp(0.1, lerpForwardFrictionForce, self.DEBUG_DATA.forwardFrictionForce)
|
||||||
render.draw3DBeam(pos, pos + ((lerpForwardFrictionForce / 500 + 16) * self.DEBUG_DATA.forward), 1, 0, 0)
|
render.draw3DBeam(pos, pos + ((lerpForwardFrictionForce / 500 + 0) * self.DEBUG_DATA.forward), 1, 0, 0)
|
||||||
|
|
||||||
render.setColor(Color(0, 255, 0, 255))
|
render.setColor(Color(0, 255, 0, 255))
|
||||||
lerpSideFrictionForce = math.lerp(0.1, lerpSideFrictionForce, self.DEBUG_DATA.sideFrictionForce)
|
lerpSideFrictionForce = math.lerp(0.1, lerpSideFrictionForce, self.DEBUG_DATA.sideFrictionForce)
|
||||||
render.draw3DBeam(pos, pos + ((lerpSideFrictionForce / 500 + 16) * self.DEBUG_DATA.right), 1, 0, 0)
|
render.draw3DBeam(pos, pos + ((lerpSideFrictionForce / 500 + 0) * self.DEBUG_DATA.right), 1, 0, 0)
|
||||||
|
|
||||||
render.setColor(Color(0, 0, 255, 200))
|
render.setColor(Color(0, 0, 255, 200))
|
||||||
lerpLoad = math.lerp(0.1, lerpLoad, self.DEBUG_DATA.load)
|
lerpLoad = math.lerp(0.1, lerpLoad, self.DEBUG_DATA.load)
|
||||||
@ -72,7 +73,6 @@ function Wheel:initialize(vehicle, name, config)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self.entity = config.Entity or NULL_ENTITY
|
|
||||||
self.brakePower = config.BrakePower or 0
|
self.brakePower = config.BrakePower or 0
|
||||||
self.handbrakePower = config.HandbrakePower or 0
|
self.handbrakePower = config.HandbrakePower or 0
|
||||||
self.model = config.Model or ''
|
self.model = config.Model or ''
|
||||||
@ -84,33 +84,52 @@ function Wheel:initialize(vehicle, name, config)
|
|||||||
}
|
}
|
||||||
self.wireOutputs = {
|
self.wireOutputs = {
|
||||||
[string.format('%s_RPM', self.name)] = 'number',
|
[string.format('%s_RPM', self.name)] = 'number',
|
||||||
[string.format('%s_Fx', self.name)] = 'number',
|
|
||||||
[string.format('%s_CTq', self.name)] = 'number',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rot = 0
|
self.rot = 0
|
||||||
|
|
||||||
self.holo = self:createHolo(self.entity)
|
---@type Entity?
|
||||||
|
self.entity = nil
|
||||||
|
|
||||||
self.customWheel = CustomWheel:new(table.merge(table.copy(config.CustomWheel), { Direction = self.direction, Name = self.name }))
|
---@type Hologram?
|
||||||
|
self.holo = nil
|
||||||
|
|
||||||
|
self.customWheel = CustomWheel:new(
|
||||||
|
table.merge(
|
||||||
|
table.copy(config.CustomWheel),
|
||||||
|
{ Direction = self.direction, Name = self.name, ParentPhysObj = vehicle.basePhysObject }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
hook.add('Input', 'vehicle_wheel_update' .. self.name, function(name, value)
|
hook.add('Input', 'vehicle_wheel_update' .. self.name, function(name, value)
|
||||||
if name == self.name then
|
if name == self.name then
|
||||||
self.entity = value
|
self:setEntity(value)
|
||||||
self.customWheel:setEntity(value)
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@override
|
||||||
|
---@return nil
|
||||||
|
function Wheel:onWirePortsReady()
|
||||||
|
self:setEntity(self.CONFIG.Entity or wire.ports[self.name])
|
||||||
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@param entity Entity
|
||||||
|
---@return nil
|
||||||
|
function Wheel:setEntity(entity)
|
||||||
|
self.entity = entity
|
||||||
|
self.customWheel:setEntity(entity)
|
||||||
|
self.customWheel.radius = self:getEntityRadius()
|
||||||
|
|
||||||
if isValid(self.holo) then
|
if isValid(self.holo) then
|
||||||
self.holo:remove()
|
self.holo:remove()
|
||||||
end
|
end
|
||||||
|
|
||||||
self.holo = self:createHolo(self.entity)
|
self.holo = self:createHolo(self.entity)
|
||||||
self.customWheel.radius = self:getEntityRadius()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
self.steerVelocity = 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return number
|
||||||
function Wheel:getEntityRadius()
|
function Wheel:getEntityRadius()
|
||||||
if not isValid(self.entity) then
|
if not isValid(self.entity) then
|
||||||
return 0
|
return 0
|
||||||
@ -119,16 +138,16 @@ function Wheel:getEntityRadius()
|
|||||||
return self.entity:getModelRadius() * UNITS_TO_METERS
|
return self.entity:getModelRadius() * UNITS_TO_METERS
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return Hologram | Entity
|
---@return Hologram?
|
||||||
function Wheel:createHolo(entity)
|
function Wheel:createHolo(entity)
|
||||||
if not isValid(entity) then
|
if not isValid(entity) then
|
||||||
return NULL_ENTITY
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local holo = hologram.create(entity:getPos(), entity:getAngles(), self.model or '')
|
local holo = hologram.create(entity:getPos(), entity:getAngles(), self.model or '')
|
||||||
|
|
||||||
if holo == nil or isValid(holo) then
|
if holo == nil or not isValid(holo) then
|
||||||
return NULL_ENTITY
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
holo:setParent(entity)
|
holo:setParent(entity)
|
||||||
@ -143,8 +162,6 @@ function Wheel:updateWireOutputs()
|
|||||||
PowertrainComponent.updateWireOutputs(self)
|
PowertrainComponent.updateWireOutputs(self)
|
||||||
|
|
||||||
wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM()
|
wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM()
|
||||||
wire.ports[string.format('%s_Fx', self.name)] = self.customWheel.forwardFriction.force
|
|
||||||
wire.ports[string.format('%s_CTq', self.name)] = self.customWheel.counterTorque
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return number
|
---@return number
|
||||||
@ -153,8 +170,10 @@ function Wheel:queryInertia()
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@return number
|
---@return number
|
||||||
function Wheel:queryAngularVelocity()
|
function Wheel:queryAngularVelocity(angularVelocity)
|
||||||
return self.angularVelocity
|
self.customWheel.angularVelocity = angularVelocity
|
||||||
|
|
||||||
|
return self.customWheel.angularVelocity
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param torque number
|
---@param torque number
|
||||||
@ -228,4 +247,7 @@ function Wheel:deserializeDebugData(result)
|
|||||||
result.sideFrictionForce = net.readFloat()
|
result.sideFrictionForce = net.readFloat()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type fun(vehicle: KPTLVehicle, name?: string, config?: WheelConfig): Wheel
|
||||||
|
Wheel.new = Wheel.new
|
||||||
|
|
||||||
return Wheel
|
return Wheel
|
||||||
|
|||||||
@ -13,7 +13,7 @@ require('/koptilnya/libs/table.txt')
|
|||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
|
|
||||||
---@class KPTLVehicle
|
---@class KPTLVehicle
|
||||||
---@field config { [string]: any }
|
---@field config { Name: string, Input?: string, Config?: table }[]
|
||||||
---@field components PowertrainComponent[]
|
---@field components PowertrainComponent[]
|
||||||
---@field headComponents PowertrainComponent[]
|
---@field headComponents PowertrainComponent[]
|
||||||
---@field steer number
|
---@field steer number
|
||||||
@ -23,6 +23,7 @@ require('/koptilnya/libs/constants.txt')
|
|||||||
---@field base? Entity
|
---@field base? Entity
|
||||||
---@field basePhysObject? PhysObj
|
---@field basePhysObject? PhysObj
|
||||||
---@field initialized boolean
|
---@field initialized boolean
|
||||||
|
---@field initializedPlayers Player[]
|
||||||
local Vehicle = class('Vehicle')
|
local Vehicle = class('Vehicle')
|
||||||
|
|
||||||
---@param config { [string]: any }
|
---@param config { [string]: any }
|
||||||
@ -40,6 +41,7 @@ function Vehicle:initialize(config)
|
|||||||
self.steer = 0
|
self.steer = 0
|
||||||
self.brake = 0
|
self.brake = 0
|
||||||
self.handbrake = 0
|
self.handbrake = 0
|
||||||
|
self.initializedPlayers = {}
|
||||||
self.playersConnectedToHUD = {}
|
self.playersConnectedToHUD = {}
|
||||||
|
|
||||||
if SERVER then
|
if SERVER then
|
||||||
@ -67,6 +69,15 @@ function Vehicle:initialize(config)
|
|||||||
hook.add('PlayerDisconnected', 'vehicle_huddisconnected', function(ply)
|
hook.add('PlayerDisconnected', 'vehicle_huddisconnected', function(ply)
|
||||||
table.removeByValue(self.playersConnectedToHUD, ply)
|
table.removeByValue(self.playersConnectedToHUD, ply)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
hook.add('ClientInitialized', 'vehicle_clientinitialized', function(ply)
|
||||||
|
table.insert(self.initializedPlayers, ply)
|
||||||
|
|
||||||
|
if self.initialized then
|
||||||
|
net.start('VEHICLE_READY')
|
||||||
|
net.send(ply, true)
|
||||||
|
end
|
||||||
|
end)
|
||||||
else
|
else
|
||||||
--@include ./hud.txt
|
--@include ./hud.txt
|
||||||
require('./hud.txt')
|
require('./hud.txt')
|
||||||
@ -88,11 +99,9 @@ function Vehicle:start()
|
|||||||
|
|
||||||
self.initialized = true
|
self.initialized = true
|
||||||
|
|
||||||
print('VEHICLE READY')
|
|
||||||
|
|
||||||
if SERVER then
|
if SERVER then
|
||||||
net.start('VEHICLE_READY')
|
net.start('VEHICLE_READY')
|
||||||
net.send(find.allPlayers(), true)
|
net.send(self.initializedPlayers, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -144,8 +153,10 @@ function Vehicle:linkComponents()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if SERVER then
|
if SERVER then
|
||||||
|
print(Color(0, 255, 0), 'Powertrain tree:')
|
||||||
for _, component in pairs(self.headComponents) do
|
for _, component in pairs(self.headComponents) do
|
||||||
self:printComponentTree(component)
|
PrintTree(component)
|
||||||
|
print(' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -166,6 +177,10 @@ function Vehicle:createIO()
|
|||||||
end
|
end
|
||||||
|
|
||||||
wire.adjustPorts(inputs, outputs)
|
wire.adjustPorts(inputs, outputs)
|
||||||
|
|
||||||
|
for _, component in ipairs(self.components) do
|
||||||
|
component:onWirePortsReady()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@return nil
|
---@return nil
|
||||||
@ -212,18 +227,67 @@ function Vehicle:update()
|
|||||||
--end
|
--end
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param component PowertrainComponent
|
|
||||||
---@param result? string
|
|
||||||
---@return nil
|
---@return nil
|
||||||
function Vehicle:printComponentTree(component, result)
|
function PrintTree(root)
|
||||||
-- result = result or component.name
|
if root == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
-- if component.output then
|
local lines = { { Color(255, 255, 255), root.name } }
|
||||||
-- result = result .. component.name
|
local subtreeLines = PrintSubtree(root, '')
|
||||||
-- return self:printComponentTree(component.output, result .. ' -> ')
|
for _, line in ipairs(subtreeLines) do
|
||||||
-- else
|
table.insert(lines, line)
|
||||||
-- print(result)
|
end
|
||||||
-- end
|
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
print(unpack(line))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return (string | Color)[]
|
||||||
|
function PrintSubtree(root, prefix)
|
||||||
|
if root == nil then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type (string | Color)[]
|
||||||
|
local lines = {}
|
||||||
|
|
||||||
|
local left = root.outputB
|
||||||
|
local right = root.output or root.outputA
|
||||||
|
|
||||||
|
local hasLeft = left ~= nil
|
||||||
|
local hasRight = right ~= nil
|
||||||
|
|
||||||
|
if not hasLeft and not hasRight then
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasRight then
|
||||||
|
local printStrand = hasLeft and (right.output ~= nil or right.outputA ~= nil or right.outputB ~= nil)
|
||||||
|
local newPrefix = prefix .. (printStrand and '│ \t' or '\t\t')
|
||||||
|
|
||||||
|
table.insert(
|
||||||
|
lines,
|
||||||
|
{ Color(80, 80, 80), prefix .. (hasLeft and '├── ' or '└── '), Color(255, 255, 255), tostring(right.name) }
|
||||||
|
)
|
||||||
|
|
||||||
|
local rightLines = PrintSubtree(right, newPrefix)
|
||||||
|
for _, line in ipairs(rightLines) do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasLeft then
|
||||||
|
table.insert(lines, { Color(80, 80, 80), (hasRight and prefix or '') .. '└── ', Color(255, 255, 255), tostring(left.name) })
|
||||||
|
|
||||||
|
local leftLines = PrintSubtree(left, prefix .. '\t\t')
|
||||||
|
for _, line in ipairs(leftLines) do
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return lines
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
|
---@class Friction
|
||||||
|
---@field force number
|
||||||
|
---@field slip number
|
||||||
|
---@field speed number
|
||||||
local Friction = class('Friction')
|
local Friction = class('Friction')
|
||||||
|
|
||||||
function Friction:initialize(config)
|
function Friction:initialize()
|
||||||
config = config or {}
|
|
||||||
|
|
||||||
self.slipCoefficient = config.SlipCoefficient or 0.8
|
|
||||||
self.forceCoefficient = config.ForceCoefficient or 1.2
|
|
||||||
|
|
||||||
self.force = 0
|
self.force = 0
|
||||||
self.slip = 0
|
self.slip = 0
|
||||||
self.speed = 0
|
self.speed = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type fun(): Friction
|
||||||
|
Friction.new = Friction.new
|
||||||
|
|
||||||
return Friction
|
return Friction
|
||||||
|
|||||||
@ -1,5 +1,17 @@
|
|||||||
|
---@class FrictionPresetConfig
|
||||||
|
---@field B? number
|
||||||
|
---@field C? number
|
||||||
|
---@field D? number
|
||||||
|
---@field E? number
|
||||||
|
|
||||||
|
---@class FrictionPreset
|
||||||
|
---@field B number
|
||||||
|
---@field C number
|
||||||
|
---@field D number
|
||||||
|
---@field E number
|
||||||
local FrictionPreset = class('FrictionPreset')
|
local FrictionPreset = class('FrictionPreset')
|
||||||
|
|
||||||
|
---@param config FrictionPresetConfig
|
||||||
function FrictionPreset:initialize(config)
|
function FrictionPreset:initialize(config)
|
||||||
config = config or {}
|
config = config or {}
|
||||||
|
|
||||||
@ -9,10 +21,15 @@ function FrictionPreset:initialize(config)
|
|||||||
self.E = config.E or 0.992
|
self.E = config.E or 0.992
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param slip number
|
||||||
|
---@return number
|
||||||
function FrictionPreset:evaluate(slip)
|
function FrictionPreset:evaluate(slip)
|
||||||
local t = math.abs(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))))
|
return self.D * math.sin(self.C * math.atan(self.B * t - self.E * (self.B * t - math.atan(self.B * t))))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type fun(config?: FrictionPresetConfig): FrictionPreset
|
||||||
|
FrictionPreset.new = FrictionPreset.new
|
||||||
|
|
||||||
return FrictionPreset
|
return FrictionPreset
|
||||||
|
|||||||
@ -3,39 +3,51 @@
|
|||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
--@include /koptilnya/libs/utils.txt
|
--@include /koptilnya/libs/utils.txt
|
||||||
|
|
||||||
|
---@type Friction
|
||||||
local Friction = require('./friction.txt')
|
local Friction = require('./friction.txt')
|
||||||
|
|
||||||
|
---@type FrictionPreset
|
||||||
local FrictionPreset = require('./friction_preset.txt')
|
local FrictionPreset = require('./friction_preset.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
require('/koptilnya/libs/utils.txt')
|
require('/koptilnya/libs/utils.txt')
|
||||||
|
|
||||||
---@class CustomWheelConfig
|
---@class CustomWheelConfig
|
||||||
|
---@field ParentPhysObj PhysObj
|
||||||
---@field Name? string
|
---@field Name? string
|
||||||
---@field Direction? integer
|
---@field Direction? integer
|
||||||
|
---@field LateralFrictionPreset? FrictionPresetConfig
|
||||||
|
---@field LongitudinalFrictionPreset? FrictionPresetConfig
|
||||||
|
---@field AligningFrictionPreset? FrictionPresetConfig
|
||||||
|
|
||||||
---@class CustomWheel
|
---@class CustomWheel
|
||||||
local Wheel = class('Wheel')
|
local Wheel = class('Wheel')
|
||||||
|
|
||||||
|
---@param config CustomWheelConfig
|
||||||
function Wheel:initialize(config)
|
function Wheel:initialize(config)
|
||||||
config = config or {}
|
config = config or {}
|
||||||
|
|
||||||
|
---@type string
|
||||||
self.name = config.Name
|
self.name = config.Name
|
||||||
|
|
||||||
self.direction = config.Direction or 1
|
self.direction = config.Direction or 1
|
||||||
self.mass = config.Mass or 20
|
self.mass = config.Mass or 20
|
||||||
self.radius = config.Radius or 0.27
|
self.radius = config.Radius or 0.27
|
||||||
self.rollingResistance = config.RollingResistance or 20
|
self.rollingResistance = config.RollingResistance or 20
|
||||||
self.squat = config.Squat or 0.1
|
|
||||||
self.slipCircleShape = config.SlipCircleShape or 1.05
|
|
||||||
self.casterAngle = math.rad(config.CasterAngle or 0)
|
self.casterAngle = math.rad(config.CasterAngle or 0)
|
||||||
self.camberAngle = math.rad(config.CamberAngle or 0)
|
self.camberAngle = math.rad(config.CamberAngle or 0)
|
||||||
self.toeAngle = math.rad(config.ToeAngle or 0)
|
self.toeAngle = math.rad(config.ToeAngle or 0)
|
||||||
|
|
||||||
self.forwardFriction = Friction:new(config.ForwardFriction)
|
self.forwardFriction = Friction:new()
|
||||||
self.sideFriction = Friction:new(config.SideFriction)
|
self.sideFriction = Friction:new()
|
||||||
|
|
||||||
self.lateralFrictionPreset = FrictionPreset:new(config.LateralFrictionPreset)
|
self.lateralFrictionPreset = FrictionPreset:new(config.LateralFrictionPreset)
|
||||||
|
|
||||||
self.longitudinalFrictionPreset = FrictionPreset:new(config.LongitudinalFrictionPreset)
|
self.longitudinalFrictionPreset = FrictionPreset:new(config.LongitudinalFrictionPreset)
|
||||||
|
|
||||||
self.aligningFrictionPreset = FrictionPreset:new(config.AligningFrictionPreset)
|
self.aligningFrictionPreset = FrictionPreset:new(config.AligningFrictionPreset)
|
||||||
|
self.slipCircleShape = config.SlipCircleShape or 1.05
|
||||||
|
|
||||||
self.motorTorque = 0
|
self.motorTorque = 0
|
||||||
self.brakeTorque = 0
|
self.brakeTorque = 0
|
||||||
@ -51,15 +63,20 @@ function Wheel:initialize(config)
|
|||||||
self.right = Vector(0)
|
self.right = Vector(0)
|
||||||
self.up = Vector(0)
|
self.up = Vector(0)
|
||||||
self.entity = NULL_ENTITY
|
self.entity = NULL_ENTITY
|
||||||
|
|
||||||
|
---@type PhysObj?
|
||||||
self.physObj = nil
|
self.physObj = nil
|
||||||
self.baseInertia = 0.5 * self.mass * math.pow(self.radius, 2)
|
self.baseInertia = 0.6 * self.mass * math.pow(self.radius, 2)
|
||||||
self.inertia = self.baseInertia
|
self.inertia = self.baseInertia
|
||||||
|
|
||||||
self.printDebounced = debounce(function(...)
|
self.printDebounced = debounce(function(...)
|
||||||
print(...)
|
print(...)
|
||||||
end, 1)
|
end, 1)
|
||||||
|
|
||||||
|
self.parentPhysObj = config.ParentPhysObj
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param entity Entity
|
||||||
function Wheel:setEntity(entity)
|
function Wheel:setEntity(entity)
|
||||||
self.entity = entity
|
self.entity = entity
|
||||||
self.entity:setNoDraw(false)
|
self.entity:setNoDraw(false)
|
||||||
@ -72,12 +89,12 @@ function Wheel:setEntity(entity)
|
|||||||
self.physObj:enableDrag(false)
|
self.physObj:enableDrag(false)
|
||||||
self.physObj:setDragCoefficient(0)
|
self.physObj:setDragCoefficient(0)
|
||||||
self.physObj:setAngleDragCoefficient(0)
|
self.physObj:setAngleDragCoefficient(0)
|
||||||
|
self:setInertia(0.6 * self.mass * math.pow(self.radius, 2))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Wheel:setInertia(inertia)
|
function Wheel:setInertia(inertia)
|
||||||
if isValid(self.physObj) then
|
if isValid(self.physObj) then
|
||||||
print(inertia)
|
|
||||||
self.physObj:setInertia(Vector(inertia))
|
self.physObj:setInertia(Vector(inertia))
|
||||||
self.inertia = inertia
|
self.inertia = inertia
|
||||||
end
|
end
|
||||||
@ -104,7 +121,7 @@ function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I)
|
|||||||
local VxAbs = math.abs(Vx)
|
local VxAbs = math.abs(Vx)
|
||||||
local Sx = 0
|
local Sx = 0
|
||||||
|
|
||||||
if Lc / 1000 < 0.01 then
|
if Lc < 0.01 then
|
||||||
Sx = 0
|
Sx = 0
|
||||||
elseif VxAbs >= 0.01 then
|
elseif VxAbs >= 0.01 then
|
||||||
Sx = (W * R - Vx) / VxAbs
|
Sx = (W * R - Vx) / VxAbs
|
||||||
@ -116,6 +133,10 @@ function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I)
|
|||||||
|
|
||||||
W = W + Tm / I * TICK_INTERVAL
|
W = W + Tm / I * TICK_INTERVAL
|
||||||
|
|
||||||
|
if self.name == 'WheelRL' or self.name == 'WheelRR' then
|
||||||
|
self.printDebounced(Color(255, 255, 0), string.format('[%s] ', self.name), Color(255, 255, 255), string.format('W: %.2f', W))
|
||||||
|
end
|
||||||
|
|
||||||
Tb = Tb * (W > 0 and -1 or 1)
|
Tb = Tb * (W > 0 and -1 or 1)
|
||||||
local TbCap = math.abs(W) * I / TICK_INTERVAL
|
local TbCap = math.abs(W) * I / TICK_INTERVAL
|
||||||
local Tbr = math.abs(Tb) - math.abs(TbCap)
|
local Tbr = math.abs(Tb) - math.abs(TbCap)
|
||||||
@ -125,13 +146,11 @@ function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I)
|
|||||||
|
|
||||||
local maxTorque = self.longitudinalFrictionPreset:evaluate(math.abs(Sx)) * Lc * R
|
local maxTorque = self.longitudinalFrictionPreset:evaluate(math.abs(Sx)) * Lc * R
|
||||||
|
|
||||||
|
-- local inertiaSum = (I + self.vehicle.basePhysObject:getInertia():getLength())
|
||||||
|
|
||||||
local errorTorque = (W - Vx / R) * I / TICK_INTERVAL
|
local errorTorque = (W - Vx / R) * I / TICK_INTERVAL
|
||||||
local surfaceTorque = math.clamp(errorTorque, -maxTorque, maxTorque)
|
local surfaceTorque = math.clamp(errorTorque, -maxTorque, maxTorque)
|
||||||
|
|
||||||
if self.name == 'WheelFL' or self.name == 'WheelFR' then
|
|
||||||
surfaceTorque = surfaceTorque
|
|
||||||
end
|
|
||||||
|
|
||||||
W = W - surfaceTorque / I * TICK_INTERVAL
|
W = W - surfaceTorque / I * TICK_INTERVAL
|
||||||
local Fx = surfaceTorque / R
|
local Fx = surfaceTorque / R
|
||||||
|
|
||||||
@ -143,13 +162,6 @@ function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I)
|
|||||||
local deltaOmegaTorque = (W - Winit) * I / TICK_INTERVAL
|
local deltaOmegaTorque = (W - Winit) * I / TICK_INTERVAL
|
||||||
local Tcnt = -surfaceTorque + Tb + Tb2 - deltaOmegaTorque
|
local Tcnt = -surfaceTorque + Tb + Tb2 - deltaOmegaTorque
|
||||||
|
|
||||||
-- self.printDebounced(
|
|
||||||
-- Color(255, 255, 0),
|
|
||||||
-- string.format('[%s] ', self.name),
|
|
||||||
-- Color(255, 255, 255),
|
|
||||||
-- string.format('%s | %s | %s', math.round(Fx), math.round(W), math.round(R))
|
|
||||||
-- )
|
|
||||||
|
|
||||||
return W, Sx, Fx, Tcnt
|
return W, Sx, Fx, Tcnt
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -202,7 +214,7 @@ function Wheel:stepLateral(Vx, Vy, Lc)
|
|||||||
local VxAbs = math.abs(Vx)
|
local VxAbs = math.abs(Vx)
|
||||||
local Sy = 0
|
local Sy = 0
|
||||||
|
|
||||||
if Lc / 1000 < 0.01 then
|
if Lc < 0.01 then
|
||||||
Sy = 0
|
Sy = 0
|
||||||
elseif VxAbs > 0.1 then
|
elseif VxAbs > 0.1 then
|
||||||
Sy = math.deg(math.atan(Vy / VxAbs))
|
Sy = math.deg(math.atan(Vy / VxAbs))
|
||||||
@ -237,7 +249,7 @@ function Wheel:slipCircle(Sx, Sy, Fx, Fy, slipCircleShape)
|
|||||||
return Sx, Sy, Fx, Fy
|
return Sx, Sy, Fx, Fy
|
||||||
end
|
end
|
||||||
|
|
||||||
function Wheel:selfAligningTorque(Sy, load)
|
function Wheel:selfAligningTorque(Sy, Fy, load)
|
||||||
if math.abs(Sy) < 0.001 or load < 0.001 then
|
if math.abs(Sy) < 0.001 or load < 0.001 then
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@ -247,32 +259,34 @@ function Wheel:selfAligningTorque(Sy, load)
|
|||||||
local D = self.aligningFrictionPreset.D
|
local D = self.aligningFrictionPreset.D
|
||||||
local E = self.aligningFrictionPreset.E
|
local E = self.aligningFrictionPreset.E
|
||||||
|
|
||||||
local entityWheelDown = self.entity:localToWorld(Vector(0, 0, -self.radius))
|
local entityWheelDown = self.entity:localToWorld(Vector(0, 0, -self.radius * UNITS_PER_METER))
|
||||||
local physWheelDown = self.entity:localToWorld(-self.up * self.radius)
|
local physWheelDown = self.entity:localToWorld(-self.up * self.radius * UNITS_PER_METER)
|
||||||
local mechanicalTrail = entityWheelDown:getDistance(physWheelDown) * 10
|
|
||||||
|
|
||||||
local casterEffect = math.tan(self.casterAngle) * self.direction
|
local mechanicalTrail = entityWheelDown:getDistance(physWheelDown) * UNITS_TO_METERS
|
||||||
local camberTrailEffect = self.camberAngle * 0.005 * self.direction
|
local pneumaticTrail = 0.2 * math.exp(-5 * math.abs(Sy))
|
||||||
-- local toeSyOffset = math.tan(self.toeAngle * self.direction) * 0.1
|
|
||||||
|
|
||||||
local effectiveTrail = mechanicalTrail * self.radius
|
local Mz = -Fy * (mechanicalTrail + pneumaticTrail)
|
||||||
|
|
||||||
local D_scaled = D * load * effectiveTrail
|
if self.name == 'WheelFL' or self.name == 'WheelFR' then
|
||||||
|
-- self.printDebounced(
|
||||||
|
-- Color(255, 255, 0),
|
||||||
|
-- string.format('[%s] ', self.name),
|
||||||
|
-- Color(255, 255, 255),
|
||||||
|
-- string.format('Mz: %.2f | Sy: %.2f | Fy: %.2f | mechanicalTrail: %.3f', Mz, Sy, Fy, mechanicalTrail)
|
||||||
|
-- )
|
||||||
|
end
|
||||||
|
|
||||||
-- Sy = Sy + toeSyOffset
|
return Mz -- TICK_INTERVAL
|
||||||
|
|
||||||
local term = B * Sy - E * (B * Sy - math.atan(B * Sy))
|
|
||||||
local Mz = D_scaled * math.sin(C * math.atan(term))
|
|
||||||
|
|
||||||
return Mz * self.direction -- / TICK_INTERVAL
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Wheel:rotateAxes()
|
function Wheel:rotateAxes()
|
||||||
local ang = self.entity:getAngles()
|
local ang = self.physObj:getAngles()
|
||||||
|
|
||||||
-- Base directions
|
-- Base directions
|
||||||
|
---@type Vector
|
||||||
local forward = ang:getForward() * self.direction
|
local forward = ang:getForward() * self.direction
|
||||||
local up = ang:getUp()
|
local up = ang:getUp()
|
||||||
|
---@type Vector
|
||||||
local right = -ang:getRight() * self.direction
|
local right = -ang:getRight() * self.direction
|
||||||
|
|
||||||
-- Step 1: Steer + Toe (rotate forward/right around up)
|
-- Step 1: Steer + Toe (rotate forward/right around up)
|
||||||
@ -301,15 +315,44 @@ function Wheel:update()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local externalStress = self.physObj:getStress()
|
local frictionSnapshot = self.physObj:getFrictionSnapshot()
|
||||||
local velocity = self.physObj:getVelocity() * UNITS_TO_METERS
|
local externalStress, internalStress = self.physObj:getStress()
|
||||||
|
|
||||||
self.hasHit = externalStress ~= 0
|
self.hasHit = #frictionSnapshot > 0
|
||||||
self.load = externalStress * KG_TO_KN * 1000
|
self.load = self.hasHit and externalStress * KG_TO_N or 0
|
||||||
|
|
||||||
|
---@type PhysObj?
|
||||||
|
local frictionSnapshotItem = self.hasHit and frictionSnapshot[1] or nil
|
||||||
|
|
||||||
|
local surfaceVelocity = Vector(0, 0, 0)
|
||||||
|
|
||||||
|
if frictionSnapshotItem ~= nil then
|
||||||
|
surfaceVelocity = frictionSnapshotItem.Other:getVelocityAtPoint(frictionSnapshotItem.ContactPoint)
|
||||||
|
end
|
||||||
|
|
||||||
|
local velocity = (self.parentPhysObj:getVelocityAtPoint(self.entity:getPos()) - surfaceVelocity) * UNITS_TO_METERS
|
||||||
|
-- local velocity = self.physObj:getVelocity() * UNITS_TO_METERS
|
||||||
|
|
||||||
|
-- self.load = frictionSnapshotItem and frictionSnapshotItem.NormalForce / KG_TO_N or 0
|
||||||
|
|
||||||
self.longitudinalLoadCoefficient = self:getLongitudinalLoadCoefficient(self.load)
|
self.longitudinalLoadCoefficient = self:getLongitudinalLoadCoefficient(self.load)
|
||||||
self.lateralLoadCoefficient = self:getLateralLoadCoefficient(self.load)
|
self.lateralLoadCoefficient = self:getLateralLoadCoefficient(self.load)
|
||||||
|
|
||||||
|
-- if self.name == 'WheelRL' or self.name == 'WheelRR' then
|
||||||
|
-- self.printDebounced(
|
||||||
|
-- Color(255, 255, 0),
|
||||||
|
-- string.format('[%s] ', self.name),
|
||||||
|
-- Color(255, 255, 255),
|
||||||
|
-- string.format(
|
||||||
|
-- 'Load: %.2f | LonLc: %.2f | LaLc: %.2f | externalStress: %.2f',
|
||||||
|
-- self.load,
|
||||||
|
-- self.longitudinalLoadCoefficient,
|
||||||
|
-- self.lateralLoadCoefficient,
|
||||||
|
-- externalStress
|
||||||
|
-- )
|
||||||
|
-- )
|
||||||
|
-- end
|
||||||
|
|
||||||
self:rotateAxes()
|
self:rotateAxes()
|
||||||
|
|
||||||
local forwardSpeed = 0
|
local forwardSpeed = 0
|
||||||
@ -332,9 +375,19 @@ function Wheel:update()
|
|||||||
|
|
||||||
local Sy, Fy = self:stepLateral(forwardSpeed, sideSpeed, self.lateralLoadCoefficient)
|
local Sy, Fy = self:stepLateral(forwardSpeed, sideSpeed, self.lateralLoadCoefficient)
|
||||||
|
|
||||||
Sx, Sy, Fx, Fy = self:slipCircle(Sx, Sy, Fx, Fy, 1.05)
|
Sx, Sy, Fx, Fy = self:slipCircle(Sx, Sy, Fx, Fy, self.slipCircleShape)
|
||||||
|
|
||||||
self.mz = self:selfAligningTorque(Sy, self.load)
|
-- Traction circle: ограничение по максимальной силе сцепления
|
||||||
|
-- local mu = 1.0 -- коэффициент трения (может быть в пресете)
|
||||||
|
-- local F_max = self.load * mu
|
||||||
|
-- local F_total = math.sqrt(Fx * Fx + Fy * Fy)
|
||||||
|
-- if F_total > F_max and F_total > 0 then
|
||||||
|
-- local scale = F_max / F_total
|
||||||
|
-- Fx = Fx * scale
|
||||||
|
-- Fy = Fy * scale
|
||||||
|
-- end
|
||||||
|
|
||||||
|
self.mz = self:selfAligningTorque(Sy, Fy, self.load)
|
||||||
|
|
||||||
self.angularVelocity = W
|
self.angularVelocity = W
|
||||||
self.counterTorque = CounterTq * self.direction
|
self.counterTorque = CounterTq * self.direction
|
||||||
@ -346,4 +399,7 @@ function Wheel:update()
|
|||||||
self.sideFriction.speed = sideSpeed
|
self.sideFriction.speed = sideSpeed
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type fun(config: CustomWheelConfig): CustomWheel
|
||||||
|
Wheel.new = Wheel.new
|
||||||
|
|
||||||
return Wheel
|
return Wheel
|
||||||
|
|||||||
@ -19,6 +19,9 @@ function WireComponent:initialize()
|
|||||||
self.wireOutputs = {}
|
self.wireOutputs = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
|
function WireComponent:onWirePortsReady() end
|
||||||
|
|
||||||
---@return nil
|
---@return nil
|
||||||
function WireComponent:updateWireOutputs() end
|
function WireComponent:updateWireOutputs() end
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ function Sound:initialize(redline, parent, sounds)
|
|||||||
[8500] = 'https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_on_8500.ogg',
|
[8500] = 'https://raw.githubusercontent.com/koptilnya/gmod-data/main/engine_sounds/bmw_s54/ext_e30s54_on_8500.ogg',
|
||||||
}
|
}
|
||||||
local redline = redline or 7000
|
local redline = redline or 7000
|
||||||
self.active = false
|
self.active = true
|
||||||
local soundObjects = {}
|
local soundObjects = {}
|
||||||
local soundRpms = {}
|
local soundRpms = {}
|
||||||
local maxValue = 0
|
local maxValue = 0
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
--@name koptilnya/libs/constants
|
--@name koptilnya/libs/constants
|
||||||
|
|
||||||
_G.NULL_ENTITY = entity(0)
|
NULL_ENTITY = entity(0)
|
||||||
_G.CURRENT_PLAYER = player()
|
CURRENT_PLAYER = player()
|
||||||
_G.OWNER = owner()
|
OWNER = owner()
|
||||||
_G.IS_ME = CURRENT_PLAYER == OWNER
|
IS_ME = CURRENT_PLAYER == OWNER
|
||||||
_G.TICK_INTERVAL = game.getTickInterval()
|
TICK_INTERVAL = game.getTickInterval()
|
||||||
_G.RAD_TO_RPM = 9.5493
|
RAD_TO_RPM = 9.5493
|
||||||
_G.RPM_TO_RAD = 0.10472
|
RPM_TO_RAD = 0.10472
|
||||||
_G.UNITS_PER_METER = 39.37
|
UNITS_PER_METER = 39.37
|
||||||
_G.UNITS_TO_METERS = 0.01905
|
UNITS_TO_METERS = 0.01905
|
||||||
_G.KG_TO_N = 9.80665
|
KG_TO_N = 9.80665
|
||||||
_G.KG_TO_KN = 0.00980665
|
KG_TO_KN = 0.00980665
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user