new powertrain смерть чуркам

This commit is contained in:
Valera
2025-06-13 21:16:20 +07:00
parent ba9afba4d1
commit 964b46e1c5
15 changed files with 868 additions and 416 deletions

View File

@@ -0,0 +1,49 @@
using Sandbox;
using System;
namespace VeloX.Powertrain;
public class Clutch : PowertrainComponent
{
[Property] public override float Inertia { get; set; } = 0.002f;
[Property] public float SlipTorque { get; set; } = 1000f;
public float Pressing { get; set; } = 1; // todo
public override float QueryInertia()
{
if ( !HasOutput )
return Inertia;
return Inertia + Output.QueryInertia() * Pressing;
}
public override float QueryAngularVelocity( float angularVelocity )
{
this.angularVelocity = angularVelocity;
if ( !HasOutput )
return angularVelocity;
float outputW = Output.QueryAngularVelocity( angularVelocity ) * Pressing;
float inputW = angularVelocity * (1 - Pressing);
return outputW + inputW;
}
public override float ForwardStep( float torque, float inertia )
{
if ( !HasOutput )
return torque;
Torque = Math.Clamp( torque, -SlipTorque, SlipTorque );
Torque = torque * (1 - (1 - MathF.Pow( Pressing, 0.3f )));
float returnTorque = Output.ForwardStep( Torque, inertia * Pressing + Inertia ) * Pressing;
return Math.Clamp( returnTorque, -SlipTorque, SlipTorque );
}
}

View File

@@ -0,0 +1,79 @@
using Sandbox;
using System;
namespace VeloX.Powertrain;
[Category( "VeloX/Powertrain/Gearbox" )]
public abstract class BaseDifferential : PowertrainComponent
{
[Property] public float FinalDrive { get; set; } = 3.392f;
[Property] public override float Inertia { get; set; } = 0.01f;
//[Property] public float CoastRamp { get; set; } = 1f;
//[Property] public float PowerRamp { get; set; } = 1f;
//[Property] public float Stiffness { get; set; } = 0.1f;
//[Property] public float SlipTorque { get; set; } = 0f;
//[Property] public float SteerLock { get; set; } = 45f;
/// <summary>
/// The PowertrainComponent this component will output to.
/// </summary>
[Property]
public PowertrainComponent OutputB
{
get => _outputb;
set
{
if ( value == this )
{
_outputb = null;
return;
}
_outputb = value;
if ( _outputb != null )
_outputb.Input = this;
}
}
private PowertrainComponent _outputb;
public override bool HasOutput => Output.IsValid() && OutputB.IsValid();
public override float QueryAngularVelocity( float angularVelocity )
{
this.angularVelocity = angularVelocity;
if ( !HasOutput )
return angularVelocity;
float aW = Output.QueryAngularVelocity( angularVelocity );
float bW = OutputB.QueryAngularVelocity( angularVelocity );
return (aW + bW) * FinalDrive * 0.5f;
}
public abstract void SplitTorque( float aW, float bW, float aI, float bI, out float tqA, out float tqB );
public override float ForwardStep( float torque, float inertia )
{
if ( !HasOutput )
return torque;
float aW = Output.QueryAngularVelocity( angularVelocity );
float bW = OutputB.QueryAngularVelocity( angularVelocity );
float aI = Output.QueryInertia();
float bI = OutputB.QueryInertia();
Torque = torque * FinalDrive;
SplitTorque( aW, bW, aI, bI, out float tqA, out float tqB );
tqA = Output.ForwardStep( tqA, inertia * 0.5f * MathF.Pow( FinalDrive, 2 ) + aI );
tqB = OutputB.ForwardStep( tqB, inertia * 0.5f * MathF.Pow( FinalDrive, 2 ) + bI );
return tqA + tqB;
}
}

View File

@@ -0,0 +1,13 @@
using Sandbox;
namespace VeloX.Powertrain;
public class OpenDifferential : BaseDifferential
{
[Property] public float BiasAB { get; set; } = 0.5f;
public override void SplitTorque( float aW, float bW, float aI, float bI, out float tqA, out float tqB )
{
tqA = Torque * (1 - BiasAB);
tqB = Torque * BiasAB;
}
}

View File

