From e12b75be4511234a3d99db939a0c8d942f0f8eb2 Mon Sep 17 00:00:00 2001 From: Valera Date: Tue, 25 Nov 2025 19:16:40 +0700 Subject: [PATCH] update --- Code/Base/Powertrain/Engine.cs | 33 +++++--- Code/Base/VeloXBase.Engine.cs | 1 - Code/Base/VeloXBase.Phys.cs | 2 +- Code/Base/VeloXBase.cs | 16 +++- Code/Base/Wheel/Friction.cs | 10 ++- Code/Base/Wheel/VeloXWheel.Friction.cs | 101 ++++++++++++------------- Code/Base/Wheel/VeloXWheel.cs | 31 -------- Code/Base/Wheel/WheelManager.cs | 14 +++- Code/Car/VeloXCar.ABS.cs | 6 +- Code/Car/VeloXCar.ESC.cs | 10 ++- Code/Car/VeloXCar.cs | 2 +- 11 files changed, 119 insertions(+), 107 deletions(-) diff --git a/Code/Base/Powertrain/Engine.cs b/Code/Base/Powertrain/Engine.cs index fbff1d5..da18dc5 100644 --- a/Code/Base/Powertrain/Engine.cs +++ b/Code/Base/Powertrain/Engine.cs @@ -78,7 +78,7 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents /// [Property] public bool FlyingStartEnabled { get; set; } - [Property] public bool Ignition { get; set; } + [Property] public bool Ignition { get; private set; } /// /// Power curve with RPM range [0,1] on the X axis and power coefficient [0,1] on Y axis. @@ -225,13 +225,21 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents if ( FlyingStartEnabled ) { FlyingStart(); + IsRunning = true; + IsActive = true; } else if ( !StarterActive && Controller != null ) { StarterCoroutine(); } } + else + { + IsRunning = true; + IsActive = true; + } } + private async void StarterCoroutine() { if ( Type == EngineType.Electric || StarterActive ) @@ -251,6 +259,11 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents { startTimer += 0.1f; await Task.DelaySeconds( 0.1f ); + + if ( OutputAngularVelocity >= _idleAngularVelocity * 0.8f ) + { + break; + } } } finally @@ -258,6 +271,7 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents _starterTorque = 0; StarterActive = false; IsActive = true; + IsRunning = true; } } @@ -267,12 +281,15 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents Ignition = true; StarterActive = false; OutputAngularVelocity = IdleRPM.RPMToAngularVelocity(); + IsRunning = true; + IsActive = true; } public void StopEngine() { Ignition = false; - IsActive = true; + IsRunning = false; + IsActive = false; OnEngineStop?.Invoke(); } @@ -308,11 +325,6 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents _revLimiterAngularVelocity = RevLimiterRPM.RPMToAngularVelocity(); if ( _revLimiterAngularVelocity == 0f ) return; - // Check for start on throttle - - if ( !IsRunning && !StarterActive && AutoStartOnThrottle && ThrottlePosition > 0.2f ) - StartEngine(); - bool wasRunning = IsRunning; IsRunning = Ignition; @@ -334,13 +346,14 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents // Calculate generated torque and power float generatedTorque = CalculateTorqueICE( OutputAngularVelocity, dt ); generatedPower = TorqueToPowerInKW( in OutputAngularVelocity, in generatedTorque ); - + // Calculate reaction torque float reactionTorque = (targetAngularVelocity - OutputAngularVelocity) * Inertia / dt; // Calculate/get torque returned from wheels OutputTorque = generatedTorque - reactionTorque; + float returnTorque = ForwardStep( OutputTorque, 0, dt ); float totalTorque = generatedTorque + returnTorque + reactionTorque; @@ -408,7 +421,7 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents // Apply idle throttle correction to keep the engine running else ApplyICEIdleCorrection(); - + // Trigger rev limiter if needed if ( angularVelocity >= _revLimiterAngularVelocity && !RevLimiterActive ) RevLimiter(); @@ -454,7 +467,7 @@ public class Engine : PowertrainComponent, IScenePhysicsEvents // if the idle RPM is below the target RPM. float idleCorrection = _idleAngularVelocity * 1.08f - OutputAngularVelocity; idleCorrection = idleCorrection < 0f ? 0f : idleCorrection; - float idleThrottlePosition = Math.Clamp( idleCorrection * 0.01f, 0, 1 ); + float idleThrottlePosition = Math.Clamp( idleCorrection * 0.01f, 0, 1f ); ThrottlePosition = Math.Max( _userThrottleInput, idleThrottlePosition ); } } diff --git a/Code/Base/VeloXBase.Engine.cs b/Code/Base/VeloXBase.Engine.cs index 899e323..90858d5 100644 --- a/Code/Base/VeloXBase.Engine.cs +++ b/Code/Base/VeloXBase.Engine.cs @@ -30,7 +30,6 @@ public abstract partial class VeloXBase Engine.Controller = this; Engine.Inertia = 0.25f; - Engine.Ignition = false; if ( !Clutch.IsValid() ) Clutch = new GameObject( Engine.GameObject, true, "Clutch" ).GetOrAddComponent(); diff --git a/Code/Base/VeloXBase.Phys.cs b/Code/Base/VeloXBase.Phys.cs index 5bbc0b5..93a1e91 100644 --- a/Code/Base/VeloXBase.Phys.cs +++ b/Code/Base/VeloXBase.Phys.cs @@ -42,7 +42,7 @@ public abstract partial class VeloXBase } else { - v.BrakeTorque = SwappedBrakes * BrakeForce * 0.2f; + v.BrakeTorque = SwappedBrakes * BrakeForce * 0.7f; v.BrakeTorque += Handbrake * HandbrakeForce; } diff --git a/Code/Base/VeloXBase.cs b/Code/Base/VeloXBase.cs index 112fa7a..739e7c1 100644 --- a/Code/Base/VeloXBase.cs +++ b/Code/Base/VeloXBase.cs @@ -1,13 +1,23 @@ using Sandbox; +using System; namespace VeloX; public abstract partial class VeloXBase : Component { - [Sync] public WaterState WaterState { get; set; } - [Sync] public bool IsEngineOnFire { get; set; } - [Property, Sync] public EngineState EngineState { get; set; } + [Sync, Change( nameof( OnEngineIgnitionChange ) )] + public bool EngineIgnition { get; set; } + + + private void OnEngineIgnitionChange( bool oldvalue, bool newvalue ) + { + if ( newvalue ) + Engine?.StartEngine(); + else + Engine?.StopEngine(); + } + [Property] public Vector3 AngularDrag { get; set; } = new( -0.1f, -0.1f, -3 ); [Property] public float Mass { get; set; } = 900; diff --git a/Code/Base/Wheel/Friction.cs b/Code/Base/Wheel/Friction.cs index 997978f..2947bcc 100644 --- a/Code/Base/Wheel/Friction.cs +++ b/Code/Base/Wheel/Friction.cs @@ -1,4 +1,6 @@ -namespace VeloX; +using System; + +namespace VeloX; public struct Friction { @@ -8,4 +10,10 @@ public struct Friction public float Force { get; set; } public float Slip { get; set; } public float Speed { get; set; } + + + public override readonly string ToString() + { + return $"Force:{Math.Round(Force, 2)}\nSlip:{Math.Round(Slip, 2)}\nSpeed:{Math.Round(Speed, 2)}"; + } } diff --git a/Code/Base/Wheel/VeloXWheel.Friction.cs b/Code/Base/Wheel/VeloXWheel.Friction.cs index 05984e1..2b68d19 100644 --- a/Code/Base/Wheel/VeloXWheel.Friction.cs +++ b/Code/Base/Wheel/VeloXWheel.Friction.cs @@ -47,25 +47,14 @@ public partial class VeloXWheel public Vector3 ContactRight => hitSidewaysDirection; public Vector3 ContactForward => hitForwardDirection; - [Sync] - public float LongitudinalSlip - { - get => ForwardFriction.Slip; - private set => ForwardFriction.Slip = value; - } + public float LongitudinalSlip => ForwardFriction.Slip; public float LongitudinalSpeed => ForwardFriction.Speed; public bool IsSkiddingLongitudinally => NormalizedLongitudinalSlip > 0.35f; public float NormalizedLongitudinalSlip => Math.Clamp( Math.Abs( LongitudinalSlip ), 0, 1 ); - [Sync] - public float LateralSlip - { - get => SidewayFriction.Slip; - private set => SidewayFriction.Slip = value; - } - + public float LateralSlip => SidewayFriction.Slip; public float LateralSpeed => SidewayFriction.Speed; public bool IsSkiddingLaterally => NormalizedLateralSlip > 0.35f; public float NormalizedLateralSlip => Math.Clamp( Math.Abs( LateralSlip ), 0, 1 ); @@ -86,8 +75,8 @@ public partial class VeloXWheel } else { - ForwardFriction.Speed = 0f; - SidewayFriction.Speed = 0f; + ForwardFriction = new(); + SidewayFriction = new(); } } @@ -97,6 +86,7 @@ public partial class VeloXWheel private Vector3 currentPosition; private Vector3 referenceError; private Vector3 correctiveForce; + public bool wheelIsBlocked; private void UpdateFriction( float dt ) { var motorTorque = DriveTorque; @@ -120,8 +110,6 @@ public partial class VeloXWheel float loadPercent = Math.Clamp( Fz / LoadRating, 0f, 1f ); float slipLoadModifier = 1f - loadPercent * 0.4f; - //DebugOverlay.Text( WorldPosition, SidewayFriction.Speed.ToString(), overlay: true ); - float mass = Vehicle.Body.Mass; float absForwardSpeed = Math.Abs( ForwardFriction.Speed ); @@ -133,10 +121,7 @@ public partial class VeloXWheel forwardSpeedClamp = Math.Clamp( forwardSpeedClamp, 1.5f, 10f ); float clampedAbsForwardSpeed = Math.Max( absForwardSpeed, forwardSpeedClamp ); - // Calculate effect of camber on friction - float camberFrictionCoeff = Math.Max( 0, Vehicle.WorldRotation.Up.Dot( ContactNormal ) ); - - float peakForwardFrictionForce = forwardLoadFactor; + float peakForwardFrictionForce = 11000 * (1 - MathF.Exp( -0.00014f * forwardLoadFactor )); float absCombinedBrakeTorque = Math.Max( 0, brakeTorque + RollingResistanceTorque ); float signedCombinedBrakeTorque = absCombinedBrakeTorque * -Math.Sign( ForwardFriction.Speed ); @@ -152,7 +137,7 @@ public partial class VeloXWheel ForwardFriction.Force = forwardInputForce > maxForwardForce ? maxForwardForce : forwardInputForce < -maxForwardForce ? -maxForwardForce : forwardInputForce; - bool wheelIsBlocked = false; + wheelIsBlocked = false; if ( IsOnGround ) { float combinedWheelForce = motorForce + absCombinedBrakeTorque * invRadius * -Math.Sign( AngularVelocity ); @@ -195,7 +180,6 @@ public partial class VeloXWheel float maxCounterTorque = inertia * absAngularVelocity; CounterTorque = Math.Clamp( (signedCombinedBrakeForce - ForwardFriction.Force) * mRadius, -maxCounterTorque, maxCounterTorque ); - ForwardFriction.Slip = (ForwardFriction.Speed - AngularVelocity * mRadius) / clampedAbsForwardSpeed; ForwardFriction.Slip *= slipLoadModifier; @@ -204,10 +188,13 @@ public partial class VeloXWheel float sideSlipSign = SidewayFriction.Slip > 0 ? 1 : -1; float absSideSlip = Math.Abs( SidewayFriction.Slip ); - float peakSideFrictionForce = sideLoadFactor; + float peakSideFrictionForce = 18000 * (1 - MathF.Exp( -0.0001f * sideLoadFactor )); - float sideForce = -sideSlipSign * Tire.Evaluate( absSideSlip ) * sideLoadFactor; + float sideForce = -sideSlipSign * Tire.Evaluate( absSideSlip ) * peakSideFrictionForce; SidewayFriction.Force = Math.Clamp( sideForce, -sideForceClamp, sideForceClamp ); + + // Calculate effect of camber on friction + float camberFrictionCoeff = Math.Max( 0, Vehicle.WorldRotation.Up.Dot( ContactNormal ) ); SidewayFriction.Force *= camberFrictionCoeff; if ( IsOnGround && absForwardSpeed < 0.12f && absSideSpeed < 0.12f ) @@ -241,9 +228,7 @@ public partial class VeloXWheel ForwardFriction.Force += correctiveForce.Dot( hitForwardDirection ); } SidewayFriction.Force += correctiveForce.Dot( hitSidewaysDirection ); - } - } else { @@ -251,46 +236,56 @@ public partial class VeloXWheel } - ForwardFriction.Force = Math.Clamp( ForwardFriction.Force, -peakForwardFrictionForce, peakForwardFrictionForce ); + //float forwardSlipPercent = -ForwardFriction.Slip / Tire.GetPeakSlip(); + //float sideSlipPercent = SidewayFriction.Slip / Tire.GetPeakSlip(); + //float slipCircleLimit = MathF.Sqrt( forwardSlipPercent * forwardSlipPercent + sideSlipPercent * sideSlipPercent ); + //if ( slipCircleLimit > 1f ) + //{ + // float beta = MathF.Atan2( sideSlipPercent, forwardSlipPercent * 1.05f ); + // float sinBeta = MathF.Sin( beta ); + // float cosBeta = MathF.Cos( beta ); - SidewayFriction.Force = Math.Clamp( SidewayFriction.Force, -peakSideFrictionForce, peakSideFrictionForce ); + // float absForwardForce = ForwardFriction.Force < 0 ? -ForwardFriction.Force : ForwardFriction.Force; + // float absSideForce = SidewayFriction.Force < 0 ? -SidewayFriction.Force : SidewayFriction.Force; + // float f = absForwardForce * cosBeta * cosBeta + absSideForce * sinBeta * sinBeta; - if ( absForwardSpeed > 0.01f || absAngularVelocity > 0.01f ) - { + // ForwardFriction.Force = 0.5f * ForwardFriction.Force - f * cosBeta; + // SidewayFriction.Force = 0.5f * SidewayFriction.Force - f * sinBeta; + //} - float forwardSlipPercent = ForwardFriction.Slip / Tire.GetPeakSlip(); - float sideSlipPercent = SidewayFriction.Slip / Tire.GetPeakSlip(); - float slipCircleLimit = MathF.Sqrt( forwardSlipPercent * forwardSlipPercent + sideSlipPercent * sideSlipPercent ); - if ( slipCircleLimit > 1f ) - { - float beta = MathF.Atan2( sideSlipPercent, forwardSlipPercent * 1.05f ); - float sinBeta = MathF.Sin( beta ); - float cosBeta = MathF.Cos( beta ); + //var slipVector = new Vector2( + // ForwardFriction.Slip, + // SidewayFriction.Slip + //); + //if ( slipVector.Length > 0.001f ) + //{ + // var frictionDirection = slipVector.Normal; - float absForwardForce = ForwardFriction.Force < 0 ? -ForwardFriction.Force : ForwardFriction.Force; + // float frictionMagnitude = MathF.Sqrt( + // ForwardFriction.Force * ForwardFriction.Force + + // SidewayFriction.Force * SidewayFriction.Force + // ); - float absSideForce = SidewayFriction.Force < 0 ? -SidewayFriction.Force : SidewayFriction.Force; - float f = absForwardForce * cosBeta * cosBeta + absSideForce * sinBeta * sinBeta; + // ForwardFriction.Force = Math.Abs( frictionDirection.x ) * frictionMagnitude * Math.Sign( ForwardFriction.Force ); + // SidewayFriction.Force = Math.Abs( frictionDirection.y ) * frictionMagnitude * Math.Sign( SidewayFriction.Force ); + //} + + var f = MathF.Sqrt( ForwardFriction.Force * ForwardFriction.Force + SidewayFriction.Force * SidewayFriction.Force ); + var d = Math.Abs( new Vector2( ForwardFriction.Slip, SidewayFriction.Slip ).Normal.y ); + SidewayFriction.Force = f * d * Math.Sign( SidewayFriction.Force ); - ForwardFriction.Force = 0.5f * ForwardFriction.Force - f * cosBeta; - SidewayFriction.Force = 0.5f * SidewayFriction.Force - f * sinBeta; - } - } if ( IsOnGround ) { FrictionForce.x = (hitSidewaysDirection.x * SidewayFriction.Force + hitForwardDirection.x * ForwardFriction.Force).MeterToInch(); FrictionForce.y = (hitSidewaysDirection.y * SidewayFriction.Force + hitForwardDirection.y * ForwardFriction.Force).MeterToInch(); FrictionForce.z = (hitSidewaysDirection.z * SidewayFriction.Force + hitForwardDirection.z * ForwardFriction.Force).MeterToInch(); - //DebugOverlay.Normal( WorldPosition, hitSidewaysDirection * 10, overlay: true ); - //DebugOverlay.Normal( WorldPosition, hitForwardDirection * 10, overlay: true ); - //DebugOverlay.Normal( WorldPosition, FrictionForce / 100, overlay: true ); - //DebugOverlay.Normal( ContactPosition, Vector3.Up * AngularVelocity, overlay: true ); - - //DebugOverlay.Sphere( new( ContactPosition, 4 ), overlay: true ); - //Vehicle.Body.ApplyForceAt( ContactPosition, FrictionForce ); + //DebugOverlay.Normal( WorldPosition, hitSidewaysDirection * 10, overlay: true, color: Color.Red ); + //DebugOverlay.Normal( WorldPosition, hitForwardDirection * 10, overlay: true, color: Color.Green ); + //DebugOverlay.Normal( WorldPosition, FrictionForce.ClampLength( 30 ), overlay: true, color: Color.Cyan ); + //DebugOverlay.ScreenText( Scene.Camera.PointToScreenPixels( WorldPosition ), $"{ForwardFriction}\nMotor:{(int)motorTorque}\nBrake:{(int)brakeTorque}", flags: TextFlag.LeftTop ); } else FrictionForce = Vector3.Zero; diff --git a/Code/Base/Wheel/VeloXWheel.cs b/Code/Base/Wheel/VeloXWheel.cs index 258d66c..3ef287a 100644 --- a/Code/Base/Wheel/VeloXWheel.cs +++ b/Code/Base/Wheel/VeloXWheel.cs @@ -208,39 +208,8 @@ public partial class VeloXWheel : Component StepRotation( Vehicle, dt ); } - const float HubCoulombNm = 20f; - const float HubViscous = 0.1f; private void StepRotation( VeloXBase vehicle, in float dt ) { - float inertia = MathF.Max( 1f, Inertia ); - float roadTorque = ForwardFriction.Speed * Radius; - float externalTorque = DriveTorque - roadTorque; - float rollingResistanceTorque = Fz * Radius * SurfaceResistance; - - float coulombTorque = BrakeTorque + rollingResistanceTorque + HubCoulombNm; - - float omega = AngularVelocity; - - if ( MathF.Abs( omega ) < 1e-6f && MathF.Abs( externalTorque ) <= coulombTorque ) - { - AngularVelocity = 0f; - } - else - { - if ( HubViscous > 0f ) omega *= MathF.Exp( -(HubViscous / inertia) * dt ); - - if ( coulombTorque > 0f && omega != 0f ) - { - float dir = MathF.Sign( omega ); - float deltaOmega = (coulombTorque / inertia) * dt; - if ( deltaOmega >= MathF.Abs( omega ) ) omega = 0f; - else omega -= dir * deltaOmega; - } - - if ( MathF.Abs( omega ) < 0.01f ) omega = 0f; - } - AngularVelocity = omega; - RollAngle += MathX.RadianToDegree( AngularVelocity ) * dt; RollAngle = (RollAngle % 360f + 360f) % 360f; } diff --git a/Code/Base/Wheel/WheelManager.cs b/Code/Base/Wheel/WheelManager.cs index 9d4e70a..9b093ed 100644 --- a/Code/Base/Wheel/WheelManager.cs +++ b/Code/Base/Wheel/WheelManager.cs @@ -1,5 +1,6 @@ using Sandbox; using System.Linq; +using static Sandbox.Services.Inventory; namespace VeloX; internal sealed class WheelManager : GameObjectSystem @@ -27,6 +28,9 @@ internal sealed class WheelManager : GameObjectSystem if ( !item.IsProxy && item.IsValid() ) item.DoPhysics( timeDelta ); } ); + //foreach ( var item in wheels ) + // if ( !item.IsProxy && item.IsValid() ) + // item.DoPhysics( timeDelta ); foreach ( var wheel in wheels ) if ( !wheel.IsProxy && wheel.IsValid() ) @@ -48,9 +52,15 @@ internal sealed class WheelManager : GameObjectSystem var timeDelta = Time.Delta; Sandbox.Utility.Parallel.ForEach( engines, item => { - if ( !item.IsProxy && item.IsValid() ) - item.UpdateEngine( timeDelta ); + foreach ( var wheel in engines ) + if ( !wheel.IsProxy && wheel.IsValid() ) + wheel.UpdateEngine( timeDelta ); } ); + //foreach ( var wheel in engines ) + // if ( !wheel.IsProxy && wheel.IsValid() ) + // wheel.UpdateEngine( timeDelta ); + + //sw.Stop(); //DebugOverlaySystem.Current.ScreenText( new Vector2( 120, 54 ), $"Engine Sim: {sw.Elapsed.TotalMilliseconds,6:F2} ms", 24 ); diff --git a/Code/Car/VeloXCar.ABS.cs b/Code/Car/VeloXCar.ABS.cs index 912db1c..dbfb9dc 100644 --- a/Code/Car/VeloXCar.ABS.cs +++ b/Code/Car/VeloXCar.ABS.cs @@ -15,7 +15,7 @@ public partial class VeloXCar if ( !UseABS ) return; - if ( TotalSpeed < 100 || ABSActive || Steering.AlmostEqual( 0, 1 ) ) + if ( TotalSpeed < 100 || Steering.AlmostEqual( 0, 1 ) ) return; @@ -26,10 +26,10 @@ public partial class VeloXCar if ( !wheel.IsOnGround ) continue; - if ( wheel.NormalizedLongitudinalSlip >= 0.55f ) + if ( wheel.wheelIsBlocked ) { ABSActive = true; - wheel.BrakeTorque *= 0.25f; + wheel.BrakeTorque = 0f; } } diff --git a/Code/Car/VeloXCar.ESC.cs b/Code/Car/VeloXCar.ESC.cs index 58a1d9b..a06640a 100644 --- a/Code/Car/VeloXCar.ESC.cs +++ b/Code/Car/VeloXCar.ESC.cs @@ -24,6 +24,15 @@ public partial class VeloXCar angle -= SteerAngle.yaw * 0.5f; float absAngle = angle < 0 ? -angle : angle; + //foreach ( var wheel in Wheels ) + //{ + // if ( !wheel.IsOnGround ) + // continue; + // Log.Info( wheel.LongitudinalSlip < 0.1f ); + // if ( wheel.LongitudinalSlip < 0.1f ) + // wheel.BrakeTorque += Math.Abs( wheel.LongitudinalSlip ) * 10000; + //} + if ( Engine.RevLimiterActive || absAngle < 2f ) return; @@ -32,7 +41,6 @@ public partial class VeloXCar if ( !wheel.IsOnGround ) continue; - float additionalBrakeTorque = -angle * Math.Sign( wheel.LocalPosition.y ) * 20f; if ( additionalBrakeTorque > 0 ) { diff --git a/Code/Car/VeloXCar.cs b/Code/Car/VeloXCar.cs index 4ea4f87..cf8a13b 100644 --- a/Code/Car/VeloXCar.cs +++ b/Code/Car/VeloXCar.cs @@ -38,7 +38,7 @@ public partial class VeloXCar : VeloXBase if ( Math.Abs( inputSteer ) < 0.1f ) return; - if ( IsOnGround && Math.Abs( WorldRotation.Roll() ) < 60 ) + if ( IsOnGround || Math.Abs( WorldRotation.Roll() ) < 60 ) return; var angVel = Body.WorldTransform.PointToLocal( WorldPosition + Body.AngularVelocity );