From 0b71940310699488a99a5d58b68aba6f35964e83 Mon Sep 17 00:00:00 2001 From: Ivan Grachyov Date: Sat, 11 Dec 2021 17:36:34 +0500 Subject: [PATCH] Implementing better steering --- koptilnya/better_steering/config/sample.txt | 42 ++++ koptilnya/better_steering/slave.txt | 114 +++++++++++ koptilnya/better_steering/steering.txt | 79 ++++++++ koptilnya/data/models/bmw_m4_g82.txt | 45 +++++ koptilnya/engine_remastered/clutch.txt | 4 +- .../engine_remastered/configs/sx240_stock.txt | 2 +- koptilnya/engine_remastered/differential.txt | 4 +- koptilnya/engine_remastered/engine.txt | 4 +- .../engine_remastered/gearboxes/base.txt | 4 +- .../engine_remastered/novojiloff_diff.txt | 4 +- koptilnya/grip_steering/main.txt | 188 ------------------ koptilnya/grip_steering/slave.txt | 29 --- koptilnya/input_system/configs/sample.txt | 6 +- .../wire_component.txt | 0 14 files changed, 294 insertions(+), 231 deletions(-) create mode 100644 koptilnya/better_steering/config/sample.txt create mode 100644 koptilnya/better_steering/slave.txt create mode 100644 koptilnya/better_steering/steering.txt create mode 100644 koptilnya/data/models/bmw_m4_g82.txt delete mode 100644 koptilnya/grip_steering/main.txt delete mode 100644 koptilnya/grip_steering/slave.txt rename koptilnya/{engine_remastered => libs}/wire_component.txt (100%) diff --git a/koptilnya/better_steering/config/sample.txt b/koptilnya/better_steering/config/sample.txt new file mode 100644 index 0000000..0831ccc --- /dev/null +++ b/koptilnya/better_steering/config/sample.txt @@ -0,0 +1,42 @@ +-- @name Better steering sample config +-- @author Koptilnya1337 +-- @server +-- @include /koptilnya/better_steering/steering.txt +require('/koptilnya/better_steering/steering.txt') + +Steering:new({ + Sensitivity = 0.04, + Slaves = {{ + Lock = 30, + Camber = -2, + Caster = 5, + Ackermann = 15.43, + Toe = 0, + Offset = 180 + }, { + Lock = 30, + Camber = -2, + Caster = 5, + Ackermann = 15.43, + Toe = 0, + Offset = 180, + IsRight = true + }, { + Lock = 1, + Camber = -2, + Caster = 0, + Ackermann = 0, + Toe = 0, + Offset = 180, + Direction = -1 + }, { + Lock = 1, + Camber = -2, + Caster = 0, + Ackermann = 0, + Toe = 0, + Offset = 180, + IsRight = true, + Direction = -1 + }} +}) diff --git a/koptilnya/better_steering/slave.txt b/koptilnya/better_steering/slave.txt new file mode 100644 index 0000000..846ab17 --- /dev/null +++ b/koptilnya/better_steering/slave.txt @@ -0,0 +1,114 @@ +-- @include /koptilnya/libs/wire_component.txt +-- @include /koptilnya/libs/constants.txt +require('/koptilnya/libs/wire_component.txt') +require('/koptilnya/libs/constants.txt') + +Slave = class('Slave', WireComponent) + +function Slave:initialize(options) + options = options or {} + + self.lock = options.Lock or 0 + self.camber = options.Camber or 0 + self.caster = options.Caster or 0 + self.ackermann = options.Ackermann or 0 + self.toe = options.Toe or 0 + self.offset = options.Offset or 0 + self.isRight = options.IsRight or false + self.direction = options.Direction or 1 + + self.order = options.Order or 0 + + self.entity = nil + self.wheel = nil + self.base = options.Base or nil + + self.steer = 0 + self.steeringAngle = 0 + self.lateralVel = 0 + self.lateralForce = 0 + self.selfAligningTorque = 0 + self.correctingAngle = 0 +end + +function Slave:getInputs() + local inputs = {} + inputs['SL_' .. self.order] = 'entity' + inputs['W_' .. self.order] = 'entity' + + return inputs +end + +function Slave:getOutputs() + local outputs = {} + + outputs['SL_' .. self.order .. '_LateralForce'] = 'number' + outputs['SL_' .. self.order .. '_SelfAligningTq'] = 'number' + outputs['SL_' .. self.order .. '_CorrectingAngle'] = 'number' + outputs['W_' .. self.order .. '_LateralVel'] = 'number' + + return outputs +end + +function Slave:updateOutputs() + wire.ports['SL_' .. self.order .. '_LateralForce'] = self.lateralForce + wire.ports['SL_' .. self.order .. '_SelfAligningTq'] = self.selfAligningTorque + wire.ports['SL_' .. self.order .. '_CorrectingAngle'] = self.correctingAngle + wire.ports['W_' .. self.order .. '_LateralVel'] = self.lateralVel +end + +function Slave:update() + self.entity = wire.ports['SL_' .. self.order] + self.wheel = wire.ports['W_' .. self.order] + + if isValid(self.entity) and isValid(self.base) and isValid(self.wheel) then + local wheelMass = self.wheel:getMass() + local wheelInertia = self.wheel:getInertia():getLength() + local wheelRadius = self.wheel:getModelRadius() / UNITS_PER_METER + local wheelFriction = self.wheel:getFriction() + local lateralVel = self.wheel:worldToLocal(self.wheel:getVelocity() + self.wheel:getPos())[2] + + self.lateralVel = lateralVel + + if math.abs(self.steer) > 0 then + self.correctingAngle = 0 + else + self.lateralForce = wheelFriction * wheelMass * lateralVel / TICK_INTERVAL / + UNITS_PER_METER + self.selfAligningTorque = self.lateralForce * wheelRadius * 2 + self.correctingAngle = self.selfAligningTorque / wheelInertia * TICK_INTERVAL + end + + if self.isRight then + self.correctingAngle = self.correctingAngle * -1 + end + + self.correctingAngle = 0 + + if not self.entity:isFrozen() and not self.entity:isPlayerHolding() then + self.entity:setFrozen(1) + end + + local inc = self.steer * self.lock * self.direction + self.steeringAngle = math.clamp(self.steeringAngle + inc + self.correctingAngle, -self.lock, self.lock) + + local camber = self.isRight and -self.camber or self.camber + local steerRatio = self.lock ~= 0 and self.steeringAngle / self.lock or 0 + local caster = steerRatio * self.caster + local toe = self.isRight and self.toe or -self.toe + local ackermann = 0 + + if steerRatio > 0 then + ackermann = self.isRight and -steerRatio * self.ackermann or steerRatio * self.ackermann + elseif steerRatio < 0 then + ackermann = self.isRight and steerRatio * self.ackermann or -steerRatio * self.ackermann + end + + -- Dividing ackermann by 2 because ackermann angle is difference between wheels angle + -- So if we don't divide it by 2, the result angle will be 2 times more than expected + local angle = Angle(0, self.offset + self.steeringAngle + toe + ackermann / 2, camber - caster) + local localizedAngle = self.base:localToWorldAngles(angle) + + self.entity:setAngles(localizedAngle) + end +end diff --git a/koptilnya/better_steering/steering.txt b/koptilnya/better_steering/steering.txt new file mode 100644 index 0000000..112368f --- /dev/null +++ b/koptilnya/better_steering/steering.txt @@ -0,0 +1,79 @@ +-- @include /koptilnya/libs/wire_component.txt +-- @include ./slave.txt +-- @include /koptilnya/libs/table.txt +require('/koptilnya/libs/wire_component.txt') +require('./slave.txt') +require('/koptilnya/libs/table.txt') + +Steering = class('Steering', WireComponent) + +function Steering:initialize(options) + options = options or {} + self.slaves = {} + self.steer = 0 + self.sensitivity = options.Sensitivity or 1 + + for key, slave in ipairs(options.Slaves) do + slave.Order = key + slave.Base = wire.ports.Base + table.insert(self.slaves, Slave:new(slave)) + end + + self:_adjustPorts() + + hook.add('tick', 'update', function() + self:update() + end) +end + +function Steering:_adjustPorts() + local inputs = {} + local outputs = {} + + inputs = table.merge(inputs, self:getInputs()) + outputs = table.merge(outputs, self:getOutputs()) + + for _, slave in ipairs(self.slaves) do + inputs = table.merge(inputs, slave:getInputs()) + outputs = table.merge(outputs, slave:getOutputs()) + end + + wire.adjustPorts(inputs, outputs) +end + +function Steering:getInputs() + return { + TargetSteer = 'number', + Base = 'entity' + } +end + +function Steering:getOutputs() + return { + Steer = 'number' + } +end + +function Steering:updateOutputs() + local totalSteeringAngle = table.reduce(self.slaves, function(steer, slave) + return steer + slave.direction * slave.steeringAngle / (slave.lock ~= 0 and slave.lock or 1) + end, 0) + + local steerableSlaves = table.filter(self.slaves, function(slave) + return slave.lock ~= 0 + end) + + wire.ports.Steer = totalSteeringAngle / #steerableSlaves +end + +function Steering:update() + self.steer = wire.ports.TargetSteer * self.sensitivity + + self:updateOutputs() + + for _, slave in ipairs(self.slaves) do + slave.steer = self.steer + slave:update() + slave:updateOutputs() + end +end diff --git a/koptilnya/data/models/bmw_m4_g82.txt b/koptilnya/data/models/bmw_m4_g82.txt new file mode 100644 index 0000000..77834e1 --- /dev/null +++ b/koptilnya/data/models/bmw_m4_g82.txt @@ -0,0 +1,45 @@ +-- @shared +-- @name BMW M4 G82 Competition +-- @author Koptilnya1337 +-- @include /koptilnya/mesh_loader/builder.txt +require("/koptilnya/mesh_loader/builder.txt") + +DEBUG_MODE = true + +local LINK = "https://drive.google.com/u/0/uc?id=1yxjPtVhoMROwSB56tfL_dbBPrW1Yjr9h&export=download" +local SCALE = Vector(1) + +local Materials = +{ + Body = "phoenix_storms/fender_white", + Carbon = "sprops/textures/sprops_cfiber2" +} + +local Colors = +{ + Body = Color(115,218,201), + Carbon = Color(255,255,255) +} + +local builder = {} + +if SERVER then + builder = MeshBuilder:new(LINK) + + local this = chip() + + -- Body group + builder:build("Body", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) + builder:build("LeftDoor", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) + builder:build("RightDoor", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) + builder:build("Bonnet", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) + builder:build("Trunk", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) + builder:build("Roof", Vector(0), Angle(0), SCALE, Colors.Carbon, Materials.Carbon, this, this) + + local result = builder:getResult() +else + PERMA.onPermissionsGained = function() + builder = MeshBuilder:new(LINK) + end + PERMA.build() +end diff --git a/koptilnya/engine_remastered/clutch.txt b/koptilnya/engine_remastered/clutch.txt index 9bb9ec4..561880b 100644 --- a/koptilnya/engine_remastered/clutch.txt +++ b/koptilnya/engine_remastered/clutch.txt @@ -1,8 +1,8 @@ --- @include ./wire_component.txt +-- @include /koptilnya/libs/wire_component.txt -- @include ./enums/gearbox.txt -- @include /koptilnya/libs/constants.txt require('/koptilnya/libs/constants.txt') -require('./wire_component.txt') +require('/koptilnya/libs/wire_component.txt') local gearboxTypes = require('./enums/gearbox.txt') Clutch = class('Clutch', WireComponent) diff --git a/koptilnya/engine_remastered/configs/sx240_stock.txt b/koptilnya/engine_remastered/configs/sx240_stock.txt index 09408e5..e2ff6c2 100644 --- a/koptilnya/engine_remastered/configs/sx240_stock.txt +++ b/koptilnya/engine_remastered/configs/sx240_stock.txt @@ -24,7 +24,7 @@ Vehicle:new({ } }, Clutch = {Stiffness = 20, Damping = 0.7, MaxTorque = 400}, - Gearbox = {Type = 'MANUAL', ShiftDuration = 0.2, ShiftSmoothness = 0.3, Ratios = {3.626, 2.200, 1.541, 1.213, 1.000, 0.767}, Reverse = 3.437}, + Gearbox = {Type = 'MANUAL', ShiftDuration = 0.2, ShiftSmoothness = 0.3, Ratios = {3.626, 2.200, 1.541, 1.213, 1.000, 0.767}, Reverse = 3.437}, Axles = {{Power = 0.96, Coast = 0.96, Preload = 10, UsePowerBias = 10, ViscousCoeff = 0.96, Axle = Vector(1, 0, 0), DistributionCoeff = 1, FinalDrive = 3.392}}, Systems = {{Type = 'LAUNCH', Limit = 3500}, {Type = 'TRACTION', Limit = 1.5}} }) diff --git a/koptilnya/engine_remastered/differential.txt b/koptilnya/engine_remastered/differential.txt index d92361f..6dbf61c 100644 --- a/koptilnya/engine_remastered/differential.txt +++ b/koptilnya/engine_remastered/differential.txt @@ -1,8 +1,8 @@ -- @server -- @include /koptilnya/libs/constants.txt --- @include ./wire_component.txt +-- @include /koptilnya/libs/wire_component.txt require('/koptilnya/libs/constants.txt') -require('./wire_component.txt') +require('/koptilnya/libs/wire_component.txt') Differential = class('Differential', WireComponent) diff --git a/koptilnya/engine_remastered/engine.txt b/koptilnya/engine_remastered/engine.txt index 134c919..5016026 100644 --- a/koptilnya/engine_remastered/engine.txt +++ b/koptilnya/engine_remastered/engine.txt @@ -1,6 +1,6 @@ --- @include ./wire_component.txt +-- @include /koptilnya/libs/wire_component.txt -- @include /koptilnya/libs/constants.txt -require('./wire_component.txt') +require('/koptilnya/libs/wire_component.txt') require('/koptilnya/libs/constants.txt') Engine = class('Engine', WireComponent) diff --git a/koptilnya/engine_remastered/gearboxes/base.txt b/koptilnya/engine_remastered/gearboxes/base.txt index cbb4e2c..c0aec4a 100644 --- a/koptilnya/engine_remastered/gearboxes/base.txt +++ b/koptilnya/engine_remastered/gearboxes/base.txt @@ -1,5 +1,5 @@ --- @include ../wire_component.txt -require('../wire_component.txt') +-- @include /koptilnya/libs/wire_component.txt +require('/koptilnya/libs/wire_component.txt') Gearbox = class('Gearbox', WireComponent) diff --git a/koptilnya/engine_remastered/novojiloff_diff.txt b/koptilnya/engine_remastered/novojiloff_diff.txt index 80c01c6..c947107 100644 --- a/koptilnya/engine_remastered/novojiloff_diff.txt +++ b/koptilnya/engine_remastered/novojiloff_diff.txt @@ -1,8 +1,8 @@ -- @server -- @include /koptilnya/libs/constants.txt --- @include ./wire_component.txt +-- @include /koptilnya/libs/wire_component.txt require('/koptilnya/libs/constants.txt') -require('./wire_component.txt') +require('/koptilnya/libs/wire_component.txt') Differential = class('Differential', WireComponent) diff --git a/koptilnya/grip_steering/main.txt b/koptilnya/grip_steering/main.txt deleted file mode 100644 index e2bddf2..0000000 --- a/koptilnya/grip_steering/main.txt +++ /dev/null @@ -1,188 +0,0 @@ --- @name Grip Steering --- @author DarkSupah --- @server --- @include ./slave.txt -Steering = class('Steering') - -require('./slave.txt') - -function inrange(val, min, max) - return val >= min and val <= max -end - -function isNaN(val) - return val ~= val -end - -function vectorIsNaN(vec) - return isNaN(vec[1]) or isNaN(vec[2]) or isNaN(vec[3]) -end - -function getLocalVelocity(ent) - return ent:worldToLocal((ent:getVelocity() + ent:getPos())) -end - -local options = { - Camber = -2, - Caster = 5, - Ackermann = 15, - Lock = 45, - - ZeroThreshold = 2, - - StartSpeedThreshold = 40, - EndSpeedThreshold = 150, - SpeedCoeff = 0.2, - - StartCorrectionThreshold = 5, - EndCorrectionThreshold = 40, - CorrectionOnCoeff = 0.6, - CorrectionOffCoeff = 1, - CorrectionOnLerp = 0.3, - CorrectionOffLerp = 0.6 -} - -function Steering:_adjustPorts() - local inputs = { - Axles = 'table', - Base = 'entity', - LeftSlave = 'entity', - RightSlave = 'entity' - } - - local outputs = { - SteerAngle = 'number', - TargetAngle = 'number', - CorrectionAngle = 'number' - } - - wire.adjustPorts(inputs, outputs) -end - -function Steering:_createSlaves() - local entities = {wire.ports.LeftSlave, wire.ports.RightSlave} - local slaves = {} - - for k, v in pairs(entities) do - local slave = Slave:new({ - Entity = v, - IsLeft = k % 2 == 1, - Offset = 180, - Base = wire.ports.Base, - Camber = self.slavesConfig.Camber, - Caster = self.slavesConfig.Caster, - Ackermann = self.slavesConfig.Ackermann - }) - - table.insert(self.slaves, slave) - end -end - -function Steering:_updateOutputs() - wire.ports.TargetAngle = self.targetAngle - wire.ports.SteerAngle = self.angle - wire.ports.CorrectionAngle = self.correction -end - -function Steering:_getCorrectionGradient(speed) - return math.clamp(math.remap(speed, self.startCorrectionThreshold, self.endCorrectionThreshold, 0, 1), 0, 1) -end - -function Steering:getBaseSpeed() - local MPH = self.base:getVelocity():getLength() * 3600 / 63360 - local KPH = MPH * 1.609 - - return KPH -end - -function Steering:_getSpeedCorrectionGradient(speed) - return math.clamp(math.remap(speed, self.startSpeedThreshold, self.endSpeedThreshold, 0, 1), 0, 1) -end - -function Steering:_getCorrectionAngle() - local localVel = getLocalVelocity(self.base) - local forwardVel = (localVel * Vector(1, 0, 0)):getNormalized() - local planarVec = (localVel * Vector(1, 1, 0)):getNormalized() - - forwardVel = vectorIsNaN(forwardVel) and Vector(0, 0, 0) or forwardVel - planarVec = vectorIsNaN(planarVec) and Vector(0, 0, 0) or planarVec - - local cross = forwardVel:cross(planarVec) - - return math.deg(math.acos(forwardVel:dot(planarVec))) * math.sign(localVel[2]) -end - -function Steering:initialize(options) - self:_adjustPorts() - - options = options or {} - - self.base = wire.ports.Base - - self.slavesConfig = { - Camber = options.Camber, - Caster = options.Caster, - Ackermann = options.Ackermann - } - - self.lock = options.Lock - - self.targetAngle = 0 - self.angle = 0 - - self.zeroThreshold = options.ZeroThreshold - - self.speedCoeff = options.SpeedCoeff - self.startSpeedThreshold = options.StartSpeedThreshold - self.endSpeedThreshold = options.EndSpeedThreshold - - self.startCorrectionThreshold = options.StartCorrectionThreshold - self.endCorrectionThreshold = options.EndCorrectionThreshold - self.correctionOnCoeff = options.CorrectionOnCoeff - self.correctionOffCoeff = options.CorrectionOffCoeff - - self.correctionOnLerp = options.CorrectionOnLerp - self.correctionOffLerp = options.CorrectionOffLerp - - self.correction = 0 - - self.slaves = {} - - self:_createSlaves() -end - -function Steering:update() - local horizontal = wire.ports.Axles.Horizontal.Value - local horizontalTarget = wire.ports.Axles.Horizontal.Target - - local baseSpeedKMH = self:getBaseSpeed() - local angleCorrection = self:_getCorrectionGradient(baseSpeedKMH) - local speedCorrection = self.angle * self:_getSpeedCorrectionGradient(baseSpeedKMH) * self.speedCoeff - - self.targetAngle = horizontal * self.lock - - local correctionCoeff = horizontalTarget ~= 0 and self.correctionOnCoeff or self.correctionOffCoeff - local correctionLerpCoeff = horizontalTarget ~= 0 and self.correctionOnLerp or self.correctionOffLerp - local correction = self:_getCorrectionAngle() * angleCorrection * correctionCoeff - self.correction = math.lerp(correctionLerpCoeff, self.correction, isNaN(correction) and 0 or correction) - - local possibleAngle = math.clamp(horizontal * self.lock + speedCorrection + self.correction, -self.lock, self.lock) - - if inrange(possibleAngle, -self.zeroThreshold, self.zeroThreshold) then - self.angle = 0 - else - self.angle = possibleAngle - end - - for k, v in pairs(self.slaves) do - v:rotate(self.angle) - end - - self:_updateOutputs() -end - -local steering = Steering:new(options) - -hook.add('think', 'SteeringUpdate', function() - steering:update() -end) diff --git a/koptilnya/grip_steering/slave.txt b/koptilnya/grip_steering/slave.txt deleted file mode 100644 index 6497fff..0000000 --- a/koptilnya/grip_steering/slave.txt +++ /dev/null @@ -1,29 +0,0 @@ -Slave = class('Slave') - -function Slave:initialize(options) - options = options or {} - - self.entity = options.Entity - self.base = options.Base - - self.camber = options.Camber - self.caster = options.Caster - self.ackermann = options.Ackermann - self.isLeft = options.IsLeft - self.offset = options.Offset -end - -function Slave:rotate(ang) - if self.entity:isFrozen() == false then - self.entity:setFrozen(1) - end - - local ackermann = math.sin(math.rad(ang)) ^ 2 * self.ackermann * (self.isLeft and -1 or 1) - local yaw = self.offset + ang + ackermann - local roll = (self.isLeft and 1 or -1) * self.camber - ang / 270 * self.caster - - local angle = Angle(0, yaw, roll) - local transformedAngle = self.base:localToWorldAngles(angle) - - self.entity:setAngles(transformedAngle) -end diff --git a/koptilnya/input_system/configs/sample.txt b/koptilnya/input_system/configs/sample.txt index 4dba483..c36a5da 100644 --- a/koptilnya/input_system/configs/sample.txt +++ b/koptilnya/input_system/configs/sample.txt @@ -2,8 +2,8 @@ Axles = { Horizontal = { Negative = KEY.D, Positive = KEY.A, - Gravity = 0.1, - Sensitivity = 0.2 + Gravity = 1, + Sensitivity = 1 }, Throttle = { Positive = KEY.W, @@ -20,5 +20,5 @@ Axles = { Negative = MOUSE.RIGHT, Gravity = 1, Sensitivity = 1 - }, + } } diff --git a/koptilnya/engine_remastered/wire_component.txt b/koptilnya/libs/wire_component.txt similarity index 100% rename from koptilnya/engine_remastered/wire_component.txt rename to koptilnya/libs/wire_component.txt