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
|
-- @shared
|
||||||
-- @name Audi A6 2012
|
-- @name Audi A6 2012
|
||||||
-- @author DarkSupah
|
-- @author Opti1337, DarkSupah
|
||||||
-- @include /koptilnya/mesh_loader/builder.txt
|
-- @include /koptilnya/mesh_loader/builder.txt
|
||||||
require("/koptilnya/mesh_loader/builder.txt")
|
require("/koptilnya/mesh_loader/builder.txt")
|
||||||
|
|
||||||
@ -11,7 +11,31 @@ local SCALE = Vector(1)
|
|||||||
|
|
||||||
local Materials =
|
local Materials =
|
||||||
{
|
{
|
||||||
Body = "phoenix_storms/fender_white",
|
Body = "models/debug/debugwhite",
|
||||||
|
Doors = {
|
||||||
|
basetexture = "https://i.imgur.com/VfIk2D6.png",
|
||||||
|
options = {
|
||||||
|
flags = 134219776,
|
||||||
|
color = Vector(0.7969166636467, 0.7969166636467, 0.73720490932465),
|
||||||
|
envmap = "env_cubemap",
|
||||||
|
envmaptint = Vector(0.069727085530758, 0.069727085530758, 0.069727085530758),
|
||||||
|
envmapsaturation = 0.69999998807907,
|
||||||
|
phong = 1,
|
||||||
|
phongexponent = 10.0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BonnetLivery = {
|
||||||
|
basetexture = "https://i.imgur.com/MR4z42U.png",
|
||||||
|
options = {
|
||||||
|
flags = 0x0100,
|
||||||
|
baseLayout = {
|
||||||
|
x = 170,
|
||||||
|
y = 575,
|
||||||
|
w = 200,
|
||||||
|
h = 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Shadow = "models/debug/debugwhite",
|
Shadow = "models/debug/debugwhite",
|
||||||
Grille = "models/shiny",
|
Grille = "models/shiny",
|
||||||
WindowsOutline = "models/debug/debugwhite",
|
WindowsOutline = "models/debug/debugwhite",
|
||||||
@ -94,9 +118,10 @@ if SERVER then
|
|||||||
|
|
||||||
// Body group
|
// Body group
|
||||||
builder:build("Body", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
builder:build("Body", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
||||||
|
builder:build("Bonnet", Vector(0), Angle(0), SCALE + Vector(0.002), Color(255, 255, 255), Materials.BonnetLivery, this, this)
|
||||||
builder:build("Bonnet", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
builder:build("Bonnet", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
||||||
builder:build("Trunk", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
builder:build("Trunk", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
||||||
builder:build("Doors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
builder:build("Doors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Doors, this, this)
|
||||||
builder:build("FrontBumper", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
builder:build("FrontBumper", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
||||||
builder:build("BodyMirrors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
builder:build("BodyMirrors", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this)
|
||||||
|
|
||||||
|
|||||||
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("plastic", Vector(0, OFFSET, 0), Angle(0), SCALE, COLORS.Plastic, MATERIALS.Plastic, wheel, wheel)
|
||||||
builder:build("rim1", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.RimAround, MATERIALS.Rim, wheel, wheel)
|
builder:build("rim1", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.RimAround, MATERIALS.Rim, wheel, wheel)
|
||||||
builder:build("rimAround", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.Rim, MATERIALS.RimAround, wheel, wheel)
|
builder:build("rimAround", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), COLORS.Rim, MATERIALS.RimAround, wheel, wheel)
|
||||||
builder:build("tyre", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), Color(255, 255, 255), "sprops/textures/sprops_rubber", wheel, wheel)
|
builder:build("tyre", Vector(0, OFFSET, 0), Angle(0), SCALE - Vector(0, 0.1, 0), Color(255, 255, 255), {
|
||||||
|
basetexture = "phoenix_storms/car_tire",
|
||||||
|
bumpmap = "phoenix_storms/tire_bump",
|
||||||
|
options = {
|
||||||
|
envmap = "env_cubemap",
|
||||||
|
envmaptint = Vector(0.45345649123192, 0.45345649123192, 0.45345649123192),
|
||||||
|
flags = 138414080,
|
||||||
|
phong = 1,
|
||||||
|
phongexponent = 10.0
|
||||||
|
}
|
||||||
|
}, wheel, wheel)
|
||||||
end
|
end
|
||||||
|
|
||||||
builder:getResult()
|
builder:getResult()
|
||||||
|
|||||||
@ -6,7 +6,7 @@ require("/koptilnya/mesh_loader/builder.txt")
|
|||||||
local LINK = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/vaz2106.obj"
|
local LINK = "https://raw.githubusercontent.com/koptilnya/gmod-data/main/vaz2106.obj"
|
||||||
local SCALE = Vector(0.9)
|
local SCALE = Vector(0.9)
|
||||||
local MATERIALS = {
|
local MATERIALS = {
|
||||||
Exterior = "phoenix_storms/mrref2",
|
Exterior = "models/debug/debugwhite",
|
||||||
Chrome = "models/shiny",
|
Chrome = "models/shiny",
|
||||||
Plastic = "models/debug/debugwhite",
|
Plastic = "models/debug/debugwhite",
|
||||||
Glass = "phoenix_storms/mrref2",
|
Glass = "phoenix_storms/mrref2",
|
||||||
@ -70,6 +70,7 @@ if SERVER then
|
|||||||
|
|
||||||
builder:build("Body__Exterior", Vector(0), Angle(0), SCALE, COLORS.Exterior, MATERIALS.Exterior, chip(), chip())
|
builder:build("Body__Exterior", Vector(0), Angle(0), SCALE, COLORS.Exterior, MATERIALS.Exterior, chip(), chip())
|
||||||
|
|
||||||
|
--[[
|
||||||
builder:build("Body__WindowsLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip())
|
builder:build("Body__WindowsLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip())
|
||||||
builder:build("Body__TrunkLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip())
|
builder:build("Body__TrunkLine__Rubber", Vector(0), Angle(0), SCALE, COLORS.Insulation, MATERIALS.Insulation, chip(), chip())
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ if SERVER then
|
|||||||
builder:build("RightRearDoor__Handle__Chrome", Vector(0), Angle(0), SCALE, COLORS.DoorHandle, MATERIALS.DoorHandle, chip(), chip())
|
builder:build("RightRearDoor__Handle__Chrome", Vector(0), Angle(0), SCALE, COLORS.DoorHandle, MATERIALS.DoorHandle, chip(), chip())
|
||||||
|
|
||||||
builder:build("Glass", Vector(0), Angle(0), SCALE, COLORS.Glass, MATERIALS.Glass, chip(), chip())
|
builder:build("Glass", Vector(0), Angle(0), SCALE, COLORS.Glass, MATERIALS.Glass, chip(), chip())
|
||||||
|
]]--
|
||||||
builder:getResult()
|
builder:getResult()
|
||||||
else
|
else
|
||||||
PERMA.onPermissionsGained = function()
|
PERMA.onPermissionsGained = function()
|
||||||
|
|||||||
@ -1,16 +1,28 @@
|
|||||||
--@name SX 240
|
--@name SX 240
|
||||||
-- @author Koptilnya1337
|
--@author Koptilnya
|
||||||
--@server
|
--@server
|
||||||
--@include /koptilnya/engine_remastered/vehicle.txt
|
--@include /koptilnya/engine_remastered/vehicle.txt
|
||||||
require('/koptilnya/engine_remastered/vehicle.txt')
|
|
||||||
|
local Vehicle, POWERTRAIN_COMPONENT = unpack(require('/koptilnya/engine_remastered/vehicle.txt'))
|
||||||
|
|
||||||
|
|
||||||
|
local WheelConfig = {
|
||||||
|
BrakePower = 800,
|
||||||
|
CustomWheel = {}
|
||||||
|
}
|
||||||
|
local FrontWheelsConfig = table.merge(table.copy(WheelConfig), { SteerLock = 10 })
|
||||||
|
local RearWheelsConfig = table.merge(table.copy(WheelConfig), { HandbrakePower = 2000 })
|
||||||
|
|
||||||
Vehicle:new({
|
Vehicle:new({
|
||||||
Engine = {
|
{
|
||||||
|
Name = 'Engine',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Engine,
|
||||||
|
Config = {
|
||||||
IdleRPM = 900,
|
IdleRPM = 900,
|
||||||
MaxRPM = 7500,
|
MaxRPM = 7500,
|
||||||
Inertia = 0.151,
|
Inertia = 0.151,
|
||||||
StartFriction = -30,
|
StartFriction = -10,
|
||||||
FrictionCoeff = 0.02,
|
FrictionCoeff = 0.01,
|
||||||
LimiterDuration = 0.08,
|
LimiterDuration = 0.08,
|
||||||
TorqueMap = {
|
TorqueMap = {
|
||||||
118.89918444138, 122.0751393736, 125.25109430583, 128.42704923806, 131.60300417029, 134.77895910251, 137.95491403474, 141.13086896697, 144.3068238992, 147.48277883143, 150.65873376365, 153.83468869588, 157.01064362811, 160.18659856034, 163.36255349256,
|
118.89918444138, 122.0751393736, 125.25109430583, 128.42704923806, 131.60300417029, 134.77895910251, 137.95491403474, 141.13086896697, 144.3068238992, 147.48277883143, 150.65873376365, 153.83468869588, 157.01064362811, 160.18659856034, 163.36255349256,
|
||||||
@ -21,19 +33,33 @@ Vehicle:new({
|
|||||||
270.43722869263, 271.35167349511, 272.26611829758, 273.18056310006, 274.09500790254, 275.00945270501, 275.92389750749, 276.83834230997, 275.50329427594, 274.16824624192, 272.8331982079, 271.49815017388, 270.16310213985, 268.82805410583, 267.49300607181,
|
270.43722869263, 271.35167349511, 272.26611829758, 273.18056310006, 274.09500790254, 275.00945270501, 275.92389750749, 276.83834230997, 275.50329427594, 274.16824624192, 272.8331982079, 271.49815017388, 270.16310213985, 268.82805410583, 267.49300607181,
|
||||||
266.15795803778, 264.82291000376, 263.48786196974, 262.15281393572, 260.81776590169, 259.48271786767, 250.23203287321, 240.98134787874, 231.73066288428, 222.47997788981
|
266.15795803778, 264.82291000376, 263.48786196974, 262.15281393572, 260.81776590169, 259.48271786767, 250.23203287321, 240.98134787874, 231.73066288428, 222.47997788981
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Clutch = {
|
{
|
||||||
|
Name = 'Clutch',
|
||||||
|
Input = 'Engine',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Clutch,
|
||||||
|
Config = {
|
||||||
Inertia = 0.002,
|
Inertia = 0.002,
|
||||||
SlipTorque = 1000
|
SlipTorque = 1000
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Gearbox = {
|
{
|
||||||
|
Name = 'Gearbox',
|
||||||
|
Input = 'Clutch',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Gearbox,
|
||||||
|
Config = {
|
||||||
Type = 'MANUAL',
|
Type = 'MANUAL',
|
||||||
Inertia = 0.01,
|
Inertia = 0.01,
|
||||||
Ratios = { 3.626, 2.200, 1.541, 1.213, 1.000, 0.767 },
|
Ratios = { 3.626, 2.200, 1.541, 1.213, 1.000, 0.767 },
|
||||||
Reverse = 3.437
|
Reverse = 3.437
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Axles = {
|
|
||||||
{
|
{
|
||||||
|
Name = 'Axle1',
|
||||||
|
Input = 'Gearbox',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Differential,
|
||||||
|
Config = {
|
||||||
FinalDrive = 3.392,
|
FinalDrive = 3.392,
|
||||||
Inertia = 0.01,
|
Inertia = 0.01,
|
||||||
BiasAB = 0.5,
|
BiasAB = 0.5,
|
||||||
@ -42,5 +68,27 @@ Vehicle:new({
|
|||||||
Stiffness = 0.1,
|
Stiffness = 0.1,
|
||||||
SlipTorque = 1000
|
SlipTorque = 1000
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name = 'WheelFL',
|
||||||
|
Input = 'Axle1',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Wheel,
|
||||||
|
Config = FrontWheelsConfig
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name = 'WheelFR',
|
||||||
|
Input = 'Axle1',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Wheel,
|
||||||
|
Config = FrontWheelsConfig
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name = 'WheelRL',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Wheel,
|
||||||
|
Config = RearWheelsConfig
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name = 'WheelRR',
|
||||||
|
Type = POWERTRAIN_COMPONENT.Wheel,
|
||||||
|
Config = RearWheelsConfig
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
types = {
|
local Types = {
|
||||||
MANUAL = 'MANUAL',
|
MANUAL = 'MANUAL',
|
||||||
AUTO = 'AUTOMATIC/ROBOT',
|
AUTO = 'AUTOMATIC/ROBOT',
|
||||||
CVT = 'CVT'
|
CVT = 'CVT'
|
||||||
}
|
}
|
||||||
|
|
||||||
return types
|
return Types
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
return {
|
||||||
|
Engine = 1,
|
||||||
|
Clutch = 2,
|
||||||
|
Gearbox = 3,
|
||||||
|
Differential = 4,
|
||||||
|
Wheel = 5
|
||||||
|
}
|
||||||
@ -1,10 +1,11 @@
|
|||||||
-- @include ../gearboxes/manual.txt
|
-- @include ../powertrain/gearboxes/manual.txt
|
||||||
require('../gearboxes/manual.txt')
|
|
||||||
-- require('../gearboxes/auto.txt')
|
|
||||||
-- require('../gearboxes/cvt.txt')
|
|
||||||
|
|
||||||
GearboxFactory = class('GearboxFactory')
|
local ManualGearbox = require('../powertrain/gearboxes/manual.txt')
|
||||||
|
|
||||||
function GearboxFactory.create(...)
|
local GearboxFactory = class('GearboxFactory')
|
||||||
|
|
||||||
|
function GearboxFactory.static:create(...)
|
||||||
return ManualGearbox:new(...)
|
return ManualGearbox:new(...)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return GearboxFactory
|
||||||
|
|||||||
@ -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 ./powertrain_component.txt
|
||||||
--@include ./enums/gearbox.txt
|
|
||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
|
|
||||||
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
require('./powertrain_component.txt')
|
|
||||||
local gearboxTypes = require('./enums/gearbox.txt')
|
|
||||||
|
|
||||||
Clutch = class('Clutch', PowertrainComponent)
|
local Clutch = class('Clutch', PowertrainComponent)
|
||||||
|
|
||||||
function Clutch:initialize(config)
|
function Clutch:initialize(vehicle, name, config)
|
||||||
PowertrainComponent.initialize(self, 'Clutch')
|
PowertrainComponent.initialize(self, vehicle, name, config)
|
||||||
|
|
||||||
self.wireInputs = {
|
self.wireInputs = {
|
||||||
Clutch = 'number'
|
Clutch = 'number'
|
||||||
@ -65,3 +64,5 @@ function Clutch:forwardStep(torque, inertia)
|
|||||||
|
|
||||||
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
|
return math.clamp(returnTorque, -self.slipTorque, self.slipTorque)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return Clutch
|
||||||
@ -1,22 +1,21 @@
|
|||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
--@include ./powertrain_component.txt
|
--@include ./powertrain_component.txt
|
||||||
--@include ./wheel.txt
|
|
||||||
|
|
||||||
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
local WheelComponent = require('./wheel.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
require('./powertrain_component.txt')
|
|
||||||
require('./wheel.txt')
|
|
||||||
|
|
||||||
Differential = class('Differential', PowertrainComponent)
|
local Differential = class('Differential', PowertrainComponent)
|
||||||
|
|
||||||
function Differential:initialize(config, order)
|
function Differential:initialize(vehicle, name, config)
|
||||||
PowertrainComponent.initialize(self, 'Axle')
|
PowertrainComponent.initialize(self, vehicle, name, config)
|
||||||
|
|
||||||
self.wireOutputs = {
|
self.wireOutputs = {
|
||||||
Diff_Torque = 'number'
|
Diff_Torque = 'number'
|
||||||
}
|
}
|
||||||
|
|
||||||
local prefix = 'Axle' .. (order or '') .. '_'
|
|
||||||
|
|
||||||
self.finalDrive = config.FinalDrive or 4
|
self.finalDrive = config.FinalDrive or 4
|
||||||
self.inertia = config.Inertia or 0.2
|
self.inertia = config.Inertia or 0.2
|
||||||
self.biasAB = config.Bias or 0.5
|
self.biasAB = config.Bias or 0.5
|
||||||
@ -25,20 +24,20 @@ function Differential:initialize(config, order)
|
|||||||
self.preload = config.Preload or 10
|
self.preload = config.Preload or 10
|
||||||
self.stiffness = config.Stiffness or 0.1
|
self.stiffness = config.Stiffness or 0.1
|
||||||
self.slipTorque = config.SlipTorque or 1000
|
self.slipTorque = config.SlipTorque or 1000
|
||||||
|
end
|
||||||
|
|
||||||
local wheelConfig = {
|
function Differential:linkComponent(component)
|
||||||
CalculateInertia = true
|
if not component:isInstanceOf(PowertrainComponent) then
|
||||||
}
|
return
|
||||||
self.outputA = Wheel:new(wheelConfig, prefix .. 'Left')
|
end
|
||||||
self.outputB = Wheel:new(table.merge(wheelConfig, { Direction = -1 }), prefix .. 'Right')
|
|
||||||
|
|
||||||
self.outputA.input = self
|
if self.outputA == nil then
|
||||||
self.outputB.input = self
|
self.outputA = component
|
||||||
|
component.input = self
|
||||||
table.merge(self.wireInputs, self.outputA.wireInputs)
|
elseif self.outputB == nil then
|
||||||
table.merge(self.wireInputs, self.outputB.wireInputs)
|
self.outputB = component
|
||||||
table.merge(self.wireOutputs, self.outputA.wireOutputs)
|
component.input = self
|
||||||
table.merge(self.wireOutputs, self.outputB.wireOutputs)
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Differential:updateWireOutputs()
|
function Differential:updateWireOutputs()
|
||||||
@ -58,7 +57,7 @@ function Differential:queryInertia()
|
|||||||
return self.inertia
|
return self.inertia
|
||||||
end
|
end
|
||||||
|
|
||||||
return self.inertia + self.outputA:queryInertia() + self.outputB:queryInertia()
|
return self.inertia + (self.outputA:queryInertia() + self.outputB:queryInertia()) / math.pow(self.finalDrive, 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Differential:queryAngularVelocity(angularVelocity)
|
function Differential:queryAngularVelocity(angularVelocity)
|
||||||
@ -100,8 +99,8 @@ function Differential:forwardStep(torque, inertia)
|
|||||||
self.slipTorque
|
self.slipTorque
|
||||||
)
|
)
|
||||||
|
|
||||||
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 + aI)
|
tqA = self.outputA:forwardStep(tqA, inertia * 0.5 * math.pow(self.finalDrive, 2) + aI) / self.finalDrive
|
||||||
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 + bI)
|
tqB = self.outputB:forwardStep(tqB, inertia * 0.5 * math.pow(self.finalDrive, 2) + bI) / self.finalDrive
|
||||||
|
|
||||||
return (tqA + tqB) / self.finalDrive
|
return (tqA + tqB) / self.finalDrive
|
||||||
end
|
end
|
||||||
@ -156,3 +155,5 @@ function Differential:_HLSDTorqueSplit(tq, aW, bW, aI, bI, biasAB, preload, stif
|
|||||||
|
|
||||||
return tq * (1 - biasAB) - dTq, tq * biasAB + dTq
|
return tq * (1 - biasAB) - dTq, tq * biasAB + dTq
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return Differential
|
||||||
@ -1,13 +1,14 @@
|
|||||||
--@include ./powertrain_component.txt
|
--@include ./powertrain_component.txt
|
||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
|
|
||||||
require('./powertrain_component.txt')
|
local PowertrainComponent = require('./powertrain_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
|
|
||||||
Engine = class('Engine', PowertrainComponent)
|
local Engine = class('Engine', PowertrainComponent)
|
||||||
|
|
||||||
function Engine:initialize(config, clutch)
|
function Engine:initialize(vehicle, name, config)
|
||||||
PowertrainComponent.initialize(self, 'Engine')
|
PowertrainComponent.initialize(self, vehicle, name, config)
|
||||||
|
|
||||||
self.wireInputs = {
|
self.wireInputs = {
|
||||||
Throttle = 'number'
|
Throttle = 'number'
|
||||||
@ -15,7 +16,10 @@ function Engine:initialize(config, clutch)
|
|||||||
self.wireOutputs = {
|
self.wireOutputs = {
|
||||||
Engine_RPM = 'number',
|
Engine_RPM = 'number',
|
||||||
Engine_Torque = 'number',
|
Engine_Torque = 'number',
|
||||||
Engine_MasterThrottle = 'number'
|
Engine_MasterThrottle = 'number',
|
||||||
|
Engine_ReactTorque = 'number',
|
||||||
|
Engine_ReturnedTorque = 'number',
|
||||||
|
Engine_FinalTorque = 'number'
|
||||||
}
|
}
|
||||||
|
|
||||||
self.idleRPM = config.IdleRPM or 900
|
self.idleRPM = config.IdleRPM or 900
|
||||||
@ -31,12 +35,18 @@ function Engine:initialize(config, clutch)
|
|||||||
self.masterThrottle = 0
|
self.masterThrottle = 0
|
||||||
|
|
||||||
self.limiterTimer = 0
|
self.limiterTimer = 0
|
||||||
|
|
||||||
|
self.reactTorque = 0
|
||||||
|
self.returnedTorque = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
function Engine:updateWireOutputs()
|
function Engine:updateWireOutputs()
|
||||||
wire.ports.Engine_RPM = self:getRPM()
|
wire.ports.Engine_RPM = self:getRPM()
|
||||||
wire.ports.Engine_Torque = self.torque
|
wire.ports.Engine_Torque = self.torque
|
||||||
wire.ports.Engine_MasterThrottle = self.masterThrottle
|
wire.ports.Engine_MasterThrottle = self.masterThrottle
|
||||||
|
wire.ports.Engine_ReactTorque = self.reactTorque
|
||||||
|
wire.ports.Engine_ReturnedTorque = self.returnedTorque
|
||||||
|
wire.ports.Engine_FinalTorque = self.finalTorque
|
||||||
end
|
end
|
||||||
|
|
||||||
function Engine:getThrottle()
|
function Engine:getThrottle()
|
||||||
@ -72,7 +82,7 @@ function Engine:_generateTorque()
|
|||||||
return self.torque
|
return self.torque
|
||||||
end
|
end
|
||||||
|
|
||||||
function Engine:integrateDownwards()
|
function Engine:forwardStep()
|
||||||
if self.output == nil then
|
if self.output == nil then
|
||||||
local generatedTorque = self:_generateTorque()
|
local generatedTorque = self:_generateTorque()
|
||||||
|
|
||||||
@ -88,8 +98,16 @@ function Engine:integrateDownwards()
|
|||||||
local generatedTorque = self:_generateTorque()
|
local generatedTorque = self:_generateTorque()
|
||||||
local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL
|
local reactTorque = (targetW - self.angularVelocity) * self.inertia / TICK_INTERVAL
|
||||||
local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, 0) -- ??? 0 -> self.inertia
|
local returnedTorque = self.output:forwardStep(generatedTorque - reactTorque, 0) -- ??? 0 -> self.inertia
|
||||||
local finalTorque = generatedTorque + returnedTorque + reactTorque
|
|
||||||
|
self.reactTorque = reactTorque
|
||||||
|
self.returnedTorque = returnedTorque
|
||||||
|
|
||||||
|
local finalTorque = generatedTorque + reactTorque + returnedTorque
|
||||||
|
|
||||||
|
self.finalTorque = finalTorque
|
||||||
|
|
||||||
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
|
self.angularVelocity = self.angularVelocity + finalTorque / inertiaSum * TICK_INTERVAL
|
||||||
self.angularVelocity = math.max(self.angularVelocity, 0)
|
self.angularVelocity = math.max(self.angularVelocity, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return Engine
|
||||||
@ -1,11 +1,11 @@
|
|||||||
--@include ../powertrain_component.txt
|
--@include ../powertrain_component.txt
|
||||||
|
|
||||||
require('../powertrain_component.txt')
|
local PowertrainComponent = require('../powertrain_component.txt')
|
||||||
|
|
||||||
Gearbox = class('Gearbox', PowertrainComponent)
|
local Gearbox = class('Gearbox', PowertrainComponent)
|
||||||
|
|
||||||
function Gearbox:initialize(config)
|
function Gearbox:initialize(vehicle, name, config)
|
||||||
PowertrainComponent.initialize(self, 'Gearbox')
|
PowertrainComponent.initialize(self, vehicle, name, config)
|
||||||
|
|
||||||
self.wireInputs = {
|
self.wireInputs = {
|
||||||
Upshift = 'number',
|
Upshift = 'number',
|
||||||
@ -53,7 +53,7 @@ function Gearbox:forwardStep(torque, inertia)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if self.ratio == 0 then
|
if self.ratio == 0 then
|
||||||
self.output:forwardStep(0, inertia)
|
self.output:forwardStep(0, self.inertia)
|
||||||
|
|
||||||
return torque
|
return torque
|
||||||
end
|
end
|
||||||
@ -62,3 +62,5 @@ function Gearbox:forwardStep(torque, inertia)
|
|||||||
|
|
||||||
return self.output:forwardStep(self.torque, (inertia + self.inertia) * math.pow(self.ratio, 2)) / self.ratio
|
return self.output:forwardStep(self.torque, (inertia + self.inertia) * math.pow(self.ratio, 2)) / self.ratio
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return Gearbox
|
||||||
@ -1,13 +1,14 @@
|
|||||||
--@include /koptilnya/libs/watcher.txt
|
--@include /koptilnya/libs/watcher.txt
|
||||||
--@include ./base.txt
|
--@include ./base.txt
|
||||||
|
|
||||||
|
local BaseGearbox = require('./base.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/watcher.txt')
|
require('/koptilnya/libs/watcher.txt')
|
||||||
require('./base.txt')
|
|
||||||
|
|
||||||
ManualGearbox = class('ManualGearbox', Gearbox)
|
local ManualGearbox = class('ManualGearbox', BaseGearbox)
|
||||||
|
|
||||||
function ManualGearbox:initialize(config)
|
function ManualGearbox:initialize(vehicle, name, config)
|
||||||
Gearbox.initialize(self, config)
|
BaseGearbox.initialize(self, vehicle, name, config)
|
||||||
|
|
||||||
table.merge(self.wireOutputs, {
|
table.merge(self.wireOutputs, {
|
||||||
Gearbox_Gear = 'number'
|
Gearbox_Gear = 'number'
|
||||||
@ -35,7 +36,7 @@ function ManualGearbox:initialize(config)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ManualGearbox:updateWireOutputs()
|
function ManualGearbox:updateWireOutputs()
|
||||||
Gearbox.updateWireOutputs(self)
|
BaseGearbox.updateWireOutputs(self)
|
||||||
|
|
||||||
wire.ports.Gearbox_Gear = self.gear
|
wire.ports.Gearbox_Gear = self.gear
|
||||||
end
|
end
|
||||||
@ -62,9 +63,11 @@ function ManualGearbox:shift(dir)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ManualGearbox:forwardStep(torque, inertia)
|
function ManualGearbox:forwardStep(torque, inertia)
|
||||||
local result = Gearbox.forwardStep(self, torque, inertia)
|
local result = BaseGearbox.forwardStep(self, torque, inertia)
|
||||||
|
|
||||||
self.shiftWatcher()
|
self.shiftWatcher()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return ManualGearbox
|
||||||
@ -1,15 +1,20 @@
|
|||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
--@include ./wire_component.txt
|
--@include ../wire_component.txt
|
||||||
|
|
||||||
|
local WireComponent = require('../wire_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
require('./wire_component.txt')
|
|
||||||
|
|
||||||
PowertrainComponent = class('PowertrainComponent', WireComponent)
|
local PowertrainComponent = class('PowertrainComponent', WireComponent)
|
||||||
|
|
||||||
|
function PowertrainComponent:initialize(vehicle, name, config)
|
||||||
|
config = config or {}
|
||||||
|
|
||||||
function PowertrainComponent:initialize(name)
|
|
||||||
WireComponent.initialize(self)
|
WireComponent.initialize(self)
|
||||||
|
|
||||||
|
self.vehicle = vehicle
|
||||||
self.name = name or 'PowertrainComponent'
|
self.name = name or 'PowertrainComponent'
|
||||||
|
self.CONFIG = config
|
||||||
self.input = nil
|
self.input = nil
|
||||||
self.output = nil
|
self.output = nil
|
||||||
|
|
||||||
@ -18,6 +23,20 @@ function PowertrainComponent:initialize(name)
|
|||||||
self.torque = 0
|
self.torque = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function PowertrainComponent:start()
|
||||||
|
end
|
||||||
|
|
||||||
|
function PowertrainComponent:linkComponent(component)
|
||||||
|
if not component:isInstanceOf(PowertrainComponent) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.output == nil then
|
||||||
|
self.output = component
|
||||||
|
component.input = self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function PowertrainComponent:getRPM()
|
function PowertrainComponent:getRPM()
|
||||||
return self.angularVelocity * RAD_TO_RPM
|
return self.angularVelocity * RAD_TO_RPM
|
||||||
end
|
end
|
||||||
@ -47,3 +66,5 @@ function PowertrainComponent:forwardStep(torque, inertia)
|
|||||||
|
|
||||||
return self.output:forwardStep(self.torque, self.inertia + inertia)
|
return self.output:forwardStep(self.torque, self.inertia + inertia)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return PowertrainComponent
|
||||||
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
|
--@server
|
||||||
--@include ./engine.txt
|
--@include ./enums/powertrain_component.txt
|
||||||
--@include ./clutch.txt
|
--@include ./factories/powertrain_component.txt
|
||||||
--@include ./differential.txt
|
|
||||||
--@include ./factories/gearbox.txt
|
|
||||||
--@include /koptilnya/libs/table.txt
|
--@include /koptilnya/libs/table.txt
|
||||||
--@include /koptilnya/libs/constants.txt
|
--@include /koptilnya/libs/constants.txt
|
||||||
|
|
||||||
require('./engine.txt')
|
local PowertrainComponentFactory = require('./factories/powertrain_component.txt')
|
||||||
require('./clutch.txt')
|
local CustomWheel = require('./wheel/wheel.txt')
|
||||||
require('./differential.txt')
|
|
||||||
require('./factories/gearbox.txt')
|
local POWERTRAIN_COMPONENT = require('./enums/powertrain_component.txt')
|
||||||
|
|
||||||
require('/koptilnya/libs/table.txt')
|
require('/koptilnya/libs/table.txt')
|
||||||
require('/koptilnya/libs/constants.txt')
|
require('/koptilnya/libs/constants.txt')
|
||||||
|
|
||||||
Vehicle = class('Vehicle')
|
local Vehicle = class('Vehicle')
|
||||||
|
|
||||||
function Vehicle:initialize(config)
|
function Vehicle:initialize(config)
|
||||||
if config == nil then
|
if not Vehicle:validateConfig(config) then
|
||||||
throw('Vehicle config not provided')
|
throw('Please check your vehicle configuration!')
|
||||||
end
|
end
|
||||||
|
|
||||||
self.engine = Engine:new(config.Engine)
|
self.config = config
|
||||||
self.clutch = Clutch:new(config.Clutch)
|
self.components = {}
|
||||||
self.gearbox = GearboxFactory.create(config.Gearbox)
|
self.independentComponents = {}
|
||||||
self.axles = table.map(config.Axles, function(config, idx)
|
|
||||||
return Differential:new(config, idx)
|
|
||||||
end)
|
|
||||||
|
|
||||||
self.components = { self.engine, self.clutch, self.gearbox }
|
|
||||||
table.add(self.components, self.axles)
|
|
||||||
|
|
||||||
|
self:createComponents()
|
||||||
self:linkComponents()
|
self:linkComponents()
|
||||||
self:createIO()
|
self:createIO()
|
||||||
|
|
||||||
|
self.rootComponent = self:getRootComponent()
|
||||||
|
self.steer = 0
|
||||||
|
self.brake = 0
|
||||||
|
self.handbrake = 0
|
||||||
|
|
||||||
|
hook.add('input', 'vehicle_wire_input', function(name, value)
|
||||||
|
self:handleWireInput(name, value)
|
||||||
|
end)
|
||||||
|
|
||||||
hook.add('tick', 'vehicle_update', function()
|
hook.add('tick', 'vehicle_update', function()
|
||||||
self:update()
|
self:update()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Vehicle.static:validateConfig(config)
|
||||||
|
return type(config) == 'table'
|
||||||
|
end
|
||||||
|
|
||||||
|
function Vehicle:getComponentByName(name)
|
||||||
|
return table.find(self.components, function(component)
|
||||||
|
return component.name == name
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Vehicle:createComponents()
|
||||||
|
for _, componentConfig in pairs(self.config) do
|
||||||
|
local component = PowertrainComponentFactory:create(self, componentConfig.Type, componentConfig.Name, componentConfig.Config)
|
||||||
|
|
||||||
|
table.insert(self.components, component)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Vehicle:linkComponents()
|
||||||
|
for _, componentConfig in pairs(self.config) do
|
||||||
|
local component = self:getComponentByName(componentConfig.Name)
|
||||||
|
|
||||||
|
if componentConfig.Type == POWERTRAIN_COMPONENT.Wheel and componentConfig.Input == nil then
|
||||||
|
table.insert(self.independentComponents, component)
|
||||||
|
else
|
||||||
|
local inputComponent = self:getComponentByName(componentConfig.Input)
|
||||||
|
|
||||||
|
if inputComponent ~= nil then
|
||||||
|
inputComponent:linkComponent(component)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function Vehicle:createIO()
|
function Vehicle:createIO()
|
||||||
local inputs = {
|
local inputs = {
|
||||||
Base = "entity"
|
Base = 'entity',
|
||||||
|
Steer = 'number',
|
||||||
|
Brake = 'number',
|
||||||
|
Handbrake = 'number'
|
||||||
}
|
}
|
||||||
local outputs = {}
|
local outputs = {}
|
||||||
|
|
||||||
@ -52,29 +92,47 @@ function Vehicle:createIO()
|
|||||||
wire.adjustPorts(inputs, outputs)
|
wire.adjustPorts(inputs, outputs)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Vehicle:linkComponents()
|
function Vehicle:getRootComponent()
|
||||||
self.engine.output = self.clutch
|
return table.find(self.components, function(component)
|
||||||
self.clutch.output = self.gearbox
|
return component.input == nil
|
||||||
self.gearbox.output = self.axles[1]
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
self.axles[1].input = self.gearbox
|
function Vehicle:handleWireInput(name, value)
|
||||||
self.gearbox.input = self.clutch
|
if name == 'Base' then
|
||||||
self.clutch.input = self.engine
|
self.base = value
|
||||||
|
self.basePhysObject = isValid(value) and value:getPhysicsObject() or nil
|
||||||
|
elseif name == 'Steer' then
|
||||||
|
self.steer = value
|
||||||
|
elseif name == 'Brake' then
|
||||||
|
self.brake = value
|
||||||
|
elseif name == 'Handbrake' then
|
||||||
|
self.handbrake = value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Vehicle:update()
|
function Vehicle:update()
|
||||||
local base = wire.ports.Base
|
local base = wire.ports.Base
|
||||||
|
|
||||||
self.engine:integrateDownwards()
|
self.rootComponent:forwardStep()
|
||||||
|
|
||||||
|
for _, component in pairs(self.independentComponents) do
|
||||||
|
component:forwardStep(0, component:queryInertia())
|
||||||
|
end
|
||||||
|
|
||||||
for _, component in pairs(self.components) do
|
for _, component in pairs(self.components) do
|
||||||
component:updateWireOutputs()
|
component:updateWireOutputs()
|
||||||
end
|
end
|
||||||
|
|
||||||
if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then
|
--if isValid(base) and (self.clutch:getPress() == 1 or self.gearbox.ratio == 0) then
|
||||||
local tiltForce = self.engine.torque * (-1 + self.engine.masterThrottle * 2)
|
-- local tiltForce = self.engine.torque * (-1 + self.engine.masterThrottle * 2)
|
||||||
|
--
|
||||||
|
-- base:applyForceOffset(base:getUp() * tiltForce, base:getMassCenter() + base:getRight() * UNITS_PER_METER)
|
||||||
|
-- base:applyForceOffset(-base:getUp() * tiltForce, base:getMassCenter() - base:getRight() * UNITS_PER_METER)
|
||||||
|
--end
|
||||||
|
end
|
||||||
|
|
||||||
base:applyForceOffset(base:getUp() * tiltForce, base:getMassCenter() + base:getRight() * UNITS_PER_METER)
|
return {
|
||||||
base:applyForceOffset(-base:getUp() * tiltForce, base:getMassCenter() - base:getRight() * UNITS_PER_METER)
|
Vehicle,
|
||||||
end
|
POWERTRAIN_COMPONENT
|
||||||
end
|
}
|
||||||
|
|||||||
@ -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()
|
function WireComponent:initialize()
|
||||||
self.wireInputs = {}
|
self.wireInputs = {}
|
||||||
@ -7,3 +7,5 @@ end
|
|||||||
|
|
||||||
function WireComponent:updateWireOutputs()
|
function WireComponent:updateWireOutputs()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return WireComponent
|
||||||
|
|||||||
@ -13,31 +13,31 @@ local CONFIG = {
|
|||||||
},
|
},
|
||||||
Clutch = {
|
Clutch = {
|
||||||
DeviceId = 0,
|
DeviceId = 0,
|
||||||
InputId = 7,
|
InputId = 4,
|
||||||
Type = JOYSTICK_INPUT_TYPE.Axis,
|
Type = JOYSTICK_INPUT_TYPE.Button,
|
||||||
Handler = function(value)
|
Handler = function(value)
|
||||||
return math.remap(value, 0, 65535, 1, 0)
|
return math.remap(value, 0, 128, 0, 1)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
Throttle = {
|
Throttle = {
|
||||||
DeviceId = 0,
|
DeviceId = 0,
|
||||||
InputId = 1,
|
InputId = 2,
|
||||||
Type = JOYSTICK_INPUT_TYPE.Axis,
|
Type = JOYSTICK_INPUT_TYPE.Axis,
|
||||||
Handler = function(value)
|
Handler = function(value)
|
||||||
return math.remap(value, 0, 65535, 1, 0)
|
return math.clamp(math.remap(value, 32767, 128, 0, 1), 0, 1)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
Brake = {
|
Brake = {
|
||||||
DeviceId = 0,
|
DeviceId = 0,
|
||||||
InputId = 5,
|
InputId = 2,
|
||||||
Type = JOYSTICK_INPUT_TYPE.Axis,
|
Type = JOYSTICK_INPUT_TYPE.Axis,
|
||||||
Handler = function(value)
|
Handler = function(value)
|
||||||
return math.remap(value, 0, 65535, 1, 0)
|
return math.clamp(math.remap(value, 32767, 65407, 0, 1), 0, 1)
|
||||||
end
|
end
|
||||||
},
|
},
|
||||||
Handbrake = {
|
Handbrake = {
|
||||||
DeviceId = 0,
|
DeviceId = 0,
|
||||||
InputId = 0,
|
InputId = 1,
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
Type = JOYSTICK_INPUT_TYPE.Button,
|
||||||
Handler = function(value)
|
Handler = function(value)
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
return math.remap(value, 0, 128, 0, 1)
|
||||||
@ -45,7 +45,7 @@ local CONFIG = {
|
|||||||
},
|
},
|
||||||
GearUp = {
|
GearUp = {
|
||||||
DeviceId = 0,
|
DeviceId = 0,
|
||||||
InputId = 4,
|
InputId = 2,
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
Type = JOYSTICK_INPUT_TYPE.Button,
|
||||||
Handler = function(value)
|
Handler = function(value)
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
return math.remap(value, 0, 128, 0, 1)
|
||||||
@ -53,68 +53,12 @@ local CONFIG = {
|
|||||||
},
|
},
|
||||||
GearDown = {
|
GearDown = {
|
||||||
DeviceId = 0,
|
DeviceId = 0,
|
||||||
InputId = 5,
|
InputId = 0,
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
Type = JOYSTICK_INPUT_TYPE.Button,
|
||||||
Handler = function(value)
|
Handler = function(value)
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
return math.remap(value, 0, 128, 0, 1)
|
||||||
end
|
end
|
||||||
},
|
}
|
||||||
Gear1 = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 8,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
Gear2 = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 9,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
Gear3 = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 10,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
Gear4 = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 11,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
Gear5 = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 12,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
Gear6 = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 13,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
GearR = {
|
|
||||||
DeviceId = 0,
|
|
||||||
InputId = 14,
|
|
||||||
Type = JOYSTICK_INPUT_TYPE.Button,
|
|
||||||
Handler = function(value)
|
|
||||||
return math.remap(value, 0, 128, 0, 1)
|
|
||||||
end
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if CLIENT and player() == owner() then
|
if CLIENT and player() == owner() then
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
--@name koptilnya/libs/constants
|
||||||
|
|
||||||
NULL_ENTITY = entity(0)
|
NULL_ENTITY = entity(0)
|
||||||
CURRENT_PLAYER = player()
|
CURRENT_PLAYER = player()
|
||||||
OWNER = owner()
|
OWNER = owner()
|
||||||
@ -7,3 +9,5 @@ RAD_TO_RPM = 9.5493
|
|||||||
RPM_TO_RAD = 0.10472
|
RPM_TO_RAD = 0.10472
|
||||||
UNITS_PER_METER = 39.37
|
UNITS_PER_METER = 39.37
|
||||||
UNITS_TO_METERS = 0.01905
|
UNITS_TO_METERS = 0.01905
|
||||||
|
KG_TO_N = 9.80665
|
||||||
|
KG_TO_KN = 0.00980665
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
--@name /koptilnya/libs/perma
|
||||||
-- @include /koptilnya/libs/table.txt
|
-- @include /koptilnya/libs/table.txt
|
||||||
-- @shared
|
-- @shared
|
||||||
|
|
||||||
|
|||||||
@ -82,6 +82,8 @@ function table.find(tbl, predicate)
|
|||||||
return field
|
return field
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function table.reduce(tbl, reducer, init)
|
function table.reduce(tbl, reducer, init)
|
||||||
@ -93,3 +95,17 @@ function table.reduce(tbl, reducer, init)
|
|||||||
|
|
||||||
return accum
|
return accum
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function table.every(tbl, predicate)
|
||||||
|
for i, field in pairs(tbl) do
|
||||||
|
if not predicate(field, i) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.some(tbl, predicate)
|
||||||
|
return table.find(tbl, predicate) ~= nil
|
||||||
|
end
|
||||||
|
|||||||
@ -61,6 +61,14 @@ function MeshBuilder:initialize(link)
|
|||||||
net.receive("objects", function()
|
net.receive("objects", function()
|
||||||
self:_onReceiveObjects()
|
self:_onReceiveObjects()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
hook.add("Removed", "_Removed", function()
|
||||||
|
for k, v in pairs(self._cachedMaterials) do
|
||||||
|
if type(v) == "Material" then
|
||||||
|
v:destroy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function MeshBuilder:_onObjLoaded(objData)
|
function MeshBuilder:_onObjLoaded(objData)
|
||||||
@ -89,8 +97,9 @@ function MeshBuilder:_onReceiveObjects()
|
|||||||
|
|
||||||
net.readEntity(function(ent)
|
net.readEntity(function(ent)
|
||||||
local holo = ent:toHologram()
|
local holo = ent:toHologram()
|
||||||
|
local isCustomMaterial = (#table.getKeys(mat) > 1 and mat.basetexture) or isURL(mat.basetexture)
|
||||||
|
|
||||||
if (isURL(mat.basetexture)) then
|
if (isCustomMaterial) then
|
||||||
mat = self:_createMaterial(mat.shader, mat.basetexture, mat.bumpmap, mat.options)
|
mat = self:_createMaterial(mat.shader, mat.basetexture, mat.bumpmap, mat.options)
|
||||||
else
|
else
|
||||||
hasErr, mat = pcall(material.load, mat.basetexture)
|
hasErr, mat = pcall(material.load, mat.basetexture)
|
||||||
@ -172,8 +181,13 @@ function MeshBuilder:_createMaterial(shader, basetexture, bumpmap, options)
|
|||||||
self:_setTexture(mat, "$bumpmap", bumpmap, options.bumpLayout)
|
self:_setTexture(mat, "$bumpmap", bumpmap, options.bumpLayout)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
options.baseLayout = nil
|
||||||
|
options.bumpLayout = nil
|
||||||
|
|
||||||
for k, v in pairs(options) do
|
for k, v in pairs(options) do
|
||||||
if type(v) == "string" then
|
if k == "envmap" then
|
||||||
|
mat:setTexture("$envmap", v)
|
||||||
|
elseif type(v) == "string" then
|
||||||
mat:setString("$" .. k, v)
|
mat:setString("$" .. k, v)
|
||||||
elseif type(v) == "number" then
|
elseif type(v) == "number" then
|
||||||
if string.match(tostring(v), "%.") then
|
if string.match(tostring(v), "%.") then
|
||||||
@ -184,12 +198,14 @@ function MeshBuilder:_createMaterial(shader, basetexture, bumpmap, options)
|
|||||||
elseif type(v) == "nil" then
|
elseif type(v) == "nil" then
|
||||||
mat:setUndefined("$" .. k, v)
|
mat:setUndefined("$" .. k, v)
|
||||||
elseif type(v) == "Vector" then
|
elseif type(v) == "Vector" then
|
||||||
mat:setVector("$" .. k, tostring(v))
|
mat:setVector("$" .. k, v)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
self._cachedMaterials[checksum] = mat
|
self._cachedMaterials[checksum] = mat
|
||||||
|
|
||||||
|
--mat:recompute()
|
||||||
|
|
||||||
return mat
|
return mat
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
-- @include /koptilnya/libs/workers.txt
|
-- @include /koptilnya/libs/workers.txt
|
||||||
require("/koptilnya/libs/workers.txt")
|
require("/koptilnya/libs/workers.txt")
|
||||||
|
|
||||||
local TIMEOUT = 3
|
local TIMEOUT = 4
|
||||||
|
|
||||||
MeshBuilder = class("MeshBuilder")
|
MeshBuilder = class("MeshBuilder")
|
||||||
|
|
||||||
|
|||||||
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