уже лучше

This commit is contained in:
Valera 2025-06-15 21:59:42 +07:00
parent 4912d0ae1a
commit f0f89ff947
11 changed files with 218 additions and 255 deletions

View File

@ -1,5 +1,56 @@
{
"Pacejka": {},
"Pacejka": {
"Lateral": {
"a0": 1.4,
"a1": -0,
"a2": 1688,
"a3": 2400,
"a4": 6.026,
"a5": 0,
"a6": -0.359,
"a7": 1,
"a8": 0,
"a9": 0,
"a10": 0,
"a111": 0,
"a112": 0,
"a12": 0,
"a13": 0
},
"Longitudinal": {
"b0": 1.65,
"b1": 0,
"b2": 1690,
"b3": 0,
"b4": 229,
"b5": 0,
"b6": 0,
"b7": 0,
"b8": -10,
"b9": 0,
"b10": 0
},
"Aligning": {
"c0": 2,
"c1": -3.8,
"c2": -3.14,
"c3": -1.16,
"c4": -7.2,
"c5": 0,
"c6": 0,
"c7": 0.044,
"c8": -0.58,
"c9": 0.18,
"c10": 0,
"c11": 0,
"c12": 0,
"c13": 0,
"c14": 0.14,
"c15": -1.029,
"c16": 0,
"c17": 0
}
},
"RollResistanceLin": 0.001,
"RollResistanceQuad": 1E-06,
"__references": [],

View File

@ -87,7 +87,7 @@ public class Engine : PowertrainComponent
angularVelocity += finalTorque / inertiaSum * Time.Delta;
angularVelocity = Math.Max( angularVelocity, 0 );
//UpdateStream();
UpdateStream();
return finalTorque;
}

View File

