Skip to content

Instantly share code, notes, and snippets.

@CheezusChrust
Last active October 21, 2022 19:04
Show Gist options
  • Save CheezusChrust/7ccce5f5196d3adc95ab9573009f735a to your computer and use it in GitHub Desktop.
Save CheezusChrust/7ccce5f5196d3adc95ab9573009f735a to your computer and use it in GitHub Desktop.
Spawn on a StarfallEx screen, play with the numbers inside.
--@name tools/Torque Curve Visualizer v1.1
--@author Cheezus
--@shared
if CLIENT then
local Engine = {}
Engine.name = "427 V8"
--[[
ACF3 curves:
GenericPetrol = { 0.4, 0.65, 0.85, 1, 0.9, 0.6 }
GenericDiesel = { 0.7, 0.96, 1, 0.97, 0.93, 0.82, 0.7, 0.5 }
Radial = { 0.7, 0.96, 1, 0.97, 0.93, 0.82, 0.7, 0.5 }
Turbine = { 1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1 }
Rotary = { 0.35, 0.7, 0.85, 0.95, 1, 0.9, 0.7 }
--]]
Engine.curve = { 0.4, 0.65, 0.85, 1, 0.9, 0.6 } -- Torque curve - begins at idle RPM
Engine.idle = 1100 -- Idle RPM
Engine.redline = 8800 -- Peak RPM
Engine.peakTq = 630 -- Peak torque, in Nm
local res = 32 -- Resolution of the curve - ACF3 and ACE use a res of 32
local function calcCurve(points, pos)
local count = #points
if count < 3 then
throw("Cannot calculate curve with fewer than 3 points")
end
if pos <= 0 then
return points[1]
elseif pos >= 1 then
return points[count]
end
local t = (pos * (count - 1)) % 1
local currentPoint = math.floor(pos * (count - 1) + 1)
local p0 = points[math.clamp(currentPoint - 1, 1, count - 2)]
local p1 = points[math.clamp(currentPoint, 1, count - 1)]
local p2 = points[math.clamp(currentPoint + 1, 2, count)]
local p3 = points[math.clamp(currentPoint + 2, 3, count)]
return 0.5 * ((2 * p1) +
(p2 - p0) * t +
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t ^ 2 +
(3 * p1 - p0 - 3 * p2 + p3) * t ^ 3)
end
render.createRenderTarget("rt")
local function drawToRT(f)
hook.add("renderoffscreen", "drawToRT", function()
render.selectRenderTarget("rt")
f()
render.selectRenderTarget()
hook.remove("renderoffscreen", "drawToRT")
end)
end
local function getEngineData(curve, maxTq, idle, redline)
local peakTq = 0
local peakPower = 0
local powerTable = {}
local peakTqRPM
local peakPowerRPM
local powerbandMinRPM
local powerbandMaxRPM
--Calculate peak torque/power rpm
for i = 0, res do
local rpm = i / res * redline
local perc = math.remap(rpm, idle, redline, 0, 1)
local curTq = calcCurve(curve, perc)
local power = (maxTq * curTq * rpm / 9548.8)
powerTable[i] = power
if power > peakPower then
peakPower = power
peakPowerRPM = rpm
end
if curTq > peakTq then
peakTq = curTq
peakTqRPM = rpm
end
end
--Find the bounds of the powerband
for i = 0, res do
local powerFrac = powerTable[i] / peakPower
local rpm = i / res * redline
if powerFrac > 0.9 and not powerbandMinRPM then
powerbandMinRPM = math.round(rpm / 10) * 10
end
if (powerbandMinRPM and powerFrac < 0.9 and not powerbandMaxRPM) or (i == res and not powerbandMaxRPM) then
powerbandMaxRPM = math.round(rpm / 10) * 10
end
end
return {
peakTq = peakTq * Engine.peakTq,
peakTqRPM = math.round(peakTqRPM),
peakPower = math.round(peakPower),
peakPowerRPM = math.round(peakPowerRPM),
powerbandMinRPM = powerbandMinRPM or 0,
powerbandMaxRPM = powerbandMaxRPM or 0
}
end
local bigfont = render.createFont("Trebuchet", 48, nil, true)
drawToRT(function()
render.setColor(Color(50, 50, 50))
render.drawRect(0, 512, 1024, 512)
render.setColor(Color(255, 255, 255))
render.setFont("Trebuchet24")
render.drawText(512, 4, "Simulated Dynamometer", 1)
render.drawText(512, 28, Engine.name, 1)
render.setFont(bigfont)
render.drawText(512, 440, "RPM", 1)
render.setFont("Trebuchet24")
for i = 1, 16 do
render.setColor(Color(255, 255, 255))
render.drawText((i / 17) * 1024, 490, tostring(math.floor(i / 17 * (Engine.redline - 0) + 0)), 1)
render.setColor(Color(80, 80, 80))
render.drawLine((i / 17) * 1024, 512, (i / 17) * 1024, 1024)
end
local engineData = getEngineData(Engine.curve, Engine.peakTq, Engine.idle, Engine.redline)
local scale = math.max(engineData.peakPower, Engine.peakTq) * 1.1
render.setFont(bigfont)
local str = string.format("Redline: %srpm, Idle: %srpm", Engine.redline, Engine.idle)
render.setColor(Color(255, 255, 255))
render.drawText(32, 50, str)
str = string.format("Peak Torque: %sNm/%sft-lbs @ %srpm", Engine.peakTq, math.round(Engine.peakTq * 0.7376), engineData.peakTqRPM)
render.setColor(Color(50, 100, 255))
render.drawText(32, 90, str)
str = string.format("Peak Power: %skW/%shp @ %srpm", engineData.peakPower, math.round(engineData.peakPower * 1.341), engineData.peakPowerRPM)
render.setColor(Color(255, 0, 0))
render.drawText(32, 130, str)
render.setColor(Color(255, 200, 100))
render.drawText(32, 170, "Powerband: " .. engineData.powerbandMinRPM .. " - " .. engineData.powerbandMaxRPM .. "rpm")
render.setColor(Color(255, 200, 100, 100))
render.drawRect(engineData.powerbandMinRPM / Engine.redline * 1024, 512, 2, 512)
render.drawRect(engineData.powerbandMaxRPM / Engine.redline * 1024 - 1, 512, 2, 512)
for i = 1, res do
local rpm1 = ((i - 1) / res) * Engine.redline
local rpm2 = (i / res) * Engine.redline
local perc1 = math.remap(rpm1, Engine.idle, Engine.redline, 0, 1)
local perc2 = math.remap(rpm2, Engine.idle, Engine.redline, 0, 1)
local tq1 = math.clamp(calcCurve(Engine.curve, perc1), 0, 1) * Engine.peakTq
local tq2 = math.clamp(calcCurve(Engine.curve, perc2), 0, 1) * Engine.peakTq
local kw1 = tq1 * rpm1 / 9548.8
local kw2 = tq2 * rpm2 / 9548.8
local x1 = (i - 1) / res * 1024
local x2 = i / res * 1024
local tq_y1 = 1024 - (tq1 / scale) * 512
local tq_y2 = 1024 - (tq2 / scale) * 512
local kw_y1 = 1024 - (kw1 / scale) * 512
local kw_y2 = 1024 - (kw2 / scale) * 512
render.setColor(Color(50, 100, 255))
render.drawLine(x1, tq_y1, x2, tq_y2)
render.setColor(Color(255, 0, 0))
render.drawLine(x1, kw_y1, x2, kw_y2)
end
local tq_x = (engineData.peakTqRPM / Engine.redline) * 1024
local tq_y = 1024 - (engineData.peakTq / scale) * 512
local power_x = (engineData.peakPowerRPM / Engine.redline) * 1024
local power_y = 1024 - (engineData.peakPower / scale) * 512
render.setFont("Trebuchet24")
render.setColor(Color(50, 100, 255))
render.drawCircle(tq_x, tq_y, 8)
local str = string.format("%sNm, %sft-lbs", Engine.peakTq, math.round(Engine.peakTq * 0.7376))
render.drawText(tq_x, tq_y - 32, str, 2)
render.setColor(Color(255, 0, 0))
render.drawCircle(power_x, power_y, 8)
local str = string.format("%skW, %shp", engineData.peakPower, math.round(engineData.peakPower * 1.341))
render.drawText(power_x, power_y - 32, str, 2)
render.setColor(Color(25, 25, 25, 200))
render.drawRect(0, 512, (Engine.idle / Engine.redline) * 1024, 512)
end)
hook.add("render", "", function()
render.setColor(Color(255, 255, 255))
render.setRenderTargetTexture("rt")
render.drawTexturedRect(0, 0, 512, 512)
render.setRenderTargetTexture()
end)
else
chip():isWeldedTo():linkComponent(chip())
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment