using Sandbox; using System; namespace VeloX; [GameResource( "Wheel Friction", "tire", "Wheel Friction", Category = "VeloX", Icon = "radio_button_checked" )] public class TirePreset : GameResource { [Property] public Pacejka Pacejka { get; set; } public float RollResistanceLin { get; set; } = 1E-3f; public float RollResistanceQuad { get; set; } = 1E-6f; public float GetRollingResistance( float velocity, float resistance_factor ) { // surface influence on rolling resistance float resistance = resistance_factor * RollResistanceLin; // heat due to tire deformation increases rolling resistance // approximate by quadratic function resistance += velocity * velocity * RollResistanceQuad; return resistance; } public void ComputeSlip( float vlon, float vlat, float vrot, out float slip_ratio, out float slip_angle ) { float rvlon = 1 / MathF.Max( MathF.Abs( vlon ), 1E-3f ); float vslip = vrot - vlon; slip_ratio = vslip * rvlon; slip_angle = -MathF.Atan( vlat * rvlon ); } /// approximate asin(x) = x + x^3/6 for +-18 deg range public static float ComputeCamberAngle( float sin_camber ) { float sc = Math.Clamp( sin_camber, -0.3f, 0.3f ); return ((1 / 6.0f) * (sc * sc) + 1) * sc; } public struct TireState() { /// /// surface friction coefficient /// public float friction = 0; /// /// tire camber angle relative to track surface /// public float camber = 0; /// /// camber thrust induced lateral slip velocity /// public float vcam = 0; /// /// ratio of tire contact patch speed to road speed /// public float slip = 0; /// /// the angle between the wheel heading and the wheel velocity /// public float slip_angle = 0; /// /// peak force slip ratio /// public float ideal_slip = 0; /// /// peak force slip angle /// public float ideal_slip_angle = 0; /// /// positive during traction /// public float fx = 0; /// /// positive during traction in a right turn /// public float fy = 0; /// /// positive during traction in a left turn /// public float mz = 0; }; public void ComputeState( float normal_force, float rot_velocity, float lon_velocity, float lat_velocity, float camber_angle, out TireState s ) { s = new TireState { camber = camber_angle, friction = 1.0f }; if ( normal_force * s.friction < 1E-6f ) { s.slip = s.slip_angle = 0; s.fx = s.fy = s.mz = 0; return; } float Fz = MathF.Min( normal_force * 0.001f, 30f ); ComputeSlip( lon_velocity, lat_velocity, rot_velocity, out float slip, out float slip_angle ); float sigma = slip; float alpha = slip_angle.RadianToDegree(); float gamma = s.camber.RadianToDegree(); float Fx0 = Pacejka.PacejkaFx( sigma, Fz, s.friction ); float Fy0 = Pacejka.PacejkaFy( alpha, Fz, gamma, s.friction, out float camber_alpha ); // combined slip float Gx = Pacejka.PacejkaGx( slip, slip_angle ); float Gy = Pacejka.PacejkaGy( slip, slip_angle ); float Fx = Gx * Fx0; float Fy = Gy * Fy0; s.vcam = ComputeCamberVelocity( camber_alpha.DegreeToRadian(), lon_velocity ); s.slip = slip; s.slip_angle = slip_angle; s.fx = Fx; s.fy = Fy; } public static float ComputeCamberVelocity( float sa, float vx ) { float tansa = (1 / 3.0f * (sa * sa) + 1) * sa; return tansa * vx; } }