@ -7,7 +7,11 @@ public class PowerWheel : PowertrainComponent
{
[Property] public VeloXWheel Wheel { get; set; }
public override float QueryInertia() => Wheel.Inertia;
public override float QueryInertia()
{
float dtScale = Math.Clamp( Time.Delta, 0.01f, 0.05f ) / 0.005f;
return Wheel.BaseInertia * dtScale;
}
public override float QueryAngularVelocity( float angularVelocity )
{
@ -19,7 +23,8 @@ public class PowerWheel : PowertrainComponent
Wheel.AutoPhysics = false;
Wheel.Torque = torque;
Wheel.Brake = Vehicle.Brake;
Inertia = Wheel.BaseInertia + inertia;
Wheel.Inertia = inertia;
Wheel.DoPhysics( Vehicle );
angularVelocity = Wheel.AngularVelocity;

View File

@ -167,7 +167,7 @@ public class Pacejka
/// pacejka magic formula for longitudinal force
public float PacejkaFx( float sigma, float Fz, float friction_coeff )
public float PacejkaFx( float sigma, float Fz, float friction_coeff, out float maxforce_output )
{
var b = Longitudinal;
@ -198,6 +198,7 @@ public class Pacejka
// scale by surface friction
Fx *= friction_coeff;
maxforce_output = D;
return Fx;
}

View File

@ -24,12 +24,24 @@ public class TirePreset : GameResource
return resistance;
}
public void ComputeSlip( float vlon, float vlat, float vrot, out float slip_ratio, out float slip_angle )
public static void ComputeSlip( float lon_velocity, float lat_velocity, float rot_velocity, float wheel_radius, 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 );
var abs_lon = Math.Max( MathF.Abs( lon_velocity ), 1e-3f );
slip_ratio = lon_velocity - rot_velocity * wheel_radius;
if ( abs_lon >= 0.005f )
slip_ratio /= abs_lon;
else
slip_ratio *= abs_lon;
if ( abs_lon >= 0.5f )
slip_angle = MathF.Atan2( -lat_velocity, abs_lon ).RadianToDegree() / 50f;
else
slip_angle = -lat_velocity * (0.01f / Time.Delta);
slip_ratio = Math.Clamp( slip_ratio, -1, 1 );
slip_angle = Math.Clamp( slip_angle, -1, 1 );
}
@ -40,104 +52,6 @@ public class TirePreset : GameResource
return ((1 / 6.0f) * (sc * sc) + 1) * sc;
}
public struct TireState()
{
/// <summary>
/// surface friction coefficient
/// </summary>
public float friction = 0;
/// <summary>
/// tire camber angle relative to track surface
/// </summary>
public float camber = 0;
/// <summary>
/// camber thrust induced lateral slip velocity
/// </summary>
public float vcam = 0;
/// <summary>
/// ratio of tire contact patch speed to road speed
/// </summary>
public float slip = 0;
/// <summary>
/// the angle between the wheel heading and the wheel velocity
/// </summary>
public float slip_angle = 0;
/// <summary>
/// peak force slip ratio
/// </summary>
public float ideal_slip = 0;
/// <summary>
/// peak force slip angle
/// </summary>
public float ideal_slip_angle = 0;
/// <summary>
/// positive during traction
/// </summary>
public float fx = 0;
/// <summary>
/// positive during traction in a right turn
/// </summary>
public float fy = 0;
/// <summary>
/// positive during traction in a left turn
/// </summary>
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;

View File

@ -1,10 +1,14 @@
using Sandbox;
using Sandbox.Rendering;
using Sandbox.Services;
using System;
using System.Collections.Specialized;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Threading;
using static Sandbox.CameraComponent;
using static Sandbox.Package;
using static Sandbox.SkinnedModelRenderer;
namespace VeloX;
@ -40,7 +44,7 @@ public partial class VeloXWheel : Component
public float Spin { get; private set; }
public float RPM { get => angularVelocity * 30f / MathF.PI; set => angularVelocity = value / (30f / MathF.PI); }
public float AngularVelocity => angularVelocity;
public float AngularVelocity { get => angularVelocity; set => angularVelocity = value; }
internal float DistributionFactor { get; set; }
@ -65,8 +69,12 @@ public partial class VeloXWheel : Component
private Vector3 force;
public float CounterTorque { get; private set; }
private float BaseInertia => Mass * MathF.Pow( Radius.InchToMeter(), 2 );
public float Inertia => BaseInertia;
internal float BaseInertia => 0.5f * Mass * MathF.Pow( Radius.InchToMeter(), 2 );
public float Inertia
{
get => BaseInertia + inertia;
set => inertia = value;
}
protected override void OnAwake()
@ -83,12 +91,8 @@ public partial class VeloXWheel : Component
private void UpdateVisuals( VeloXBase vehicle, in float dt )
{
var entityAngles = vehicle.WorldRotation;
Spin -= angularVelocity.MeterToInch() * dt;
Spin -= angularVelocity.RadianToDegree() * dt;
WorldRotation = vehicle.WorldTransform.RotationToWorld( GetSteer( vehicle.SteerAngle.yaw ) ) * Rotation.FromAxis( Vector3.Right, Spin );
}
private Rotation GetSteer( float steer )
@ -108,16 +112,14 @@ 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 ));
float lastload;
private float inertia;
public void DoPhysics( VeloXBase vehicle )
{
var pos = vehicle.WorldTransform.PointToWorld( StartPos );
var ang = vehicle.WorldTransform.RotationToWorld( GetSteer( vehicle.SteerAngle.yaw ) );
forward = ang.Forward;
right = ang.Right;
up = ang.Up;
var maxLen = SuspensionLength;
var endPos = pos + ang.Down * maxLen;
@ -129,6 +131,12 @@ public partial class VeloXWheel : Component
.UseHitPosition( false )
.WithoutTags( vehicle.WheelIgnoredTags )
.Run();
//forward = ang.Forward;
//right = ang.Right;
up = ang.Up;
forward = Vector3.VectorPlaneProject( ang.Forward, Trace.Normal ).Normal;
right = Vector3.VectorPlaneProject( ang.Right, Trace.Normal ).Normal;
var fraction = Trace.Fraction;
@ -141,7 +149,9 @@ public partial class VeloXWheel : Component
var normal = Trace.Normal;
var vel = vehicle.Body.GetVelocityAtPoint( pos );
var vel = vehicle.Body.GetVelocityAtPoint( contactPos );
//var vel = vehicle.Body.GetVelocityAtPoint( pos );
if ( !IsOnGround )
{
@ -191,76 +201,105 @@ public partial class VeloXWheel : Component
float camber_rad = CamberAngle.DegreeToRadian();
float R = Radius.InchToMeter();
TirePreset.ComputeState(
2500,
angularVelocity * R,
forwardSpeed,
sideSpeed,
camber_rad,
out var tireState
);
//TirePreset.ComputeState(
// load,
// angularVelocity,
// forwardSpeed,
// sideSpeed,
// camber_rad,
// R,
// Inertia,
// out var tireState
//);
float linearSpeed = angularVelocity * Radius.InchToMeter();
float F_roll = TirePreset.GetRollingResistance( linearSpeed, 1.0f );
F_roll = -MathF.Sign( forwardSpeed ) * F_roll;
F_roll *= -Math.Clamp( linearSpeed * 0.25f, -1, 1 );
float Fx_total = tireState.fx + F_roll;
//float Fx_total = tireState.fx + F_roll;
float T_brake = Brake * BrakePowerMax;
if ( angularVelocity > 0 ) T_brake = -T_brake;
else T_brake = angularVelocity < 0 ? T_brake : -MathF.Sign( Torque ) * T_brake;
float totalTorque = Torque + T_brake - Fx_total * R;
//float totalTorque = Torque + tireState.fx;
// not work
Vector3 c = pos.Cross( forward );
Vector3 v = (c * vehicle.Body.PhysicsBody.Inertia).Cross( pos );
var m = 1 / (1 / vehicle.Body.Mass + forward.Dot( v ));
m *= Inertia / (m * R * R + Inertia);
float ve = forward.Dot( vel ).InchToMeter() - angularVelocity;
angularVelocity += Torque / Inertia * Time.Delta;
angularVelocity += T_brake / Inertia * Time.Delta;
angularVelocity += F_roll * 9.80665f / Inertia * Time.Delta;
TirePreset.ComputeSlip( forwardSpeed, sideSpeed, angularVelocity, R, out var slip, out var slip_ang );
var fx = TirePreset.Pacejka.PacejkaFx( slip, load, 1, out var maxTorque );
var fy = TirePreset.Pacejka.PacejkaFy( slip_ang * load, load, camber_rad, 1, out var _ );
angularVelocity += ve * m * Radius * (1 / Inertia);// totalTorque * (1 / Inertia) * Time.Delta;
maxTorque *= R;
var errorTorque = (angularVelocity - forwardSpeed / R) * Inertia / Time.Delta;
var surfaceTorque = Math.Clamp( errorTorque, -maxTorque, maxTorque );
angularVelocity -= surfaceTorque / Inertia * Time.Delta;
forwardFriction = new Friction()
{
Slip = tireState.slip,
Force = Fx_total,
Slip = slip,
Force = -fx,
Speed = forwardSpeed
};
sideFriction = new Friction()
{
Slip = tireState.slip_angle,
Force = tireState.fy,
Slip = slip_ang,
Force = fy,
Speed = sideSpeed
};
Vector3 frictionForce = forward * forwardFriction.Force + right * sideFriction.Force;
vehicle.Body.ApplyForceAt( contactPos, (force + frictionForce) / Time.Delta );
vehicle.Body.ApplyForceAt( pos, force / Time.Delta * ProjectSettings.Physics.SubSteps );
vehicle.Body.ApplyForceAt( pos, frictionForce * ProjectSettings.Physics.SubSteps );
}
else
}
//todo
protected (float Mass, float Inertia) CalcMassAndInertia()
{
// Колесо в воздухе: сбрасываем силы
forwardFriction = new Friction();
sideFriction = new Friction();
// section width in millimeters, measured from sidewall to sidewall
// ratio of sidewall height to section width in percent
// diameter of the wheel in inches
var tire_size = new Vector3( 215, 45, 17 );
// Обновление угловой скорости только от мотора/тормозов
float T_brake = Brake * BrakePowerMax;
float tire_width = tire_size[0] * 0.001f;
float tire_aspect_ratio = tire_size[1] * 0.01f;
float tire_radius = tire_size[2] * 0.5f * 0.0254f + tire_width * tire_aspect_ratio;
float tire_thickness = 0.02f;
float tire_density = 1000; // rubber
if ( angularVelocity > 0 ) T_brake = -T_brake;
else T_brake = angularVelocity < 0 ? T_brake : -MathF.Sign( Torque ) * T_brake;
angularVelocity += (Torque + T_brake) / Inertia * Time.Delta;
float rim_radius = tire_radius - tire_width * tire_aspect_ratio;
float rim_width = tire_width;
float rim_thickness = 0.01f;
float rim_density = 2700; // aluminium
float tire_volume = float.Pi * tire_width * tire_thickness * (2 * tire_radius - tire_thickness);
float rim_volume = float.Pi * rim_width * rim_thickness * (2 * rim_radius - rim_thickness);
float tire_mass = tire_density * tire_volume;
float rim_mass = rim_density * rim_volume;
float tire_inertia = tire_mass * tire_radius * tire_radius;
float rim_inertia = rim_mass * rim_radius * rim_radius;
float mass = tire_mass + rim_mass;
float inertia = tire_inertia + rim_inertia;
return (mass, inertia);
}
}
// debug
protected override void OnUpdate()
{
DebugOverlay.Normal( contactPos, forward * forwardFriction.Force / 100f, Color.Red, overlay: true );
DebugOverlay.Normal( contactPos, right * sideFriction.Force / 100f, Color.Green, overlay: true );
DebugOverlay.Normal( contactPos, forward * forwardFriction.Force / 1000f, Color.Red, overlay: true );
DebugOverlay.Normal( contactPos, right * sideFriction.Force / 1000f, Color.Green, overlay: true );
DebugOverlay.Normal( contactPos, up * force / 1000f, Color.Blue, overlay: true );
}
}