@@ -0,0 +1,101 @@
using Sandbox;
using System;
namespace VeloX.Powertrain;
public class Engine : PowertrainComponent
{
[Property, Group( "Settings" )] public float IdleRPM { get; set; } = 900f;
[Property, Group( "Settings" )] public float MaxRPM { get; set; } = 7000f;
[Property, Group( "Settings" )] public override float Inertia { get; set; } = 0.151f;
[Property, Group( "Settings" )] public float StartFriction { get; set; } = 50f;
[Property, Group( "Settings" )] public float FrictionCoeff { get; set; } = 0.02f;
[Property, Group( "Settings" )] public float LimiterDuration { get; set; } = 0.05f;
[Property, Group( "Settings" )] public Curve TorqueMap { get; set; }
[Property, Group( "Settings" )] public EngineStream Stream { get; set; }
[Sync] public float Throttle { get; internal set; }
[Property] public bool IsRedlining => !limiterTimer;
[Property] public float RPMPercent => Math.Clamp( (RPM - IdleRPM) / (MaxRPM - IdleRPM), 0, 1 );
private float masterThrottle;
private TimeUntil limiterTimer;
private float finalTorque;
private EngineStreamPlayer StreamPlayer;
protected override void OnStart()
{
base.OnStart();
StreamPlayer = new( Stream );
}
private float GenerateTorque()
{
float throttle = Throttle;
float rpm = RPM;
float friction = StartFriction - rpm * FrictionCoeff;
float maxInitialTorque = TorqueMap.Evaluate( RPMPercent ) - friction;
float idleFadeStart = Math.Clamp( MathX.Remap( rpm, IdleRPM - 300, IdleRPM, 1, 0 ), 0, 1 );
float idleFadeEnd = Math.Clamp( MathX.Remap( rpm, IdleRPM, IdleRPM + 600, 1, 0 ), 0, 1 );
float additionalEnergySupply = idleFadeEnd * (-friction / maxInitialTorque) + idleFadeStart;
if ( rpm > MaxRPM )
{
throttle = 0;
limiterTimer = LimiterDuration;
}
else if ( !limiterTimer )
throttle = 0;
masterThrottle = Math.Clamp( additionalEnergySupply + throttle, 0, 1 );
float realInitialTorque = maxInitialTorque * masterThrottle;
Torque = realInitialTorque + friction;
return Torque;
}
public override float ForwardStep( float _, float __ )
{
if ( !HasOutput )
{
angularVelocity += GenerateTorque() / Inertia * Time.Delta;
angularVelocity = Math.Max( angularVelocity, 0 );
return 0;
}
float outputInertia = Output.QueryInertia();
float inertiaSum = Inertia + outputInertia;
float outputW = Output.QueryAngularVelocity( angularVelocity );
float targetW = Inertia / inertiaSum * angularVelocity + outputInertia / inertiaSum * outputW;
float generatedTorque = GenerateTorque();
float reactTorque = (targetW - angularVelocity) * Inertia / Time.Delta;
float returnedTorque = Output.ForwardStep( generatedTorque - reactTorque, Inertia );
finalTorque = generatedTorque + reactTorque + returnedTorque;
angularVelocity += finalTorque / inertiaSum * Time.Delta;
angularVelocity = Math.Max( angularVelocity, 0 );
UpdateStream();
return finalTorque;
}
private void UpdateStream()
{
if ( StreamPlayer is null )
return;
StreamPlayer.Throttle = Throttle;
StreamPlayer.RPMPercent = RPMPercent;
StreamPlayer.EngineState = EngineState.Running;
StreamPlayer.IsRedlining = IsRedlining;
StreamPlayer.Update( Time.Delta, WorldPosition );
}
}

View File

