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
-- @name Audi A6 2012
-- @author DarkSupah
-- @author Opti1337, DarkSupah
-- @include /koptilnya/mesh_loader/builder.txt
require("/koptilnya/mesh_loader/builder.txt")
@ -11,7 +11,31 @@ local SCALE = Vector(1)
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",
Grille = "models/shiny",
WindowsOutline = "models/debug/debugwhite",
@ -94,9 +118,10 @@ if SERVER then
// Body group
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("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("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("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("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
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 SCALE = Vector(0.9)
local MATERIALS = {
Exterior = "phoenix_storms/mrref2",
Exterior = "models/debug/debugwhite",
Chrome = "models/shiny",
Plastic = "models/debug/debugwhite",
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__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())
@ -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("Glass", Vector(0), Angle(0), SCALE, COLORS.Glass, MATERIALS.Glass, chip(), chip())
]]--
builder:getResult()
else
PERMA.onPermissionsGained = function()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,20 @@
--@include /koptilnya/libs/constants.txt
--@include ./wire_component.txt
--@include ../wire_component.txt
local WireComponent = require('../wire_component.txt')
require('/koptilnya/libs/constants.txt')
require('./wire_component.txt')
PowertrainComponent = class('PowertrainComponent', WireComponent)
local PowertrainComponent = class('PowertrainComponent', WireComponent)
function PowertrainComponent:initialize(vehicle, name, config)
config = config or {}
function PowertrainComponent:initialize(name)
WireComponent.initialize(self)
self.vehicle = vehicle
self.name = name or 'PowertrainComponent'
self.CONFIG = config
self.input = nil
self.output = nil
@ -18,6 +23,20 @@ function PowertrainComponent:initialize(name)
self.torque = 0
end
function PowertrainComponent:start()
end
function PowertrainComponent:linkComponent(component)
if not component:isInstanceOf(PowertrainComponent) then
return
end
if self.output == nil then
self.output = component
component.input = self
end
end
function PowertrainComponent:getRPM()
return self.angularVelocity * RAD_TO_RPM
end
@ -47,3 +66,5 @@ function PowertrainComponent:forwardStep(torque, inertia)
return self.output:forwardStep(self.torque, self.inertia + inertia)
end
return PowertrainComponent

View File