View File

@ -14,33 +14,40 @@ public partial class VeloXCar
[Property, Feature( "Steer" )] public float MaxSteerAngle { get; set; } = 35f;
[Sync] public float Steering { get; private set; }
private float jTurnMultiplier;
private float inputSteer;
public static float SignedAngle( Vector3 from, Vector3 to, Vector3 axis )
{
float unsignedAngle = Vector3.GetAngle( from, to );
float cross_x = from.y * to.z - from.z * to.y;
float cross_y = from.z * to.x - from.x * to.z;
float cross_z = from.x * to.y - from.y * to.x;
float sign = MathF.Sign( axis.x * cross_x + axis.y * cross_y + axis.z * cross_z );
return unsignedAngle * sign;
}
public float VelocityAngle { get; private set; }
public int CarDirection { get { return ForwardSpeed.InchToMeter() < 5 ? 0 : (VelocityAngle < 90 && VelocityAngle > -90 ? 1 : -1); } }
private void UpdateSteering( float dt )
{
var inputSteer = Input.AnalogMove.y;
var absInputSteer = Math.Abs( inputSteer );
var sideSlip = Math.Clamp( avgSideSlip, -1, 1 );
VelocityAngle = -SignedAngle( Body.Velocity, WorldRotation.Forward, WorldRotation.Up );
var steerConeFactor = Math.Clamp( TotalSpeed / SteerConeMaxSpeed, 0, 1 );
var steerCone = 1 - steerConeFactor * (1 - SteerConeMaxAngle);
steerCone = Math.Clamp( steerCone, Math.Abs( sideSlip ), 1 );
inputSteer = ExpDecay( this.inputSteer, inputSteer * steerCone, SteerConeChangeRate, dt );
this.inputSteer = inputSteer;
var counterSteer = sideSlip * steerConeFactor * (1 - absInputSteer);
counterSteer = Math.Clamp( counterSteer, -1, 1 ) * CounterSteer;
inputSteer = Math.Clamp( inputSteer + counterSteer, -1, 1 );
float target = -inputSteer * MaxSteerAngle;
if ( CarDirection > 0 )
target -= VelocityAngle * CounterSteer;
inputSteer = Math.Clamp( inputSteer, -1, 1 );
Steering = inputSteer;
SteerAngle = new( 0, -inputSteer * MaxSteerAngle, 0 );
if ( ForwardSpeed < -100 )
jTurnMultiplier = 0.5f;
else
jTurnMultiplier = ExpDecay( jTurnMultiplier, 1, 2, dt );
SteerAngle = new( 0, target, 0 );
}
}