@@ -0,0 +1,45 @@
using Sandbox;
using System;
namespace VeloX.Powertrain;
[Category( "VeloX/Powertrain/Gearbox" )]
public abstract class BaseGearbox : PowertrainComponent
{
[Property] public override float Inertia { get; set; } = 0.01f;
protected float ratio;
public override float QueryInertia()
{
if ( !HasOutput || ratio == 0 )
return Inertia;
return Inertia + Output.QueryInertia() / MathF.Pow( ratio, 2 );
}
public override float QueryAngularVelocity( float angularVelocity )
{
this.angularVelocity = angularVelocity;
if ( !HasOutput || ratio == 0 )
return angularVelocity;
return Output.QueryAngularVelocity( angularVelocity ) * ratio;
}
public override float ForwardStep( float torque, float inertia )
{
Torque = torque * ratio;
if ( !HasOutput )
return torque;
if ( ratio == 0 )
{
Output.ForwardStep( 0, Inertia * 0.5f );
return torque;
}
return Output.ForwardStep( Torque, (inertia + Inertia) * MathF.Pow( ratio, 2 ) ) / ratio;
}
}

View File

@@ -0,0 +1,52 @@
using Sandbox;
namespace VeloX.Powertrain;
public class ManualGearbox : BaseGearbox
{
[Property] public float[] Ratios { get; set; } = [3.626f, 2.200f, 1.541f, 1.213f, 1.000f, 0.767f];
[Property] public float Reverse { get; set; } = 3.4f;
[Property, InputAction] public string ForwardAction { get; set; } = "Attack1";
[Property, InputAction] public string BackwardAction { get; set; } = "Attack2";
private int gear;
protected void SetGear( int gear )
{
if ( gear < -1 || gear >= Ratios.Length )
return;
this.gear = gear;
RecalcRatio();
}
private void RecalcRatio()
{
if ( gear == -1 )
ratio = -Reverse;
else if ( gear == 0 )
ratio = 0;
else
ratio = Ratios[gear - 1];
}
public void Shift( int dir )
{
SetGear( gear + dir );
}
private void InputResolve()
{
if ( Sandbox.Input.Pressed( ForwardAction ) )
Shift( 1 );
else if ( Sandbox.Input.Pressed( BackwardAction ) )
Shift( -1 );
}
public override float ForwardStep( float torque, float inertia )
{
InputResolve();
return base.ForwardStep( torque, inertia );
}
}

View File

@@ -0,0 +1,29 @@
using Sandbox;
using System;
namespace VeloX.Powertrain;
public class PowerWheel : PowertrainComponent
{
[Property] public VeloXWheel Wheel { get; set; }
public override float QueryInertia() => Wheel.Inertia;
public override float QueryAngularVelocity( float angularVelocity )
{
return Wheel.AngularVelocity;
}
public override float ForwardStep( float torque, float inertia )
{
Wheel.AutoPhysics = false;
Wheel.Torque = torque;
Wheel.Brake = Vehicle.Brake;
Wheel.DoPhysics( Vehicle );
angularVelocity = Wheel.AngularVelocity;
return Wheel.CounterTorque;
}
}

View File

@@ -0,0 +1,100 @@
using Sandbox;
using System;
namespace VeloX.Powertrain;
public abstract class PowertrainComponent : Component
{
protected override void OnAwake()
{
Vehicle ??= Components.Get<VeloXBase>( FindMode.EverythingInSelfAndAncestors );
}
public const float RAD_TO_RPM = 60f / MathF.Tau;
public const float RPM_TO_RAD = 1 / (60 / MathF.Tau);
public const float UNITS_PER_METER = 39.37f;
public const float UNITS_TO_METERS = 0.01905f;
public const float KG_TO_N = 9.80665f;
public const float KG_TO_KN = 0.00980665f;
[Property] public VeloXBase Vehicle { get; set; }
[Property] public virtual float Inertia { get; set; } = 0.02f;
/// <summary>
/// Input component. Set automatically.
/// </summary>
[Property]
public PowertrainComponent Input
{
get => _input;
set
{
if ( value == null || value == this )
{
_input = null;
return;
}
_input = value;
}
}
private PowertrainComponent _input;
public int InputNameHash;
/// <summary>
/// The PowertrainComponent this component will output to.
/// </summary>
[Property]
public PowertrainComponent Output
{
get => _output;
set
{
if ( value == this )
{
_output = null;
return;
}
_output = value;
if ( _output != null )
_output.Input = this;
}
}
private PowertrainComponent _output;
public float RPM => angularVelocity * RAD_TO_RPM;
protected float angularVelocity;
protected float Torque;
public virtual bool HasOutput => Output.IsValid();
public virtual float QueryInertia()
{
if ( !HasOutput )
return Inertia;
return Inertia + Output.QueryInertia();
}
public virtual float QueryAngularVelocity( float angularVelocity )
{
if ( !HasOutput )
return angularVelocity;
return Output.QueryAngularVelocity( angularVelocity );
}
public virtual float ForwardStep( float torque, float inertia )
{
if ( !HasOutput )
return Torque;
return Output.ForwardStep( Torque, inertia + Inertia );
}
}