@ -0,0 +1,122 @@
--@include ./powertrain_component.txt
--@include ../wheel/wheel.txt
--@include /koptilnya/libs/constants.txt
--@include /koptilnya/libs/entity.txt
local PowertrainComponent = require('./powertrain_component.txt')
local CustomWheel = require('../wheel/wheel.txt')
require('/koptilnya/libs/constants.txt')
require('/koptilnya/libs/entity.txt')
local Wheel = class('Wheel', PowertrainComponent)
function Wheel:initialize(vehicle, name, config)
PowertrainComponent.initialize(self, vehicle, name, config)
self.steerLock = config.SteerLock or 0
self.brakePower = config.BrakePower or 0
self.handbrakePower = config.HandbrakePower or 0
self.wireInputs = {
[self.name] = 'entity'
}
self.wireOutputs = {
[string.format('%s_RPM', self.name)] = 'number',
[string.format('%s_Mz', self.name)] = 'number',
[string.format('%s_Fz', self.name)] = 'number'
}
self.entity = config.Entity or NULL_ENTITY
self.holo = self:createHolo(self.entity)
self.customWheel = CustomWheel:new(config.CustomWheel)
hook.add('input', 'vehicle_wheel_update' .. self.name, function(name, value)
if name == self.name then
self.entity = value
self.customWheel:setEntity(value)
if isValid(self.holo) then
self.holo:remove()
end
self.holo = self:createHolo(self.entity)
if not config.Radius then
self.customWheel.radius = self:getEntityRadius()
end
end
end)
end
function Wheel:getEntityRadius()
if not isValid(self.entity) then
return 0
end
return self.entity:getModelRadius() * UNITS_TO_METERS
end
function Wheel:createHolo(entity)
if not isValid(entity) then
return NULL_ENTITY
end
local holo = holograms.create(entity:getPos(), entity:getAngles(), 'models/props_c17/pulleywheels_small01.mdl')
holo:setParent(entity)
return holo
end
function Wheel:updateWireOutputs()
wire.ports[string.format('%s_RPM', self.name)] = self.customWheel:getRPM()
wire.ports[string.format('%s_Mz', self.name)] = self.customWheel.mz
wire.ports[string.format('%s_Fz', self.name)] = self.customWheel.load
end
function Wheel:queryInertia()
return self.customWheel.inertia
end
function Wheel:queryAngularVelocity()
return self.angularVelocity
end
function Wheel:forwardStep(torque, inertia)
if not isValid(self.customWheel) then
return 0
end
self.customWheel.steerAngle = self.vehicle.steer * self.steerLock
--self.customWheel.inertia = inertia
--self.customWheel:setInertia(inertia)
self.customWheel.motorTorque = torque
self.customWheel.brakeTorque = math.max(self.brakePower * self.vehicle.brake, self.handbrakePower * self.vehicle.handbrake)
self.customWheel:update()
self.angularVelocity = self.customWheel.angularVelocity
if self.customWheel.hasHit and isValid(self.vehicle.basePhysObject) then
local surfaceForceVector = self.customWheel.right * self.customWheel.sideFriction.force + self.customWheel.forward * self.customWheel.forwardFriction.force
self.vehicle.basePhysObject:applyForceOffset(surfaceForceVector, self.entity:getPos() - Vector(0, 0, self.customWheel.radius))
end
if isValid(self.holo) then
local angles = self.holo:getAngles()
--local rot = angles:rotateAroundAxis(self.entity:getForward(), nil, 1)
local rot = angles:rotateAroundAxis(self.entity:getRight(), nil, -self.angularVelocity * TICK_INTERVAL)
--local steer = Angle():rotateAroundAxis(self.entity:getUp(), self.customWheel.steerAngle)
--local rot = self.entity:getRight():getQuaternionFromAxis(-self.angularVelocity * TICK_INTERVAL)
--local steer = self.entity:getUp():getQuaternionFromAxis(self.steerAngle)
--self.holo:setAngles(self.entity:localToWorldAngles(Angle(0, 90 - self.customWheel.steerAngle, 0)))
self.holo:setAngles(rot)
end
return self.customWheel.counterTorque
end
return Wheel

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,31 +13,31 @@ local CONFIG = {
},
Clutch = {
DeviceId = 0,
InputId = 7,
Type = JOYSTICK_INPUT_TYPE.Axis,
InputId = 4,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 65535, 1, 0)
return math.remap(value, 0, 128, 0, 1)
end
},
Throttle = {
DeviceId = 0,
InputId = 1,
InputId = 2,
Type = JOYSTICK_INPUT_TYPE.Axis,
Handler = function(value)
return math.remap(value, 0, 65535, 1, 0)
return math.clamp(math.remap(value, 32767, 128, 0, 1), 0, 1)
end
},
Brake = {
DeviceId = 0,
InputId = 5,
InputId = 2,
Type = JOYSTICK_INPUT_TYPE.Axis,
Handler = function(value)
return math.remap(value, 0, 65535, 1, 0)
return math.clamp(math.remap(value, 32767, 65407, 0, 1), 0, 1)
end
},
Handbrake = {
DeviceId = 0,
InputId = 0,
InputId = 1,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
@ -45,7 +45,7 @@ local CONFIG = {
},
GearUp = {
DeviceId = 0,
InputId = 4,
InputId = 2,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
@ -53,68 +53,12 @@ local CONFIG = {
},
GearDown = {
DeviceId = 0,
InputId = 5,
InputId = 0,
Type = JOYSTICK_INPUT_TYPE.Button,
Handler = function(value)
return math.remap(value, 0, 128, 0, 1)
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

View File

@ -1,3 +1,5 @@
--@name koptilnya/libs/constants
NULL_ENTITY = entity(0)
CURRENT_PLAYER = player()
OWNER = owner()
@ -7,3 +9,5 @@ RAD_TO_RPM = 9.5493
RPM_TO_RAD = 0.10472
UNITS_PER_METER = 39.37
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
-- @shared

View File

@ -82,6 +82,8 @@ function table.find(tbl, predicate)
return field
end
end
return nil
end
function table.reduce(tbl, reducer, init)
@ -93,3 +95,17 @@ function table.reduce(tbl, reducer, init)
return accum
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()
self:_onReceiveObjects()
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
function MeshBuilder:_onObjLoaded(objData)
@ -89,8 +97,9 @@ function MeshBuilder:_onReceiveObjects()
net.readEntity(function(ent)
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)
else
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)
end
options.baseLayout = nil
options.bumpLayout = nil
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)
elseif type(v) == "number" then
if string.match(tostring(v), "%.") then
@ -184,12 +198,14 @@ function MeshBuilder:_createMaterial(shader, basetexture, bumpmap, options)
elseif type(v) == "nil" then
mat:setUndefined("$" .. k, v)
elseif type(v) == "Vector" then
mat:setVector("$" .. k, tostring(v))
mat:setVector("$" .. k, v)
end
end
self._cachedMaterials[checksum] = mat
--mat:recompute()
return mat
end

View File

@ -3,7 +3,7 @@
-- @include /koptilnya/libs/workers.txt
require("/koptilnya/libs/workers.txt")
local TIMEOUT = 3
local TIMEOUT = 4
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()