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 { public float friction = 0; // surface friction coefficient public float camber = 0; // tire camber angle relative to track surface public float vcam = 0; // camber thrust induced lateral slip velocity public float slip = 0; // ratio of tire contact patch speed to road speed public float slip_angle = 0; // the angle between the wheel heading and the wheel velocity public float ideal_slip = 0; // peak force slip ratio public float ideal_slip_angle = 0; // peak force slip angle public float fx = 0; // positive during traction public float fy = 0; // positive in a right turn public float mz = 0; // positive in a left turn public TireState() { } }; 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 = Math.Min( normal_force * 1E-3f, 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 Fx = Pacejka.PacejkaFx( sigma, Fz, s.friction ); float Fy = Pacejka.PacejkaFy( alpha, Fz, gamma, s.friction, out float camber_alpha ); 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; } }