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,5 +0,0 @@
{
"files.associations": {
"*.txt": "lua"
}
}

View File

@ -1,6 +1,6 @@
-- @shared -- @shared
-- @name Audi A6 2012 -- @name Audi A6 2012
-- @author DarkSupah -- @author Opti1337, DarkSupah
-- @include /koptilnya/mesh_loader/builder.txt -- @include /koptilnya/mesh_loader/builder.txt
require("/koptilnya/mesh_loader/builder.txt") require("/koptilnya/mesh_loader/builder.txt")
@ -11,7 +11,31 @@ local SCALE = Vector(1)
local Materials = local Materials =
{ {
Body = "phoenix_storms/fender_white", Body = "models/debug/debugwhite",
Doors = {
basetexture = "https://i.imgur.com/VfIk2D6.png",
options = {
flags = 134219776,
color = Vector(0.7969166636467, 0.7969166636467, 0.73720490932465),
envmap = "env_cubemap",
envmaptint = Vector(0.069727085530758, 0.069727085530758, 0.069727085530758),
envmapsaturation = 0.69999998807907,
phong = 1,
phongexponent = 10.0,
}
},
BonnetLivery = {
basetexture = "https://i.imgur.com/MR4z42U.png",
options = {
flags = 0x0100,
baseLayout = {
x = 170,
y = 575,
w = 200,
h = 200
}
}
},
Shadow = "models/debug/debugwhite", Shadow = "models/debug/debugwhite",
Grille = "models/shiny", Grille = "models/shiny",
WindowsOutline = "models/debug/debugwhite", WindowsOutline = "models/debug/debugwhite",
@ -94,9 +118,10 @@ if SERVER then
// Body group // Body group
builder:build("Body", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) builder:build("Body", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("Bonnet", Vector(0), Angle(0), SCALE + Vector(0.002), Color(255, 255, 255), Materials.BonnetLivery, this, this)
builder:build("Bonnet", 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("Trunk", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("Doors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) builder:build("Doors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Doors, this, this)
builder:build("FrontBumper", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) builder:build("FrontBumper", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("BodyMirrors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) builder:build("BodyMirrors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)

View File

@ -0,0 +1,173 @@
-- @shared
-- @name BMW 850CSi 96
-- @author Opti1337, DarkSupah
-- @include /koptilnya/mesh_loader/builder.txt
--@include koptilnya/libs/flux.txt
local flux = require("koptilnya/libs/flux.txt")
require("/koptilnya/mesh_loader/builder.txt")
DEBUG_MODE = true
local LINK = "http://82.179.248.158/bmw_850csi_96.obj#baaz"
local SCALE = Vector(1)
local Materials =
{
--[[
Body = {
basetexture = "phoenix_storms/fender_white",
--bumpmap = "models/flat_normal",
options = {
flags = 134217728 + 2048,
--envmap = "env_cubemap",
--phong = 1,
--phongalbedotint = 1,
--phongboost = 6,
--phongexponent = 80,
--phongfresnelranges = Vector(1, 1, 1),
--halflambert = 1,
--envmapcontrast = 1,
--envmapsaturation = 0.7,
--envmaptint = Vector(0.3, 0.3, 0.3),
--test = Matrix()
}
},
]]
Body = "phoenix_storms/fender_white",
Shadow = "models/debug/debugwhite",
Chrome = "sprops/textures/sprops_chrome",
BlackPlastic = "models/debug/debugwhite",
Backface = "models/debug/debugwhite",
Under = "models/debug/debugwhite",
Mirror = "debug/env_cubemap_model",
Zeroblack = "models/debug/debugwhite"
}
local Colors =
{
Body = Color(230,230,255),
Shadow = Color(125,125,125),
Chrome = Color(255,255,255),
BlackPlastic = Color(25,25,25),
Backface = Color(25,25,25),
Under = Color(0,0,250),
Mirror = Color(255,255,250),
Zeroblack = Color(0,0,0)
}
local builder = {}
if SERVER then
builder = MeshBuilder:new(LINK)
local this = chip()
-- Body
local body = builder:build("body_a", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("hood_a", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("fenders_a", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("doorhandlelf", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("bumperf_a", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("bumperr_a", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("trunk_a", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("skirtl_e", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("wing_d", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
builder:build("wingmirrors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
-- Black plastic
builder:build("black_plastic", Vector(0), Angle(0), SCALE, Colors.BlackPlastic, Materials.BlackPlastic, this, this)
-- Chrome
builder:build("chrome", Vector(0), Angle(0), SCALE, Colors.Chrome, Materials.Chrome, this, this)
builder:build("exhaust_d", Vector(0), Angle(0), SCALE, Colors.Chrome, Materials.Chrome, this, this)
-- Other
builder:build("centerconsolebezels_symbols", Vector(0), Angle(0), SCALE, Colors.Shadow, "https://i.imgur.com/p61iQMu.png", this, this)
builder:build("gaugecluster_a", Vector(0), Angle(0), SCALE, Colors.Shadow, "https://i.imgur.com/VDRC1dz.png", this, this)
builder:build("bumperf_badge", Vector(0), Angle(0), SCALE, Color(255,255,255), "https://i.imgur.com/fCG0wsL.jpeg", this, this)
builder:build("trunk_badge", Vector(0), Angle(0), SCALE, Color(255,255,255), "https://i.imgur.com/fCG0wsL.jpeg", this, this)
builder:build("backface", Vector(0), Angle(0), SCALE, Colors.Backface, Materials.Backface, this, this)
builder:build("under", Vector(0), Angle(0), SCALE, Colors.Under, Materials.Under, this, this)
builder:build("mirror", Vector(0), Angle(0), SCALE, Colors.Mirror, Materials.Mirror, this, this)
builder:build("radiator", Vector(0), Angle(0), SCALE, Color(205,205,205), "phoenix_storms/dome", this, this)
builder:build("zeroblack", Vector(0), Angle(0), SCALE, Colors.Zeroblack, Materials.Zeroblack, this, this)
builder:build("dashpad_a", Vector(0), Angle(0), SCALE, Colors.Shadow, Materials.Shadow, this, this)
builder:build("doorcardlf_a", Vector(0), Angle(0), SCALE, Colors.Shadow, Materials.Shadow, this, this)
builder:build("floor_a", Vector(0), Angle(0), SCALE, Colors.Shadow, Materials.Shadow, this, this)
-- Headlights
local headlightRpos = Vector(-0.56101, -1.8125, 0.603)
local headlightRdir = headlightRpos - Vector(-0.48194, -1.8303, 0.60497)
local headlightRpivot = headlightRdir:getAngle()
local headlightR = holograms.create(this:localToWorld(headlightRpos * 39.37), this:localToWorldAngles(headlightRpivot), "models/holograms/cube.mdl")
headlightR:setNoDraw(true)
headlightR:setParent(this)
builder:build("headlightr", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, headlightR, this)
builder:build("headlightr_backface", Vector(0), Angle(0), SCALE, Colors.Backface, Materials.Backface, headlightR, this)
builder:build("headlightr_black_plastic", Vector(0), Angle(0), SCALE, Colors.BlackPlastic, Materials.BlackPlastic, headlightR, this)
builder:build("headlightr_chrome", Vector(0), Angle(0), SCALE, Colors.Chrome, Materials.Chrome, headlightR, this)
local headlightLpos = Vector(0.56101, -1.8125, 0.603)
local headlightLdir = Vector(0.48194, -1.8303, 0.60497) - headlightLpos
local headlightLpivot = headlightLdir:getAngle()
local headlightL = holograms.create(this:localToWorld(headlightLpos * 39.37), this:localToWorldAngles(headlightLpivot), "models/holograms/cube.mdl")
headlightL:setNoDraw(true)
headlightL:setParent(this)
builder:build("headlightl", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, headlightL, this)
builder:build("headlightl_backface", Vector(0), Angle(0), SCALE, Colors.Backface, Materials.Backface, headlightL, this)
builder:build("headlightl_black_plastic", Vector(0), Angle(0), SCALE, Colors.BlackPlastic, Materials.BlackPlastic, headlightL, this)
builder:build("headlightl_chrome", Vector(0), Angle(0), SCALE, Colors.Chrome, Materials.Chrome, headlightL, this)
local result = builder:getResult()
local headlightl = {
angle = 0
}
local headlightr = {
angle = 0
}
function openHeadlights()
flux.to(headlightl, 1, { angle = 28 }):delay(0.3):ease("quartout")
flux.to(headlightr, 1, { angle = 28 }):ease("quartout")
end
function closeHeadlights()
flux.to(headlightl, 0.8, { angle = 0 }):delay(0.3):ease("quartout")
flux.to(headlightr, 0.8, { angle = 0 }):ease("quartout")
end
hook.add("tick", "_tick", function()
flux.update(game.getTickInterval())
headlightR:setAngles(this:localToWorldAngles(headlightRpivot):rotateAroundAxis(headlightR:getForward(), headlightr.angle))
headlightL:setAngles(this:localToWorldAngles(headlightLpivot):rotateAroundAxis(headlightL:getForward(), headlightl.angle))
end)
wire.adjustPorts({State = "NUMBER"}, {})
hook.add("input", "_input", function(name, value)
if value > 0 then
openHeadlights()
else
closeHeadlights()
end
end)
else
PERMA.onPermissionsGained = function()
builder = MeshBuilder:new(LINK)
end
PERMA.build()
end

View File

@ -50,7 +50,17 @@ if SERVER then
builder:build("plastic", Vector(0, OFFSET, 0), Angle(0), SCALE, COLORS.Plastic, MATERIALS.Plastic, wheel, wheel) builder:build("plastic", Vector(0, OFFSET, 0), Angle(0), SCALE, COLORS.Plastic, MATERIALS.Plastic, wheel, wheel)
builder:build("rim1", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.RimAround, MATERIALS.Rim, wheel, wheel) builder:build("rim1", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.RimAround, MATERIALS.Rim, wheel, wheel)
builder:build("rimAround", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.Rim, MATERIALS.RimAround, wheel, wheel) builder:build("rimAround", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.Rim, MATERIALS.RimAround, wheel, wheel)
builder:build("tyre", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), Color(255, 255, 255), "sprops/textures/sprops_rubber", wheel, wheel) builder:build("tyre", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), Color(255, 255, 255), {
basetexture = "phoenix_storms/car_tire",
bumpmap = "phoenix_storms/tire_bump",
options = {
envmap = "env_cubemap",
envmaptint = Vector(0.45345649123192, 0.45345649123192, 0.45345649123192),
flags = 138414080,
phong = 1,
phongexponent = 10.0
}
}, wheel, wheel)
end end
builder:getResult() builder:getResult()

View File

@ -6,7 +6,7 @@ require("/koptilnya/mesh_loader/builder.txt")
local LINK = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/vaz2106.obj" local LINK = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/vaz2106.obj"
local SCALE = Vector(0.9) local SCALE = Vector(0.9)
local MATERIALS = { local MATERIALS = {
Exterior = "phoenix_storms/mrref2", Exterior = "models/debug/debugwhite",
Chrome = "models/shiny", Chrome = "models/shiny",
Plastic = "models/debug/debugwhite", Plastic = "models/debug/debugwhite",
Glass = "phoenix_storms/mrref2", Glass = "phoenix_storms/mrref2",
@ -70,6 +70,7 @@ if SERVER then
builder:build("Body__Exterior", Vector(0), Angle(0), SCALE, COLORS.Exterior, MATERIALS.Exterior, chip(), chip()) builder:build("Body__Exterior", Vector(0), Angle(0), SCALE, COLORS.Exterior, MATERIALS.Exterior, chip(), chip())
--[[
builder:build("Body__WindowsLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip()) builder:build("Body__WindowsLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip())
builder:build("Body__TrunkLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip()) builder:build("Body__TrunkLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip())
@ -154,7 +155,7 @@ if SERVER then
builder:build("RightRearDoor__Handle__Chrome", Vector(0), Angle(0), SCALE, COLORS.DoorHandle, MATERIALS.DoorHandle, chip(), chip()) builder:build("RightRearDoor__Handle__Chrome", Vector(0), Angle(0), SCALE, COLORS.DoorHandle, MATERIALS.DoorHandle, chip(), chip())
builder:build("Glass", Vector(0), Angle(0), SCALE, COLORS.Glass, MATERIALS.Glass, chip(), chip()) builder:build("Glass", Vector(0), Angle(0), SCALE, COLORS.Glass, MATERIALS.Glass, chip(), chip())
]]--
builder:getResult() builder:getResult()
else else
PERMA.onPermissionsGained = function() PERMA.onPermissionsGained = function()

View File

@ -1,16 +1,28 @@
--@name SX 240 --@name SX 240
-- @author Koptilnya1337 --@author Koptilnya
--@server --@server
--@include /koptilnya/engine_remastered/vehicle.txt --@include /koptilnya/engine_remastered/vehicle.txt
require('/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({ Vehicle:new({
Engine = { {
Name = 'Engine',
Type = POWERTRAIN_COMPONENT.Engine,
Config = {
IdleRPM = 900, IdleRPM = 900,
MaxRPM = 7500, MaxRPM = 7500,
Inertia = 0.151, Inertia = 0.151,
StartFriction = -30, StartFriction = -10,
FrictionCoeff = 0.02, FrictionCoeff = 0.01,
LimiterDuration = 0.08, LimiterDuration = 0.08,
TorqueMap = { 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, 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,
@ -21,19 +33,33 @@ Vehicle:new({
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, 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 266.15795803778, 264.82291000376, 263.48786196974, 262.15281393572, 260.81776590169, 259.48271786767, 250.23203287321, 240.98134787874, 231.73066288428, 222.47997788981
} }
}
}, },
Clutch = { {
Name = 'Clutch',
Input = 'Engine',
Type = POWERTRAIN_COMPONENT.Clutch,
Config = {
Inertia = 0.002, Inertia = 0.002,
SlipTorque = 1000 SlipTorque = 1000
}
}, },
Gearbox = { {
Name = 'Gearbox',
Input = 'Clutch',
Type = POWERTRAIN_COMPONENT.Gearbox,
Config = {
Type = 'MANUAL', Type = 'MANUAL',
Inertia = 0.01, Inertia = 0.01,
Ratios = { 3.626, 2.200, 1.541, 1.213, 1.000, 0.767 }, Ratios = { 3.626, 2.200, 1.541, 1.213, 1.000, 0.767 },
Reverse = 3.437 Reverse = 3.437
}
}, },
Axles = {
{ {
Name = 'Axle1',
Input = 'Gearbox',
Type = POWERTRAIN_COMPONENT.Differential,
Config = {
FinalDrive = 3.392, FinalDrive = 3.392,
Inertia = 0.01, Inertia = 0.01,
BiasAB = 0.5, BiasAB = 0.5,
@ -42,5 +68,27 @@ Vehicle:new({
Stiffness = 0.1, Stiffness = 0.1,
SlipTorque = 1000 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', MANUAL = 'MANUAL',
AUTO = 'AUTOMATIC/ROBOT', AUTO = 'AUTOMATIC/ROBOT',
CVT = 'CVT' 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 -- @include ../powertrain/gearboxes/manual.txt
require('../gearboxes/manual.txt')
-- require('../gearboxes/auto.txt')
-- require('../gearboxes/cvt.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(...) return ManualGearbox:new(...)
end 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 ./powertrain_component.txt
--@include ./enums/gearbox.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.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) function Clutch:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, 'Clutch') PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = { self.wireInputs = {
Clutch = 'number' Clutch = 'number'
@ -65,3 +64,5 @@ function Clutch:forwardStep(torque, inertia)
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque) return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
end end
return Clutch

View File

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

View File

@ -1,13 +1,14 @@
--@include ./powertrain_component.txt --@include ./powertrain_component.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
require('./powertrain_component.txt') local PowertrainComponent = require('./powertrain_component.txt')
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
Engine = class('Engine', PowertrainComponent) local Engine = class('Engine', PowertrainComponent)
function Engine:initialize(config, clutch) function Engine:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, 'Engine') PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = { self.wireInputs = {
Throttle = 'number' Throttle = 'number'
@ -15,7 +16,10 @@ function Engine:initialize(config, clutch)
self.wireOutputs = { self.wireOutputs = {
Engine_RPM = 'number', Engine_RPM = 'number',
Engine_Torque = '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 self.idleRPM = config.IdleRPM or 900
@ -31,12 +35,18 @@ function Engine:initialize(config, clutch)
self.masterThrottle = 0 self.masterThrottle = 0
self.limiterTimer = 0 self.limiterTimer = 0
self.reactTorque = 0
self.returnedTorque = 0
end end
function Engine:updateWireOutputs() function Engine:updateWireOutputs()
wire.ports.Engine_RPM = self:getRPM() wire.ports.Engine_RPM = self:getRPM()
wire.ports.Engine_Torque = self.torque wire.ports.Engine_Torque = self.torque
wire.ports.Engine_MasterThrottle = self.masterThrottle 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 end
function Engine:getThrottle() function Engine:getThrottle()
@ -72,7 +82,7 @@ function Engine:_generateTorque()
return self.torque return self.torque
end end
function Engine:integrateDownwards() function Engine:forwardStep()
if self.output == nil then if self.output == nil then
local generatedTorque = self:_generateTorque() local generatedTorque = self:_generateTorque()
@ -88,8 +98,16 @@ function Engine:integrateDownwards()
local generatedTorque = self:_generateTorque() local generatedTorque = self:_generateTorque()
local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL
local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, 0) -- ??? 0 -> self.inertia 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 = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
self.angularVelocity = math.max(self.angularVelocity, 0) self.angularVelocity = math.max(self.angularVelocity, 0)
end end
return Engine

View File

@ -1,11 +1,11 @@
--@include ../powertrain_component.txt --@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) function Gearbox:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, 'Gearbox') PowertrainComponent.initialize(self, vehicle, name, config)
self.wireInputs = { self.wireInputs = {
Upshift = 'number', Upshift = 'number',
@ -53,7 +53,7 @@ function Gearbox:forwardStep(torque, inertia)
end end
if self.ratio == 0 then if self.ratio == 0 then
self.output:forwardStep(0, inertia) self.output:forwardStep(0, self.inertia)
return torque return torque
end 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 return self.output:forwardStep(self.torque, (inertia + self.inertia) * math.pow(self.ratio, 2)) / self.ratio
end end
return Gearbox

View File

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

View File

@ -1,15 +1,20 @@
--@include /koptilnya/libs/constants.txt --@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('/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) WireComponent.initialize(self)
self.vehicle = vehicle
self.name = name or 'PowertrainComponent' self.name = name or 'PowertrainComponent'
self.CONFIG = config
self.input = nil self.input = nil
self.output = nil self.output = nil
@ -18,6 +23,20 @@ function PowertrainComponent:initialize(name)
self.torque = 0 self.torque = 0
end 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() function PowertrainComponent:getRPM()
return self.angularVelocity * RAD_TO_RPM return self.angularVelocity * RAD_TO_RPM
end end
@ -47,3 +66,5 @@ function PowertrainComponent:forwardStep(torque, inertia)
return self.output:forwardStep(self.torque, self.inertia + inertia) return self.output:forwardStep(self.torque, self.inertia + inertia)
end 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 --@server
--@include ./engine.txt --@include ./enums/powertrain_component.txt
--@include ./clutch.txt --@include ./factories/powertrain_component.txt
--@include ./differential.txt
--@include ./factories/gearbox.txt
--@include /koptilnya/libs/table.txt --@include /koptilnya/libs/table.txt
--@include /koptilnya/libs/constants.txt --@include /koptilnya/libs/constants.txt
require('./engine.txt') local PowertrainComponentFactory = require('./factories/powertrain_component.txt')
require('./clutch.txt') local CustomWheel = require('./wheel/wheel.txt')
require('./differential.txt')
require('./factories/gearbox.txt') local POWERTRAIN_COMPONENT = require('./enums/powertrain_component.txt')
require('/koptilnya/libs/table.txt') require('/koptilnya/libs/table.txt')
require('/koptilnya/libs/constants.txt') require('/koptilnya/libs/constants.txt')
Vehicle = class('Vehicle') local Vehicle = class('Vehicle')
function Vehicle:initialize(config) function Vehicle:initialize(config)
if config == nil then if not Vehicle:validateConfig(config) then
throw('Vehicle config not provided') throw('Please check your vehicle configuration!')
end end
self.engine = Engine:new(config.Engine) self.config = config
self.clutch = Clutch:new(config.Clutch) self.components = {}
self.gearbox = GearboxFactory.create(config.Gearbox) self.independentComponents = {}
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:createComponents()
self:linkComponents() self:linkComponents()
self:createIO() 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() hook.add('tick', 'vehicle_update', function()
self:update() self:update()
end) end)
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() function Vehicle:createIO()
local inputs = { local inputs = {
Base = "entity" Base = 'entity',
Steer = 'number',
Brake = 'number',
Handbrake = 'number'
} }
local outputs = {} local outputs = {}
@ -52,29 +92,47 @@ function Vehicle:createIO()
wire.adjustPorts(inputs, outputs) wire.adjustPorts(inputs, outputs)
end end
function Vehicle:linkComponents() function Vehicle:getRootComponent()
self.engine.output = self.clutch return table.find(self.components, function(component)
self.clutch.output = self.gearbox return component.input == nil
self.gearbox.output = self.axles[1] end)
end
self.axles[1].input = self.gearbox function Vehicle:handleWireInput(name, value)
self.gearbox.input = self.clutch if name == 'Base' then
self.clutch.input = self.engine 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 end
function Vehicle:update() function Vehicle:update()
local base = wire.ports.Base 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 for _, component in pairs(self.components) do
component:updateWireOutputs() component:updateWireOutputs()
end end
if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then --if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then
local tiltForce = self.engine.torque * (-1 + self.engine.masterThrottle * 2) -- 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
base:applyForceOffset(base:getUp() * tiltForce, base:getMassCenter() + base:getRight() * UNITS_PER_METER) return {
base:applyForceOffset(-base:getUp() * tiltForce, base:getMassCenter() - base:getRight() * UNITS_PER_METER) Vehicle,
end POWERTRAIN_COMPONENT
end }

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() function WireComponent:initialize()
self.wireInputs = {} self.wireInputs = {}
@ -7,3 +7,5 @@ end
function WireComponent:updateWireOutputs() function WireComponent:updateWireOutputs()
end end
return WireComponent

View File

@ -13,31 +13,31 @@ local CONFIG = {
}, },
Clutch = { Clutch = {
DeviceId = 0, DeviceId = 0,
InputId = 7, InputId = 4,
Type = JOYSTICK_INPUT_TYPE.Axis, Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value) Handler = function(value)
return math.remap(value, 0, 65535, 1, 0) return math.remap(value, 0, 128, 0, 1)
end end
}, },
Throttle = { Throttle = {
DeviceId = 0, DeviceId = 0,
InputId = 1, InputId = 2,
Type = JOYSTICK_INPUT_TYPE.Axis, Type = JOYSTICK_INPUT_TYPE.Axis,
Handler = function(value) Handler = function(value)
return math.remap(value, 0, 65535, 1, 0) return math.clamp(math.remap(value, 32767, 128, 0, 1), 0, 1)
end end
}, },
Brake = { Brake = {
DeviceId = 0, DeviceId = 0,
InputId = 5, InputId = 2,
Type = JOYSTICK_INPUT_TYPE.Axis, Type = JOYSTICK_INPUT_TYPE.Axis,
Handler = function(value) Handler = function(value)
return math.remap(value, 0, 65535, 1, 0) return math.clamp(math.remap(value, 32767, 65407, 0, 1), 0, 1)
end end
}, },
Handbrake = { Handbrake = {
DeviceId = 0, DeviceId = 0,
InputId = 0, InputId = 1,
Type = JOYSTICK_INPUT_TYPE.Button, Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value) Handler = function(value)
return math.remap(value, 0, 128, 0, 1) return math.remap(value, 0, 128, 0, 1)
@ -45,7 +45,7 @@ local CONFIG = {
}, },
GearUp = { GearUp = {
DeviceId = 0, DeviceId = 0,
InputId = 4, InputId = 2,
Type = JOYSTICK_INPUT_TYPE.Button, Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value) Handler = function(value)
return math.remap(value, 0, 128, 0, 1) return math.remap(value, 0, 128, 0, 1)
@ -53,68 +53,12 @@ local CONFIG = {
}, },
GearDown = { GearDown = {
DeviceId = 0, DeviceId = 0,
InputId = 5, InputId = 0,
Type = JOYSTICK_INPUT_TYPE.Button, Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value) Handler = function(value)
return math.remap(value, 0, 128, 0, 1) return math.remap(value, 0, 128, 0, 1)
end end
}, }
Gear1 = {
DeviceId = 0,
InputId = 8,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
Gear2 = {
DeviceId = 0,
InputId = 9,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
Gear3 = {
DeviceId = 0,
InputId = 10,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
Gear4 = {
DeviceId = 0,
InputId = 11,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
Gear5 = {
DeviceId = 0,
InputId = 12,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
Gear6 = {
DeviceId = 0,
InputId = 13,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
GearR = {
DeviceId = 0,
InputId = 14,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
end
},
} }
if CLIENT and player() == owner() then if CLIENT and player() == owner() then

View File

@ -1,3 +1,5 @@
--@name koptilnya/libs/constants
NULL_ENTITY = entity(0) NULL_ENTITY = entity(0)
CURRENT_PLAYER = player() CURRENT_PLAYER = player()
OWNER = owner() OWNER = owner()
@ -7,3 +9,5 @@ RAD_TO_RPM = 9.5493
RPM_TO_RAD = 0.10472 RPM_TO_RAD = 0.10472
UNITS_PER_METER = 39.37 UNITS_PER_METER = 39.37
UNITS_TO_METERS = 0.01905 UNITS_TO_METERS = 0.01905
KG_TO_N = 9.80665
KG_TO_KN = 0.00980665

View File

@ -1,3 +1,4 @@
--@name /koptilnya/libs/perma
-- @include /koptilnya/libs/table.txt -- @include /koptilnya/libs/table.txt
-- @shared -- @shared

View File

@ -82,6 +82,8 @@ function table.find(tbl, predicate)
return field return field
end end
end end
return nil
end end
function table.reduce(tbl, reducer, init) function table.reduce(tbl, reducer, init)
@ -93,3 +95,17 @@ function table.reduce(tbl, reducer, init)
return accum return accum
end end
function table.every(tbl, predicate)
for i, field in pairs(tbl) do
if not predicate(field, i) then
return false
end
end
return true
end
function table.some(tbl, predicate)
return table.find(tbl, predicate) ~= nil
end

View File

@ -61,6 +61,14 @@ function MeshBuilder:initialize(link)
net.receive("objects", function() net.receive("objects", function()
self:_onReceiveObjects() self:_onReceiveObjects()
end) end)
hook.add("Removed", "_Removed", function()
for k, v in pairs(self._cachedMaterials) do
if type(v) == "Material" then
v:destroy()
end
end
end)
end end
function MeshBuilder:_onObjLoaded(objData) function MeshBuilder:_onObjLoaded(objData)
@ -89,8 +97,9 @@ function MeshBuilder:_onReceiveObjects()
net.readEntity(function(ent) net.readEntity(function(ent)
local holo = ent:toHologram() local holo = ent:toHologram()
local isCustomMaterial = (#table.getKeys(mat) > 1 and mat.basetexture) or isURL(mat.basetexture)
if (isURL(mat.basetexture)) then if (isCustomMaterial) then
mat = self:_createMaterial(mat.shader, mat.basetexture, mat.bumpmap, mat.options) mat = self:_createMaterial(mat.shader, mat.basetexture, mat.bumpmap, mat.options)
else else
hasErr, mat = pcall(material.load, mat.basetexture) hasErr, mat = pcall(material.load, mat.basetexture)
@ -172,8 +181,13 @@ function MeshBuilder:_createMaterial(shader, basetexture, bumpmap, options)
self:_setTexture(mat, "$bumpmap", bumpmap, options.bumpLayout) self:_setTexture(mat, "$bumpmap", bumpmap, options.bumpLayout)
end end
options.baseLayout = nil
options.bumpLayout = nil
for k, v in pairs(options) do for k, v in pairs(options) do
if type(v) == "string" then if k == "envmap" then
mat:setTexture("$envmap", v)
elseif type(v) == "string" then
mat:setString("$" .. k, v) mat:setString("$" .. k, v)
elseif type(v) == "number" then elseif type(v) == "number" then
if string.match(tostring(v), "%.") then if string.match(tostring(v), "%.") then
@ -184,12 +198,14 @@ function MeshBuilder:_createMaterial(shader, basetexture, bumpmap, options)
elseif type(v) == "nil" then elseif type(v) == "nil" then
mat:setUndefined("$" .. k, v) mat:setUndefined("$" .. k, v)
elseif type(v) == "Vector" then elseif type(v) == "Vector" then
mat:setVector("$" .. k, tostring(v)) mat:setVector("$" .. k, v)
end end
end end
self._cachedMaterials[checksum] = mat self._cachedMaterials[checksum] = mat
--mat:recompute()
return mat return mat
end end

View File

@ -3,7 +3,7 @@
-- @include /koptilnya/libs/workers.txt -- @include /koptilnya/libs/workers.txt
require("/koptilnya/libs/workers.txt") require("/koptilnya/libs/workers.txt")
local TIMEOUT = 3 local TIMEOUT = 4
MeshBuilder = class("MeshBuilder") MeshBuilder = class("MeshBuilder")

View File

@ -0,0 +1,73 @@
--@name Physics debugger
--@author Opti1337
--@shared
--@include /koptilnya/libs/entity.txt
if SERVER then
require("/koptilnya/libs/entity.txt")
wire.adjustPorts(
{
W1 = "ENTITY",
W2 = "ENTITY"
}
)
timer.create("interval", 0.08, 0, function()
local data = {}
for k, w in pairs({wire.ports.W1, wire.ports.W2}) do
if not isValid(w) then return end
local physObj = w:getPhysicsObject()
if not isValid(physObj) then return end
local v = physObj:getVelocity()
local lV = getLocalVelocity(physObj)
local m = physObj:getMass()
local f = m * (lV / 0.08)
data[k] = {
Velocity = v,
LocalVelocity = lV,
Mass = m,
Force = f
}
end
net.start("data")
net.writeString(json.encode(data))
net.send(nil, true)
end)
else
local data = {}
net.receive("data", function()
data = json.decode(net.readString())
end)
hook.add("drawhud", "_drawhud", function()
if #data < 2 then return end
render.setColor(Color(0, 255, 0))
render.drawCircle(1400, 950, 50)
render.drawSimpleText(1400, 1020, "F = " .. tostring(data[1].Force), 2, 1)
--render.drawLine(1400, 950, 1400 - data[1].Force[1] / 10000, 950 - data[1].Force[2] / 10000)
render.setColor(Color(0, 0, 255))
render.drawLine(1400, 950, 1400 - data[1].Force[2] / 10000, 950)
render.setColor(Color(255, 0, 0))
render.drawLine(1400, 950, 1400, 950 - data[1].Force[1] / 10000)
render.setColor(Color(0, 255, 0))
render.drawCircle(1820, 950, 50)
render.drawSimpleText(1820, 1020, "F = " .. tostring(data[2].Force), 2, 1)
--render.drawLine(1820, 950, 1820 - data[2].Force[1] / 10000, 950 - data[2].Force[2] / 10000)
render.setColor(Color(0, 0, 255))
render.drawLine(1820, 950, 1820 - data[1].Force[2] / 10000, 950)
render.setColor(Color(255, 0, 0))
render.drawLine(1820, 950, 1820, 950 - data[2].Force[1] / 10000)
--for k, w in pairs(data) do end
end)
end

View File

@ -0,0 +1,8 @@
--@author Koptilnya
--@server
--@include /koptilnya/libs/constants.txt
require('/koptilnya/libs/constants.txt')
Controller = class('Controller')

View File

@ -0,0 +1,9 @@
Friction = class('Friction')
function Friction:initialize()
self.force = 0
self.forceCoefficient = 1.2
self.slip = 0
self.slipCoefficient = 0.8
self.speed = 0
end

View File

@ -0,0 +1,246 @@
--@name Custom Wheel
--@author Koptilnya
--@server
--@include /koptilnya/libs/constants.txt
require('/koptilnya/libs/constants.txt')
Wheel = class('Wheel')
function Wheel:initialize(config)
config = config or {}
self.name = config.Name or 'Wheel'
self.mass = config.Mass or 20
self.radius = config.Radius or 0.25
self.width = config.Width or 0.25
self.dragTorque = config.DragTorque or 40
self.B = 10.86
self.C = 2.15
self.D = 0.933
self.E = 0.992
self.motorTorque = 0
self.brakeTorque = 0
self.steerAngle = 0
self.forward = Vector(0)
self.right = Vector(0)
self.up = Vector(0)
self.inertia = 0
self.load = 0
self.angularVelocity = 0
self.baseInertia = 0.5 * self.mass * math.pow(self.radius, 2)
self.inertia = self.baseInertia
self.entity = NULL_ENTITY
self.physObject = nil
self.hasHit = false
wire.adjustPorts(
{
[self.name] = 'entity',
Torque = 'number',
Brake = 'number'
},
{
HasHit = 'number'
}
)
hook.add('tick', 'custom_wheel_update', function()
if isValid(self.physObject) then
self:update()
end
end)
hook.add('input', 'custom_wheel_input', function(input, value)
if input == self.name then
if isValid(value) then
self.entity = value
self.entity:enableSphere(true, self.radius * UNITS_PER_METER)
self.physObject = value:getPhysicsObject()
self.physObject:setMaterial('friction_00')
self.physObject:setMass(self.mass)
self.physObject:setInertia(Vector(self.inertia))
self.physObject:enableDrag(false)
self.physObject:setDragCoefficient(0)
self.physObject:setAngleDragCoefficient(0)
else
self.entity = NULL_ENTITY
self.physObject = nil
end
end
end)
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:pacejka(slip, B, C, D, E)
local t = math.abs(slip)
return D * math.sin(C * math.atan(B * t - E * (B * t - math.atan(B * t))))
end
function Wheel:stepLongitudinal(Tm, Tb, Vx, W, Lc, R, I, kFx, kSx)
if I < 0.0001 then
I = 0.0001
end
local Winit = W
local VxAbs = math.abs(Vx)
local WAbs = math.abs(W)
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:pacejka(math.abs(Sx), self.B, self.C, self.D, self.E) * 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
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:pacejka(math.abs(Sy), self.B, self.C, self.D, self.E) * 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:update()
local externalStress = self.physObject:getStress()
local velocity = self.physObject:getVelocity() * UNITS_TO_METERS
self.hasHit = externalStress ~= 0
self.load = externalStress * KG_TO_NM
self.longitudinalLoadCoefficient = self:getLongitudinalLoadCoefficient(self.load)
self.lateralLoadCoefficient = self:getLateralLoadCoefficient(self.load)
self.forward = self.entity:getForward()
self.right = -self.entity:getRight()
local forwardSpeed = velocity:dot(self.forward)
local sideSpeed = velocity:dot(self.right)
self.motorTorque = wire.ports.Torque
self.brakeTorque = wire.ports.Brake
local W, Sx, Fx, Tcnt = self:stepLongitudinal(
self.motorTorque,
self.brakeTorque + self.dragTorque,
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.
)
if self.hasHit then
local surfaceForceVector = self.right * Fy + self.forward * Fx
self.physObject:applyForceOffset(surfaceForceVector, self.entity:getPos())
end
wire.ports.HasHit = self.hasHit and 1 or 0
end
local wheel = Wheel:new()