UPDATE
This commit is contained in:
parent
345e60cf1d
commit
09be0ea795
5
koptilnya/.vscode/settings.json
vendored
5
koptilnya/.vscode/settings.json
vendored
@ -1,5 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.txt": "lua"
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
|
||||
173
koptilnya/data/models/bmw_850csi_96.txt
Normal file
173
koptilnya/data/models/bmw_850csi_96.txt
Normal 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
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
types = {
|
||||
local Types = {
|
||||
MANUAL = 'MANUAL',
|
||||
AUTO = 'AUTOMATIC/ROBOT',
|
||||
CVT = 'CVT'
|
||||
}
|
||||
|
||||
return types
|
||||
return Types
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
return {
|
||||
Engine = 1,
|
||||
Clutch = 2,
|
||||
Gearbox = 3,
|
||||
Differential = 4,
|
||||
Wheel = 5
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
122
koptilnya/engine_remastered/powertrain/wheel.txt
Normal file
122
koptilnya/engine_remastered/powertrain/wheel.txt
Normal 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
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
14
koptilnya/engine_remastered/wheel/friction.txt
Normal file
14
koptilnya/engine_remastered/wheel/friction.txt
Normal 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
|
||||
18
koptilnya/engine_remastered/wheel/friction_preset.txt
Normal file
18
koptilnya/engine_remastered/wheel/friction_preset.txt
Normal 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
|
||||
313
koptilnya/engine_remastered/wheel/wheel.txt
Normal file
313
koptilnya/engine_remastered/wheel/wheel.txt
Normal 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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
--@name /koptilnya/libs/perma
|
||||
-- @include /koptilnya/libs/table.txt
|
||||
-- @shared
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
-- @include /koptilnya/libs/workers.txt
|
||||
require("/koptilnya/libs/workers.txt")
|
||||
|
||||
local TIMEOUT = 3
|
||||
local TIMEOUT = 4
|
||||
|
||||
MeshBuilder = class("MeshBuilder")
|
||||
|
||||
|
||||
73
koptilnya/physics_debugger.txt
Normal file
73
koptilnya/physics_debugger.txt
Normal 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
|
||||
8
koptilnya/wheel_controller/controller.txt
Normal file
8
koptilnya/wheel_controller/controller.txt
Normal file
@ -0,0 +1,8 @@
|
||||
--@author Koptilnya
|
||||
--@server
|
||||
--@include /koptilnya/libs/constants.txt
|
||||
|
||||
require('/koptilnya/libs/constants.txt')
|
||||
|
||||
Controller = class('Controller')
|
||||
|
||||
9
koptilnya/wheel_controller/friction.txt
Normal file
9
koptilnya/wheel_controller/friction.txt
Normal 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
|
||||
246
koptilnya/wheel_controller/wheel.txt
Normal file
246
koptilnya/wheel_controller/wheel.txt
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user