From 09be0ea795164757da2e707ae3d1070a4a9436d0 Mon Sep 17 00:00:00 2001 From: Nikita Kruglickiy Date: Mon, 22 May 2023 19:06:26 +0300 Subject: [PATCH] UPDATE --- koptilnya/.vscode/settings.json | 5 - koptilnya/data/models/audi_a6.txt | 73 ++-- koptilnya/data/models/bmw_850csi_96.txt | 173 ++++++++++ koptilnya/data/models/jdm_wheels.txt | 20 +- koptilnya/data/models/vaz_2106.txt | 11 +- .../engine_remastered/configs/new_sx240.txt | 108 ++++-- koptilnya/engine_remastered/enums/gearbox.txt | 4 +- .../enums/powertrain_component.txt | 7 + .../engine_remastered/factories/gearbox.txt | 13 +- .../factories/powertrain_component.txt | 34 ++ .../engine_remastered/gearboxes/auto.txt | 0 koptilnya/engine_remastered/gearboxes/cvt.txt | 0 .../{ => powertrain}/clutch.txt | 13 +- .../{ => powertrain}/differential.txt | 47 +-- .../{ => powertrain}/engine.txt | 32 +- .../{ => powertrain}/gearboxes/base.txt | 12 +- .../{ => powertrain}/gearboxes/manual.txt | 15 +- .../{ => powertrain}/powertrain_component.txt | 29 +- .../engine_remastered/powertrain/wheel.txt | 122 +++++++ koptilnya/engine_remastered/vehicle.txt | 128 +++++-- koptilnya/engine_remastered/wheel.txt | 97 ------ .../engine_remastered/wheel/friction.txt | 14 + .../wheel/friction_preset.txt | 18 + koptilnya/engine_remastered/wheel/wheel.txt | 313 ++++++++++++++++++ .../engine_remastered/wire_component.txt | 4 +- koptilnya/joystick_data.txt | 78 +---- koptilnya/libs/constants.txt | 4 + koptilnya/libs/perma.txt | 1 + koptilnya/libs/table.txt | 16 + koptilnya/mesh_loader/cl_builder.txt | 26 +- koptilnya/mesh_loader/sv_builder.txt | 6 +- koptilnya/physics_debugger.txt | 73 ++++ koptilnya/wheel_controller/controller.txt | 8 + koptilnya/wheel_controller/friction.txt | 9 + koptilnya/wheel_controller/wheel.txt | 246 ++++++++++++++ 35 files changed, 1423 insertions(+), 336 deletions(-) delete mode 100644 koptilnya/.vscode/settings.json create mode 100644 koptilnya/data/models/bmw_850csi_96.txt create mode 100644 koptilnya/engine_remastered/enums/powertrain_component.txt create mode 100644 koptilnya/engine_remastered/factories/powertrain_component.txt delete mode 100644 koptilnya/engine_remastered/gearboxes/auto.txt delete mode 100644 koptilnya/engine_remastered/gearboxes/cvt.txt rename koptilnya/engine_remastered/{ => powertrain}/clutch.txt (85%) rename koptilnya/engine_remastered/{ => powertrain}/differential.txt (79%) rename koptilnya/engine_remastered/{ => powertrain}/engine.txt (78%) rename koptilnya/engine_remastered/{ => powertrain}/gearboxes/base.txt (81%) rename koptilnya/engine_remastered/{ => powertrain}/gearboxes/manual.txt (78%) rename koptilnya/engine_remastered/{ => powertrain}/powertrain_component.txt (61%) create mode 100644 koptilnya/engine_remastered/powertrain/wheel.txt delete mode 100644 koptilnya/engine_remastered/wheel.txt create mode 100644 koptilnya/engine_remastered/wheel/friction.txt create mode 100644 koptilnya/engine_remastered/wheel/friction_preset.txt create mode 100644 koptilnya/engine_remastered/wheel/wheel.txt create mode 100644 koptilnya/physics_debugger.txt create mode 100644 koptilnya/wheel_controller/controller.txt create mode 100644 koptilnya/wheel_controller/friction.txt create mode 100644 koptilnya/wheel_controller/wheel.txt diff --git a/koptilnya/.vscode/settings.json b/koptilnya/.vscode/settings.json deleted file mode 100644 index bc39c59..0000000 --- a/koptilnya/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "files.associations": { - "*.txt": "lua" - } -} \ No newline at end of file diff --git a/koptilnya/data/models/audi_a6.txt b/koptilnya/data/models/audi_a6.txt index 2ff310f..a464c6e 100644 --- a/koptilnya/data/models/audi_a6.txt +++ b/koptilnya/data/models/audi_a6.txt @@ -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", @@ -89,68 +113,69 @@ local builder = {} if SERVER then builder = MeshBuilder:new(LINK) - + local this = chip() - + // Body group builder:build("Body", Vector(0), Angle(0), SCALE, Colors.Body, Materials.Body, this, this) + builder:build("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) - + builder:build("FrontShadows", Vector(0), Angle(0), SCALE, Colors.Shadow, Materials.Shadow, this, this) builder:build("RearShadow", Vector(0), Angle(0), SCALE, Colors.Shadow, Materials.Shadow, this, this) - + builder:build("Bottom", Vector(0), Angle(0), SCALE, Colors.Shadow, Materials.Shadow, this, this) - + builder:build("GrillOutline", Vector(0), Angle(0), SCALE, Colors.Grille, Materials.Grille, this, this) - + builder:build("WindowsOutline", Vector(0), Angle(0), SCALE, Colors.WindowsOutline, Materials.WindowsOutline, this, this) - + // Badges and stuff builder:build("Badge", Vector(0), Angle(0), SCALE, Colors.Badge, Materials.Badge, this, this) builder:build("RearBadges", Vector(0), Angle(0), SCALE, Colors.Badge, Materials.Badge, this, this) - + builder:build("ExhaustPipes", Vector(0), Angle(0), SCALE, Colors.ExhaustPipe, Materials.ExhaustPipe, this, this) builder:build("ExhaustTips", Vector(0), Angle(0), SCALE, Colors.ExhaustTip, Materials.ExhaustTip, this, this) - + // Rear lights builder:build("RearLightsBase", Vector(0), Angle(0), SCALE, Colors.RearLightsBase, Materials.RearLightsBase, this, this) local stopLights = builder:build("StopLights", Vector(0), Angle(0), SCALE, Colors.StopLights, Materials.StopLights, this, this) stopLights:suppressEngineLighting(true) local revLights = builder:build("ReverseLights", Vector(0), Angle(0), SCALE, Colors.ReverseLights, Materials.ReverseLights, this, this) - revLights:suppressEngineLighting(true) + revLights:suppressEngineLighting(true) //builder:build("RearLightsGlass", Vector(0), Angle(0), SCALE, Colors.RearLightsGlass, Materials.RearLightsGlass, this, this) - + // Front lights builder:build("FrontLightsShadow", Vector(0), Angle(0), SCALE, Colors.FrontLightsBase, Materials.FrontLightsBase, this, this) //builder:build("FrontLightsGlass", Vector(0), Angle(0), SCALE, Colors.FrontLightsGlass, Materials.FrontLightsGlass, this, this) local frontLights = builder:build("FrontLights", Vector(0), Angle(0), SCALE, Colors.FrontLights, Materials.FrontLights, this, this) frontLights:suppressEngineLighting(true) - + // Interior builder:build("Floor", Vector(0), Angle(0), SCALE, Colors.Floor, Materials.Floor, this, this) - + builder:build("Dashboard", Vector(0), Angle(0), SCALE, Colors.Dashboard, Materials.Dashboard, this, this) builder:build("TachoArrow", Vector(0), Angle(0), SCALE, Colors.Arrow, Materials.Arrow, this, this) builder:build("SpeedArrow", Vector(0), Angle(0), SCALE, Colors.Arrow, Materials.Arrow, this, this) builder:build("SteeringColumn", Vector(0), Angle(0), SCALE, Colors.SteeringColumn, Materials.SteeringColumn, this, this) - + builder:build("InteriorChrome", Vector(0), Angle(0), SCALE, Colors.InteriorChrome, Materials.InteriorChrome, this, this) builder:build("Interior", Vector(0), Angle(0), SCALE, Colors.Interior, Materials.Interior, this, this) - + builder:build("DoorPanels", Vector(0), Angle(0), SCALE, Colors.DoorPanels, Materials.DoorPanels, this, this) builder:build("DoorPanelsWood", Vector(0), Angle(0), SCALE, Colors.DoorPanelsLines, Materials.DoorPanelsLines, this, this) builder:build("DoorHandles", Vector(0), Angle(0), SCALE, Colors.DoorHandles, Materials.DoorHandles, this, this) - + builder:build("Seats", Vector(0), Angle(0), SCALE, Colors.Seats, Materials.Seats, this, this) builder:build("RearSeats", Vector(0), Angle(0), SCALE, Colors.Seats, Materials.Seats, this, this) - + builder:build("Torpedo", Vector(0), Angle(0), SCALE, Colors.Seats, Materials.Seats, this, this) builder:build("ShifterOutline", Vector(0), Angle(0), SCALE, Colors.ShifterOutline, Materials.ShifterOutline, this, this) - + builder:build("Mirrors", Vector(0), Angle(0), SCALE, Colors.Mirrors, Materials.Mirrors, this, this) builder:build("ProfilesButtons", Vector(0), Angle(0), SCALE, Colors.InteriorSheet, Materials.InteriorSheet, this, this) builder:build("WindowControls", Vector(0), Angle(0), SCALE, Colors.InteriorSheet, Materials.InteriorSheet, this, this) @@ -159,15 +184,15 @@ if SERVER then builder:build("ShifterDecals", Vector(0), Angle(0), SCALE, Colors.InteriorSheet, Materials.InteriorSheet, this, this) builder:build("TopControls", Vector(0), Angle(0), SCALE, Colors.InteriorSheet, Materials.InteriorSheet, this, this) builder:build("Multimedia", Vector(0), Angle(0), SCALE, Colors.InteriorSheet, Materials.InteriorSheet, this, this) - + builder:build("Seatbelts", Vector(0), Angle(0), SCALE, Colors.Seatbelts, Materials.Seatbelts, this, this) builder:build("SeatbeltsCaps", Vector(0), Angle(0), SCALE, Colors.SeatbeltsCaps, Materials.SeatbeltsCaps, this, this) builder:build("SeatbeltsLocks", Vector(0), Angle(0), SCALE, Colors.SeatbeltsLocks, Materials.SeatbeltsLocks, this, this) builder:build("RearSeatsChrome", Vector(0), Angle(0), SCALE, Colors.Interior, Materials.Interior, this, this) builder:build("SeatbeltLockButtons", Vector(0), Angle(0), SCALE, Colors.SeatbeltLockButtons, Materials.SeatbeltLockButtons, this, this) - + builder:build("DashDials", Vector(0), Angle(0), SCALE, Colors.DashDials, Materials.DashDials, this, this) - + local result = builder:getResult() else PERMA.onPermissionsGained = function() diff --git a/koptilnya/data/models/bmw_850csi_96.txt b/koptilnya/data/models/bmw_850csi_96.txt new file mode 100644 index 0000000..52044a9 --- /dev/null +++ b/koptilnya/data/models/bmw_850csi_96.txt @@ -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 diff --git a/koptilnya/data/models/jdm_wheels.txt b/koptilnya/data/models/jdm_wheels.txt index 0c7f675..e0dde4a 100644 --- a/koptilnya/data/models/jdm_wheels.txt +++ b/koptilnya/data/models/jdm_wheels.txt @@ -38,11 +38,11 @@ if SERVER then ) builder = MeshBuilder:new(LINK) - + hook.add("input", "_input", function(name, value) if name == "Wheels" then builder:reset() - + for _, wheel in pairs(wire.ports.Wheels) do builder:build("center", Vector(0, OFFSET - 0.6, 0), Angle(0), SCALE, COLORS.Center, MATERIALS.Center, wheel, wheel) builder:build("bolt", Vector(0, OFFSET- 0.6, 0), Angle(0), SCALE, COLORS.Bolt, MATERIALS.Bolt, wheel, wheel) @@ -50,9 +50,19 @@ 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() end end) @@ -61,4 +71,4 @@ else builder = MeshBuilder:new(LINK) end PERMA.build() -end \ No newline at end of file +end diff --git a/koptilnya/data/models/vaz_2106.txt b/koptilnya/data/models/vaz_2106.txt index f6d0d73..63733cc 100644 --- a/koptilnya/data/models/vaz_2106.txt +++ b/koptilnya/data/models/vaz_2106.txt @@ -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", @@ -67,9 +67,10 @@ local builder = {} if SERVER then builder = MeshBuilder:new(LINK) - + 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()) @@ -78,7 +79,7 @@ if SERVER then builder:build("Body__TrunkPinstripe__Chrome", Vector(0), Angle(0), SCALE, COLORS.Chrome, MATERIALS.Chrome, chip(), chip()) builder:build("Body__FuelCapLine__Chrome", Vector(0), Angle(0), SCALE, COLORS.Chrome, MATERIALS.Chrome, chip(), chip()) - + builder:build("Body__Under__Bottom", Vector(0), Angle(0), SCALE, COLORS.Under, MATERIALS.Under, chip(), chip()) builder:build("Body__Misc__Exterior", Vector(0), Angle(0), SCALE, COLORS.Exterior, MATERIALS.Exterior, chip(), chip()) builder:build("Interior__Seats__Seat", Vector(0), Angle(0), SCALE, COLORS.Seats, MATERIALS.Seats, chip(), chip()) @@ -152,9 +153,9 @@ if SERVER then builder:build("RightRearDoor__Glass__Glass", Vector(0), Angle(0), SCALE, COLORS.Glass, MATERIALS.Glass, chip(), chip()) builder:build("RightRearDoor__Interior__DoorInterior", Vector(0), Angle(0), SCALE, COLORS.DoorInterior, MATERIALS.DoorInterior, 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:getResult() else PERMA.onPermissionsGained = function() diff --git a/koptilnya/engine_remastered/configs/new_sx240.txt b/koptilnya/engine_remastered/configs/new_sx240.txt index fed1e6c..c4a864a 100644 --- a/koptilnya/engine_remastered/configs/new_sx240.txt +++ b/koptilnya/engine_remastered/configs/new_sx240.txt @@ -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 } }) diff --git a/koptilnya/engine_remastered/enums/gearbox.txt b/koptilnya/engine_remastered/enums/gearbox.txt index c5d2a4f..4f443a2 100644 --- a/koptilnya/engine_remastered/enums/gearbox.txt +++ b/koptilnya/engine_remastered/enums/gearbox.txt @@ -1,7 +1,7 @@ -types = { +local Types = { MANUAL = 'MANUAL', AUTO = 'AUTOMATIC/ROBOT', CVT = 'CVT' } -return types +return Types diff --git a/koptilnya/engine_remastered/enums/powertrain_component.txt b/koptilnya/engine_remastered/enums/powertrain_component.txt new file mode 100644 index 0000000..6aaf212 --- /dev/null +++ b/koptilnya/engine_remastered/enums/powertrain_component.txt @@ -0,0 +1,7 @@ +return { + Engine = 1, + Clutch = 2, + Gearbox = 3, + Differential = 4, + Wheel = 5 +} diff --git a/koptilnya/engine_remastered/factories/gearbox.txt b/koptilnya/engine_remastered/factories/gearbox.txt index e3a29cf..9d14a01 100644 --- a/koptilnya/engine_remastered/factories/gearbox.txt +++ b/koptilnya/engine_remastered/factories/gearbox.txt @@ -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 diff --git a/koptilnya/engine_remastered/factories/powertrain_component.txt b/koptilnya/engine_remastered/factories/powertrain_component.txt new file mode 100644 index 0000000..88ea464 --- /dev/null +++ b/koptilnya/engine_remastered/factories/powertrain_component.txt @@ -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 diff --git a/koptilnya/engine_remastered/gearboxes/auto.txt b/koptilnya/engine_remastered/gearboxes/auto.txt deleted file mode 100644 index e69de29..0000000 diff --git a/koptilnya/engine_remastered/gearboxes/cvt.txt b/koptilnya/engine_remastered/gearboxes/cvt.txt deleted file mode 100644 index e69de29..0000000 diff --git a/koptilnya/engine_remastered/clutch.txt b/koptilnya/engine_remastered/powertrain/clutch.txt similarity index 85% rename from koptilnya/engine_remastered/clutch.txt rename to koptilnya/engine_remastered/powertrain/clutch.txt index e02ec6e..8cebf49 100644 --- a/koptilnya/engine_remastered/clutch.txt +++ b/koptilnya/engine_remastered/powertrain/clutch.txt @@ -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 diff --git a/koptilnya/engine_remastered/differential.txt b/koptilnya/engine_remastered/powertrain/differential.txt similarity index 79% rename from koptilnya/engine_remastered/differential.txt rename to koptilnya/engine_remastered/powertrain/differential.txt index ba97feb..2f250ab 100644 --- a/koptilnya/engine_remastered/differential.txt +++ b/koptilnya/engine_remastered/powertrain/differential.txt @@ -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 diff --git a/koptilnya/engine_remastered/engine.txt b/koptilnya/engine_remastered/powertrain/engine.txt similarity index 78% rename from koptilnya/engine_remastered/engine.txt rename to koptilnya/engine_remastered/powertrain/engine.txt index ff990a2..3552d96 100644 --- a/koptilnya/engine_remastered/engine.txt +++ b/koptilnya/engine_remastered/powertrain/engine.txt @@ -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 diff --git a/koptilnya/engine_remastered/gearboxes/base.txt b/koptilnya/engine_remastered/powertrain/gearboxes/base.txt similarity index 81% rename from koptilnya/engine_remastered/gearboxes/base.txt rename to koptilnya/engine_remastered/powertrain/gearboxes/base.txt index 795c4c1..4b77170 100644 --- a/koptilnya/engine_remastered/gearboxes/base.txt +++ b/koptilnya/engine_remastered/powertrain/gearboxes/base.txt @@ -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 diff --git a/koptilnya/engine_remastered/gearboxes/manual.txt b/koptilnya/engine_remastered/powertrain/gearboxes/manual.txt similarity index 78% rename from koptilnya/engine_remastered/gearboxes/manual.txt rename to koptilnya/engine_remastered/powertrain/gearboxes/manual.txt index 1a5e3be..97d5961 100644 --- a/koptilnya/engine_remastered/gearboxes/manual.txt +++ b/koptilnya/engine_remastered/powertrain/gearboxes/manual.txt @@ -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 diff --git a/koptilnya/engine_remastered/powertrain_component.txt b/koptilnya/engine_remastered/powertrain/powertrain_component.txt similarity index 61% rename from koptilnya/engine_remastered/powertrain_component.txt rename to koptilnya/engine_remastered/powertrain/powertrain_component.txt index 7580a77..27f0b6f 100644 --- a/koptilnya/engine_remastered/powertrain_component.txt +++ b/koptilnya/engine_remastered/powertrain/powertrain_component.txt @@ -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 diff --git a/koptilnya/engine_remastered/powertrain/wheel.txt b/koptilnya/engine_remastered/powertrain/wheel.txt new file mode 100644 index 0000000..d26d798 --- /dev/null +++ b/koptilnya/engine_remastered/powertrain/wheel.txt @@ -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 diff --git a/koptilnya/engine_remastered/vehicle.txt b/koptilnya/engine_remastered/vehicle.txt index 9816c21..5b976ad 100644 --- a/koptilnya/engine_remastered/vehicle.txt +++ b/koptilnya/engine_remastered/vehicle.txt @@ -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 +} diff --git a/koptilnya/engine_remastered/wheel.txt b/koptilnya/engine_remastered/wheel.txt deleted file mode 100644 index 32c07eb..0000000 --- a/koptilnya/engine_remastered/wheel.txt +++ /dev/null @@ -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 diff --git a/koptilnya/engine_remastered/wheel/friction.txt b/koptilnya/engine_remastered/wheel/friction.txt new file mode 100644 index 0000000..0319c47 --- /dev/null +++ b/koptilnya/engine_remastered/wheel/friction.txt @@ -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 diff --git a/koptilnya/engine_remastered/wheel/friction_preset.txt b/koptilnya/engine_remastered/wheel/friction_preset.txt new file mode 100644 index 0000000..0d77870 --- /dev/null +++ b/koptilnya/engine_remastered/wheel/friction_preset.txt @@ -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 diff --git a/koptilnya/engine_remastered/wheel/wheel.txt b/koptilnya/engine_remastered/wheel/wheel.txt new file mode 100644 index 0000000..e854d4f --- /dev/null +++ b/koptilnya/engine_remastered/wheel/wheel.txt @@ -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 diff --git a/koptilnya/engine_remastered/wire_component.txt b/koptilnya/engine_remastered/wire_component.txt index 8276421..0ee3eef 100644 --- a/koptilnya/engine_remastered/wire_component.txt +++ b/koptilnya/engine_remastered/wire_component.txt @@ -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 diff --git a/koptilnya/joystick_data.txt b/koptilnya/joystick_data.txt index a01b865..68a2b6a 100644 --- a/koptilnya/joystick_data.txt +++ b/koptilnya/joystick_data.txt @@ -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 diff --git a/koptilnya/libs/constants.txt b/koptilnya/libs/constants.txt index 8124b78..590b6cd 100644 --- a/koptilnya/libs/constants.txt +++ b/koptilnya/libs/constants.txt @@ -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 diff --git a/koptilnya/libs/perma.txt b/koptilnya/libs/perma.txt index ef2f561..9d6e183 100644 --- a/koptilnya/libs/perma.txt +++ b/koptilnya/libs/perma.txt @@ -1,3 +1,4 @@ +--@name /koptilnya/libs/perma -- @include /koptilnya/libs/table.txt -- @shared diff --git a/koptilnya/libs/table.txt b/koptilnya/libs/table.txt index ec4dbed..b20adb0 100644 --- a/koptilnya/libs/table.txt +++ b/koptilnya/libs/table.txt @@ -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 diff --git a/koptilnya/mesh_loader/cl_builder.txt b/koptilnya/mesh_loader/cl_builder.txt index 8bb9585..b4f51f2 100644 --- a/koptilnya/mesh_loader/cl_builder.txt +++ b/koptilnya/mesh_loader/cl_builder.txt @@ -30,7 +30,7 @@ function MeshBuilder:initialize(link) net.receive("initialized", function() net.readEntity(function(ply) notification.addProgress(tostring(ply:getUserID()), string.format("%s is loading mesh", ply:getName())) - end) + end) end) net.receive("obj_parsed", function() @@ -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) @@ -144,7 +153,7 @@ function MeshBuilder:_setTexture(mat, name, texture, layoutOptions) local y = layoutOptions.y or 0 local w = layoutOptions.w or 1024 local h = layoutOptions.h or 1024 - + layout(x, y, w, h) end) else @@ -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 diff --git a/koptilnya/mesh_loader/sv_builder.txt b/koptilnya/mesh_loader/sv_builder.txt index 3491000..8551aa2 100644 --- a/koptilnya/mesh_loader/sv_builder.txt +++ b/koptilnya/mesh_loader/sv_builder.txt @@ -3,7 +3,7 @@ -- @include /koptilnya/libs/workers.txt require("/koptilnya/libs/workers.txt") -local TIMEOUT = 3 +local TIMEOUT = 4 MeshBuilder = class("MeshBuilder") @@ -94,7 +94,7 @@ function MeshBuilder:build(name, pos, ang, scale, color, mat, parent, relativeTo end table.insert(self._objects, {name = name, mat = mat, holo = holo}) - + return holo end @@ -104,7 +104,7 @@ function MeshBuilder:getResult() else timer.simple(TIMEOUT, function() self._timeoutPassed = true - + self:_sendObjects(self._players) end) end diff --git a/koptilnya/physics_debugger.txt b/koptilnya/physics_debugger.txt new file mode 100644 index 0000000..81f5654 --- /dev/null +++ b/koptilnya/physics_debugger.txt @@ -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 diff --git a/koptilnya/wheel_controller/controller.txt b/koptilnya/wheel_controller/controller.txt new file mode 100644 index 0000000..ebed576 --- /dev/null +++ b/koptilnya/wheel_controller/controller.txt @@ -0,0 +1,8 @@ +--@author Koptilnya +--@server +--@include /koptilnya/libs/constants.txt + +require('/koptilnya/libs/constants.txt') + +Controller = class('Controller') + diff --git a/koptilnya/wheel_controller/friction.txt b/koptilnya/wheel_controller/friction.txt new file mode 100644 index 0000000..2f5224b --- /dev/null +++ b/koptilnya/wheel_controller/friction.txt @@ -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 diff --git a/koptilnya/wheel_controller/wheel.txt b/koptilnya/wheel_controller/wheel.txt new file mode 100644 index 0000000..8e5282e --- /dev/null +++ b/koptilnya/wheel_controller/wheel.txt @@ -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()