types
This commit is contained in:
@@ -1,14 +1,16 @@
|
||||
---@class Friction
|
||||
---@field force number
|
||||
---@field slip number
|
||||
---@field speed number
|
||||
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
|
||||
function Friction:initialize()
|
||||
self.force = 0
|
||||
self.slip = 0
|
||||
self.speed = 0
|
||||
end
|
||||
|
||||
---@type fun(): Friction
|
||||
Friction.new = Friction.new
|
||||
|
||||
return Friction
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
---@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')
|
||||
|
||||
---@param config FrictionPresetConfig
|
||||
function FrictionPreset:initialize(config)
|
||||
config = config or {}
|
||||
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
|
||||
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
|
||||
|
||||
---@param slip number
|
||||
---@return number
|
||||
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
|
||||
|
||||
---@type fun(config?: FrictionPresetConfig): FrictionPreset
|
||||
FrictionPreset.new = FrictionPreset.new
|
||||
|
||||
return FrictionPreset
|
||||
|
||||
@@ -3,39 +3,51 @@
|
||||
--@include /koptilnya/libs/constants.txt
|
||||
--@include /koptilnya/libs/utils.txt
|
||||
|
||||
---@type Friction
|
||||
local Friction = require('./friction.txt')
|
||||
|
||||
---@type FrictionPreset
|
||||
local FrictionPreset = require('./friction_preset.txt')
|
||||
|
||||
require('/koptilnya/libs/constants.txt')
|
||||
require('/koptilnya/libs/utils.txt')
|
||||
|
||||
---@class CustomWheelConfig
|
||||
---@field ParentPhysObj PhysObj
|
||||
---@field Name? string
|
||||
---@field Direction? integer
|
||||
---@field LateralFrictionPreset? FrictionPresetConfig
|
||||
---@field LongitudinalFrictionPreset? FrictionPresetConfig
|
||||
---@field AligningFrictionPreset? FrictionPresetConfig
|
||||
|
||||
---@class CustomWheel
|
||||
local Wheel = class('Wheel')
|
||||
|
||||
---@param config CustomWheelConfig
|
||||
function Wheel:initialize(config)
|
||||
config = config or {}
|
||||
|
||||
---@type string
|
||||
self.name = config.Name
|
||||
|
||||
self.direction = config.Direction or 1
|
||||
self.mass = config.Mass or 20
|
||||
self.radius = config.Radius or 0.27
|
||||
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.camberAngle = math.rad(config.CamberAngle or 0)
|
||||
self.toeAngle = math.rad(config.ToeAngle or 0)
|
||||
|
||||
self.forwardFriction = Friction:new(config.ForwardFriction)
|
||||
self.sideFriction = Friction:new(config.SideFriction)
|
||||
self.forwardFriction = Friction:new()
|
||||
self.sideFriction = Friction:new()
|
||||
|
||||
self.lateralFrictionPreset = FrictionPreset:new(config.LateralFrictionPreset)
|
||||
|
||||
self.longitudinalFrictionPreset = FrictionPreset:new(config.LongitudinalFrictionPreset)
|
||||
|
||||
self.aligningFrictionPreset = FrictionPreset:new(config.AligningFrictionPreset)
|
||||
self.slipCircleShape = config.SlipCircleShape or 1.05
|
||||
|
||||
self.motorTorque = 0
|
||||
self.brakeTorque = 0
|
||||
@@ -51,15 +63,20 @@ function Wheel:initialize(config)
|
||||
self.right = Vector(0)
|
||||
self.up = Vector(0)
|
||||
self.entity = NULL_ENTITY
|
||||
|
||||
---@type PhysObj?
|
||||
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.printDebounced = debounce(function(...)
|
||||
print(...)
|
||||
end, 1)
|
||||
|
||||
self.parentPhysObj = config.ParentPhysObj
|
||||
end
|
||||
|
||||
---@param entity Entity
|
||||
function Wheel:setEntity(entity)
|
||||
self.entity = entity
|
||||
self.entity:setNoDraw(false)
|
||||
@@ -72,12 +89,12 @@ function Wheel:setEntity(entity)
|
||||
self.physObj:enableDrag(false)
|
||||
self.physObj:setDragCoefficient(0)
|
||||
self.physObj:setAngleDragCoefficient(0)
|
||||
self:setInertia(0.6 * self.mass * math.pow(self.radius, 2))
|
||||
end
|
||||
end
|
||||
|
||||
function Wheel:setInertia(inertia)
|
||||
if isValid(self.physObj) then
|
||||
print(inertia)
|
||||
self.physObj:setInertia(Vector(inertia))
|
||||
self.inertia = inertia
|
||||
end
|
||||
@@ -104,7 +121,7 @@ function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I)
|
||||
local VxAbs = math.abs(Vx)
|
||||
local Sx = 0
|
||||
|
||||
if Lc / 1000 < 0.01 then
|
||||
if Lc < 0.01 then
|
||||
Sx = 0
|
||||
elseif VxAbs >= 0.01 then
|
||||
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
|
||||
|
||||
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)
|
||||
local TbCap = math.abs(W) * I / TICK_INTERVAL
|
||||
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 inertiaSum = (I + self.vehicle.basePhysObject:getInertia():getLength())
|
||||
|
||||
local errorTorque = (W - Vx / R) * I / TICK_INTERVAL
|
||||
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
|
||||
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 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
|
||||
end
|
||||
|
||||
@@ -202,7 +214,7 @@ function Wheel:stepLateral(Vx, Vy, Lc)
|
||||
local VxAbs = math.abs(Vx)
|
||||
local Sy = 0
|
||||
|
||||
if Lc / 1000 < 0.01 then
|
||||
if Lc < 0.01 then
|
||||
Sy = 0
|
||||
elseif VxAbs > 0.1 then
|
||||
Sy = math.deg(math.atan(Vy / VxAbs))
|
||||
@@ -237,7 +249,7 @@ function Wheel:slipCircle(Sx, Sy, Fx, Fy, slipCircleShape)
|
||||
return Sx, Sy, Fx, Fy
|
||||
end
|
||||
|
||||
function Wheel:selfAligningTorque(Sy, load)
|
||||
function Wheel:selfAligningTorque(Sy, Fy, load)
|
||||
if math.abs(Sy) < 0.001 or load < 0.001 then
|
||||
return 0
|
||||
end
|
||||
@@ -247,32 +259,34 @@ function Wheel:selfAligningTorque(Sy, load)
|
||||
local D = self.aligningFrictionPreset.D
|
||||
local E = self.aligningFrictionPreset.E
|
||||
|
||||
local entityWheelDown = self.entity:localToWorld(Vector(0, 0, -self.radius))
|
||||
local physWheelDown = self.entity:localToWorld(-self.up * self.radius)
|
||||
local mechanicalTrail = entityWheelDown:getDistance(physWheelDown) * 10
|
||||
local entityWheelDown = self.entity:localToWorld(Vector(0, 0, -self.radius * UNITS_PER_METER))
|
||||
local physWheelDown = self.entity:localToWorld(-self.up * self.radius * UNITS_PER_METER)
|
||||
|
||||
local casterEffect = math.tan(self.casterAngle) * self.direction
|
||||
local camberTrailEffect = self.camberAngle * 0.005 * self.direction
|
||||
-- local toeSyOffset = math.tan(self.toeAngle * self.direction) * 0.1
|
||||
local mechanicalTrail = entityWheelDown:getDistance(physWheelDown) * UNITS_TO_METERS
|
||||
local pneumaticTrail = 0.2 * math.exp(-5 * math.abs(Sy))
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
return Mz -- TICK_INTERVAL
|
||||
end
|
||||
|
||||
function Wheel:rotateAxes()
|
||||
local ang = self.entity:getAngles()
|
||||
local ang = self.physObj:getAngles()
|
||||
|
||||
-- Base directions
|
||||
---@type Vector
|
||||
local forward = ang:getForward() * self.direction
|
||||
local up = ang:getUp()
|
||||
---@type Vector
|
||||
local right = -ang:getRight() * self.direction
|
||||
|
||||
-- Step 1: Steer + Toe (rotate forward/right around up)
|
||||
@@ -301,15 +315,44 @@ function Wheel:update()
|
||||
return
|
||||
end
|
||||
|
||||
local externalStress = self.physObj:getStress()
|
||||
local velocity = self.physObj:getVelocity() * UNITS_TO_METERS
|
||||
local frictionSnapshot = self.physObj:getFrictionSnapshot()
|
||||
local externalStress, internalStress = self.physObj:getStress()
|
||||
|
||||
self.hasHit = externalStress ~= 0
|
||||
self.load = externalStress * KG_TO_KN * 1000
|
||||
self.hasHit = #frictionSnapshot > 0
|
||||
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.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()
|
||||
|
||||
local forwardSpeed = 0
|
||||
@@ -332,9 +375,19 @@ function Wheel:update()
|
||||
|
||||
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.counterTorque = CounterTq * self.direction
|
||||
@@ -346,4 +399,7 @@ function Wheel:update()
|
||||
self.sideFriction.speed = sideSpeed
|
||||
end
|
||||
|
||||
---@type fun(config: CustomWheelConfig): CustomWheel
|
||||
Wheel.new = Wheel.new
|
||||
|
||||
return Wheel
|
||||
|
||||
Reference in New Issue
Block a user