View File

@ -2,27 +2,9 @@
public partial class VeloXCar
{
private float avgSideSlip;
private float avgPoweredRPM;
private float avgForwardSlip;
private void WheelThink( in float dt )
{
float avgRPM = 0, totalSideSlip = 0, totalForwardSlip = 0;
foreach ( var w in Wheels )
{
w.Update( this, dt );
totalSideSlip += w.SideSlip;
totalForwardSlip += w.ForwardSlip;
var rpm = w.RPM;
avgRPM += rpm * w.DistributionFactor;
}
avgPoweredRPM = avgRPM;
avgSideSlip = totalSideSlip / Wheels.Count;
avgForwardSlip = totalForwardSlip / Wheels.Count;
}
}

View File

@ -7,32 +7,6 @@ namespace VeloX;
[Title( "VeloX - Car" )]
public partial class VeloXCar : VeloXBase
{
protected override void OnStart()
{
base.OnStart();
//StreamPlayer = new( Stream );
//if ( IsDriver )
//{
// UpdateGearList();
// UpdatePowerDistribution();
//}
}
protected override void OnUpdate()
{
base.OnUpdate();
//if ( StreamPlayer is not null )
//{
// StreamPlayer.Throttle = Throttle;
// StreamPlayer.RPMPercent = RPMPercent;
// StreamPlayer.EngineState = EngineState;
// StreamPlayer.IsRedlining = IsRedlining;
// StreamPlayer.Update( Time.Delta, WorldPosition );
//}
}
protected override void OnFixedUpdate()
{
if ( !IsDriver )

View File

@ -17,7 +17,7 @@ public class PacejkaWidget : ControlObjectWidget
Layout = Layout.Column();
Layout.Margin = 8f;
Layout.Spacing = 8;
foreach ( var item in TypeLibrary.GetType<Pacejka.LateralForce>().Fields )
foreach ( var item in TypeLibrary.GetType<Pacejka.LateralForce>().Properties )
{
var row = Layout.AddRow();
row.Spacing = 8;
@ -36,7 +36,7 @@ public class PacejkaWidget : ControlObjectWidget
Layout = Layout.Column();
Layout.Margin = 8f;
Layout.Spacing = 8;
foreach ( var item in TypeLibrary.GetType<Pacejka.LongitudinalForce>().Fields )
foreach ( var item in TypeLibrary.GetType<Pacejka.LongitudinalForce>().Properties )
{
var row = Layout.AddRow();
row.Spacing = 8;
@ -55,7 +55,7 @@ public class PacejkaWidget : ControlObjectWidget
Layout = Layout.Column();
Layout.Margin = 8f;
Layout.Spacing = 8;
foreach ( var item in TypeLibrary.GetType<Pacejka.AligningMoment>().Fields )
foreach ( var item in TypeLibrary.GetType<Pacejka.AligningMoment>().Properties )
{
var row = Layout.AddRow();
row.Spacing = 8;

View File

@ -1,7 +1,6 @@
using Editor;
using Editor.Assets;
using Editor.Inspectors;
using Editor.ShaderGraph.Nodes;
using Sandbox;
using System;
using System.Collections.Generic;
@ -68,15 +67,12 @@ class TirePresetPreview( Asset asset ) : PixmapAssetPreview( asset )
for ( float x = x0; x <= xn; x += (xn - x0) / points )
{
float yval = tire.PacejkaFy( x, load, Camber, 1.0f, out float maxforce ) / load;
float yval = tire.PacejkaFy( x, load, Camber, 1.0f, out float _ ) / load;
float xval = width * (x - x0) / (xn - x0);
yval /= ymax - ymin;
yval = (yval + 1.0f) * 0.5f;
yval = 1.0f - yval;
yval *= height;
if ( x == x0 )
pointCache.Add( new( xval, yval ) );
else
pointCache.Add( new( xval, yval ) );
}
@ -86,6 +82,29 @@ class TirePresetPreview( Asset asset ) : PixmapAssetPreview( asset )
{ // draw longitudinal line
pointCache.Clear();
Paint.SetPen( Color.Green, 1 );
float x0 = -zoom * 0.5f;
float xn = zoom * 0.5f;
float ymin = -1000.0f;
float ymax = 1000.0f;
int points = 500;
for ( float x = x0; x <= xn; x += (xn - x0) / points )
{
float yval = tire.PacejkaFx( x, load, 1.0f, out var _ ) / load;
float xval = width * (x - x0) / (xn - x0);
yval /= ymax - ymin;
yval = (yval + 1.0f) * 0.5f;
yval = 1.0f - yval;
yval *= height;
pointCache.Add( new( xval, yval ) );
}
Paint.DrawLine( pointCache );
}
{ // draw aligning line
pointCache.Clear();
Paint.SetPen( Color.Blue, 1 );
float x0 = -zoom * 0.5f * 10.0f;
float xn = zoom * 0.5f * 10.0f;
@ -101,35 +120,6 @@ class TirePresetPreview( Asset asset ) : PixmapAssetPreview( asset )
yval = (yval + 1.0f) * 0.5f;
yval = 1.0f - yval;
yval *= height;
if ( x == x0 )
pointCache.Add( new( xval, yval ) );
else
pointCache.Add( new( xval, yval ) );
}
Paint.DrawLine( pointCache );
}
{ // draw aligning line
pointCache.Clear();
Paint.SetPen( Color.Green, 1 );
float x0 = -zoom * 0.5f;
float xn = zoom * 0.5f;
float ymin = -1000.0f;
float ymax = 1000.0f;
int points = 500;
for ( float x = x0; x <= xn; x += (xn - x0) / points )
{
float yval = tire.PacejkaFx( x, load, 1.0f ) / load;
float xval = width * (x - x0) / (xn - x0);
yval /= ymax - ymin;
yval = (yval + 1.0f) * 0.5f;
yval = 1.0f - yval;
yval *= height;
if ( x == x0 )
pointCache.Add( new( xval, yval ) );
else
pointCache.Add( new( xval, yval ) );
}
Paint.DrawLine( pointCache );