View File

@@ -1,6 +1,4 @@
using Sandbox;
namespace VeloX;
namespace VeloX;
public abstract partial class VeloXBase
{
@@ -17,14 +15,9 @@ public abstract partial class VeloXBase
angForce.y = angVel.y * drag.y * mass;
angForce.z = angVel.z * drag.z * mass;
if ( Wheels.Count > 0 )
{
var dt = Time.Delta;
foreach ( var v in Wheels )
v.DoPhysics( this, in dt );
}
if ( v.AutoPhysics ) v.DoPhysics( this );
Body.ApplyTorque( angForce );
}

View File

@@ -4,7 +4,7 @@ namespace VeloX;
[GameResource( "Wheel Friction", "whfric", "Wheel Friction", Category = "VeloX", Icon = "radio_button_checked" )]
public class WheelFriction : GameResource
public class TirePreset : GameResource
{
public FrictionPreset Longitudinal { get; set; }
public FrictionPreset Lateral { get; set; }

View File

@@ -1,11 +1,6 @@
using Sandbox;
using Sandbox.UI;
using System;
using System.Buffers.Text;
using System.Numerics;
using System.Runtime.Intrinsics.Arm;
using System.Text.RegularExpressions;
using System.Threading;
namespace VeloX;
@@ -23,13 +18,10 @@ public partial class VeloXWheel : Component
public FrictionPreset LateralFrictionPreset => WheelFriction.Lateral;
public FrictionPreset AligningFrictionPreset => WheelFriction.Aligning;
[Property] public WheelFriction WheelFriction { get; set; }
[Property] public TirePreset WheelFriction { get; set; }
[Property] public float Width { get; set; } = 6;
[Sync] public float SideSlip { get; private set; }
[Sync] public float ForwardSlip { get; private set; }
[Sync, Range( 0, 1 )] public float BrakePower { get; set; }
[Sync] public float Torque { get; set; }
[Sync, Range( 0, 1 )] public float Brake { get; set; }
[Property] float BrakePowerMax { get; set; } = 3000;
@@ -43,9 +35,13 @@ public partial class VeloXWheel : Component
[Property, Group( "Suspension" )] float SpringStrength { get; set; } = 800;
[Property, Group( "Suspension" )] float SpringDamper { get; set; } = 3000;
[Property] public bool AutoPhysics { get; set; } = true;
public float Spin { get; private set; }
public float RPM { get => angularVelocity * 60f / MathF.Tau; set => angularVelocity = value / (60 / MathF.Tau); }
public float AngularVelocity => angularVelocity;
internal float DistributionFactor { get; set; }
private Vector3 StartPos { get; set; }
@@ -69,9 +65,11 @@ public partial class VeloXWheel : Component
private Friction forwardFriction;
private Friction sideFriction;
private Vector3 force;
public float CounterTorque { get; private set; }
private float BaseInertia => 0.5f * Mass * MathF.Pow( Radius.InchToMeter(), 2 );
private float Inertia => BaseInertia;
public float Inertia => BaseInertia;
protected override void OnAwake()
{
@@ -118,7 +116,7 @@ public partial class VeloXWheel : Component
}
private (float, float, float, float) StepLongitudinal( float Vx, float Lc, float kFx, float kSx, float dt )
private (float, float, float, float) StepLongitudinal( float Vx, float Lc, float kFx, float kSx)
{
float Tm = Torque;
float Tb = Brake * BrakePowerMax + RollingResistance;
@@ -138,35 +136,35 @@ public partial class VeloXWheel : Component
Sx = Math.Clamp( Sx * kSx, -1, 1 );
W += Tm / I * dt;
W += Tm / I * Time.Delta;
Tb *= W > 0 ? -1 : 1;
float TbCap = MathF.Abs( W ) * I / dt;
float TbCap = MathF.Abs( W ) * I / Time.Delta;
float Tbr = MathF.Abs( Tb ) - MathF.Abs( TbCap );
Tbr = MathF.Max( Tbr, 0 );
Tb = Math.Clamp( Tb, -TbCap, TbCap );
W += Tb / I * dt;
W += Tb / I * Time.Delta;
float maxTorque = LongitudinalFrictionPreset.Evaluate( Sx ) * Lc * kFx;
float errorTorque = (W - Vx / R) * I / dt;
float errorTorque = (W - Vx / R) * I / Time.Delta;
float surfaceTorque = MathX.Clamp( errorTorque, -maxTorque, maxTorque );
W -= surfaceTorque / I * dt;
W -= surfaceTorque / I * Time.Delta;
float Fx = surfaceTorque / R;
Tbr *= W > 0 ? -1 : 1;
float TbCap2 = MathF.Abs( W ) * I / dt;
float TbCap2 = MathF.Abs( W ) * I / Time.Delta;
float Tb2 = Math.Clamp( Tbr, -TbCap2, TbCap2 );
W += Tb2 / I * dt;
W += Tb2 / I * Time.Delta;
float deltaOmegaTorque = (W - Winit) * I / dt;
float deltaOmegaTorque = (W - Winit) * I / Time.Delta;
float Tcnt = -surfaceTorque + Tb + Tb2 - deltaOmegaTorque;
if ( Lc < 0.001f )
@@ -175,7 +173,7 @@ public partial class VeloXWheel : Component
return (W, Sx, Fx, Tcnt);
}
private (float, float) StepLateral( float Vx, float Vy, float Lc, float kFy, float kSy, float dt )
private (float, float) StepLateral( float Vx, float Vy, float Lc, float kFy, float kSy)
{
float VxAbs = MathF.Abs( Vx );
float Sy;
@@ -183,7 +181,7 @@ public partial class VeloXWheel : Component
if ( VxAbs > 0.1f )
Sy = MathF.Atan( Vy / VxAbs ).RadianToDegree() * 0.01111f;
else
Sy = Vy * (0.003f / dt);
Sy = Vy * (0.003f / Time.Delta);
Sy *= kSy * 0.95f;
@@ -224,7 +222,7 @@ public partial class VeloXWheel : Component
private static float GetLongitudinalLoadCoefficient( float load ) => 11000 * (1 - MathF.Exp( -0.00014f * load ));
private static float GetLateralLoadCoefficient( float load ) => 18000 * (1 - MathF.Exp( -0.0001f * load ));
public void DoPhysics( VeloXBase vehicle, in float dt )
public void DoPhysics( VeloXBase vehicle )
{
var pos = vehicle.WorldTransform.PointToWorld( StartPos );
@@ -274,7 +272,7 @@ public partial class VeloXWheel : Component
var springForce = (offset * SpringStrength);
var damperForce = (lastSpringOffset - offset) * SpringDamper;
lastSpringOffset = offset;
force = (springForce - damperForce) * MathF.Max( 0, up.Dot( normal ) ) * normal / dt;
force = (springForce - damperForce) * MathF.Max( 0, up.Dot( normal ) ) * normal / Time.Delta;
// Vector3.CalculateVelocityOffset is broken (need fix)
//var velU = normal.Dot( vel ).MeterToInch();
@@ -301,13 +299,11 @@ public partial class VeloXWheel : Component
forwardSpeed = vel.Dot( forward );
sideSpeed = vel.Dot( right );
}
(float W, float Sx, float Fx, float _) = StepLongitudinal(
(float W, float Sx, float Fx, float Tcnt) = StepLongitudinal(
forwardSpeed,
longitudinalLoadCoefficient,
0.95f,
0.9f,
dt
0.9f
);
(float Sy, float Fy) = StepLateral(
@@ -315,12 +311,13 @@ public partial class VeloXWheel : Component
sideSpeed,
lateralLoadCoefficient,
0.95f,
0.9f,
dt
0.9f
);
SlipCircle( Sx, Sy, Fx, ref Fy );
angularVelocity = W;
CounterTorque = Tcnt;
forwardFriction = new Friction()
{