Compare commits
7 Commits
ba9afba4d1
...
0905876b99
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0905876b99 | ||
|
|
55a178e8c5 | ||
|
|
f0f89ff947 | ||
|
|
4912d0ae1a | ||
|
|
4899a38265 | ||
|
|
629ae6715c | ||
|
|
964b46e1c5 |
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"Longitudinal": {
|
|
||||||
"B": 18,
|
|
||||||
"C": 1.5,
|
|
||||||
"D": 1.5,
|
|
||||||
"E": 0.3
|
|
||||||
},
|
|
||||||
"Lateral": {
|
|
||||||
"B": 12,
|
|
||||||
"C": 1.3,
|
|
||||||
"D": 1.8,
|
|
||||||
"E": -1.8
|
|
||||||
},
|
|
||||||
"Aligning": {
|
|
||||||
"B": 2.8,
|
|
||||||
"C": 2.1,
|
|
||||||
"D": 0.1,
|
|
||||||
"E": -2.5
|
|
||||||
},
|
|
||||||
"__references": [],
|
|
||||||
"__version": 0
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"Longitudinal": {
|
|
||||||
"B": 0,
|
|
||||||
"C": 1,
|
|
||||||
"D": 1,
|
|
||||||
"E": 0.3
|
|
||||||
},
|
|
||||||
"Lateral": {
|
|
||||||
"B": 1,
|
|
||||||
"C": 1,
|
|
||||||
"D": 1,
|
|
||||||
"E": 0.3
|
|
||||||
},
|
|
||||||
"Aligning": {
|
|
||||||
"B": 2.8,
|
|
||||||
"C": 2.1,
|
|
||||||
"D": 0.1,
|
|
||||||
"E": -2.5
|
|
||||||
},
|
|
||||||
"__references": [],
|
|
||||||
"__version": 0
|
|
||||||
}
|
|
||||||
20
Assets/frictions/default.tire
Normal file
20
Assets/frictions/default.tire
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"Pacejka": {
|
||||||
|
"Lateral": {
|
||||||
|
"B": 12,
|
||||||
|
"C": 1.3,
|
||||||
|
"D": 1.8,
|
||||||
|
"E": -1.8
|
||||||
|
},
|
||||||
|
"Longitudinal": {
|
||||||
|
"B": 10.86,
|
||||||
|
"C": 2.15,
|
||||||
|
"D": 2,
|
||||||
|
"E": 0.992
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RollResistanceLin": 0.001,
|
||||||
|
"RollResistanceQuad": 1E-06,
|
||||||
|
"__references": [],
|
||||||
|
"__version": 0
|
||||||
|
}
|
||||||
49
Code/Base/Powertrain/Clutch.cs
Normal file
49
Code/Base/Powertrain/Clutch.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Code/Base/Powertrain/Differential/BaseDifferential.cs
Normal file
79
Code/Base/Powertrain/Differential/BaseDifferential.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace VeloX.Powertrain;
|
||||||
|
|
||||||
|
[Category( "VeloX/Powertrain/Differential" )]
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
Code/Base/Powertrain/Differential/OpenDifferential.cs
Normal file
13
Code/Base/Powertrain/Differential/OpenDifferential.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
109
Code/Base/Powertrain/Engine.cs
Normal file
109
Code/Base/Powertrain/Engine.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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 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;
|
||||||
|
public float[] friction = [15.438f, 2.387f, 0.7958f];
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
|
{
|
||||||
|
base.OnStart();
|
||||||
|
|
||||||
|
StreamPlayer = new( Stream );
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetFrictionTorque( float throttle, float rpm )
|
||||||
|
{
|
||||||
|
float s = rpm < 0 ? -1f : 1f;
|
||||||
|
float r = s * rpm * 0.001f;
|
||||||
|
float f = friction[0] + friction[1] * r + friction[2] * r * r;
|
||||||
|
return -s * f * (1 - throttle);
|
||||||
|
}
|
||||||
|
private float GenerateTorque()
|
||||||
|
{
|
||||||
|
float throttle = Throttle;
|
||||||
|
float rpm = RPM;
|
||||||
|
float friction = GetFrictionTorque( throttle, rpm );
|
||||||
|
|
||||||
|
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 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
45
Code/Base/Powertrain/Gearbox/BaseGearbox.cs
Normal file
45
Code/Base/Powertrain/Gearbox/BaseGearbox.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Code/Base/Powertrain/Gearbox/ManualGearbox.cs
Normal file
52
Code/Base/Powertrain/Gearbox/ManualGearbox.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Code/Base/Powertrain/PowerWheel.cs
Normal file
34
Code/Base/Powertrain/PowerWheel.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace VeloX.Powertrain;
|
||||||
|
|
||||||
|
public class PowerWheel : PowertrainComponent
|
||||||
|
{
|
||||||
|
[Property] public VeloXWheel Wheel { get; set; }
|
||||||
|
|
||||||
|
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 )
|
||||||
|
{
|
||||||
|
return Wheel.AngularVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float ForwardStep( float torque, float inertia )
|
||||||
|
{
|
||||||
|
Wheel.AutoPhysics = false;
|
||||||
|
Wheel.Torque = torque;
|
||||||
|
Wheel.Brake = Vehicle.Brake;
|
||||||
|
Inertia = Wheel.BaseInertia + inertia;
|
||||||
|
Wheel.Inertia = inertia;
|
||||||
|
Wheel.DoPhysics( Vehicle );
|
||||||
|
|
||||||
|
angularVelocity = Wheel.AngularVelocity;
|
||||||
|
|
||||||
|
return Wheel.CounterTorque;
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Code/Base/Powertrain/PowertrainComponent.cs
Normal file
100
Code/Base/Powertrain/PowertrainComponent.cs
Normal 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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Sandbox;
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
|
|
||||||
@@ -17,13 +18,22 @@ public abstract partial class VeloXBase
|
|||||||
angForce.y = angVel.y * drag.y * mass;
|
angForce.y = angVel.y * drag.y * mass;
|
||||||
angForce.z = angVel.z * drag.z * mass;
|
angForce.z = angVel.z * drag.z * mass;
|
||||||
|
|
||||||
|
|
||||||
if ( Wheels.Count > 0 )
|
if ( Wheels.Count > 0 )
|
||||||
{
|
|
||||||
|
|
||||||
var dt = Time.Delta;
|
|
||||||
foreach ( var v in Wheels )
|
foreach ( var v in Wheels )
|
||||||
v.DoPhysics( this, in dt );
|
if ( v.AutoPhysics ) v.DoPhysics( this );
|
||||||
|
|
||||||
|
var totalSpeed = TotalSpeed + Math.Abs( Body.AngularVelocity.z );
|
||||||
|
|
||||||
|
var factor = 1 - Math.Clamp( totalSpeed / 30, 0, 1 );
|
||||||
|
|
||||||
|
if ( factor > 0.1f )
|
||||||
|
{
|
||||||
|
var vel = Body.Velocity;
|
||||||
|
|
||||||
|
var rt = WorldRotation.Right;
|
||||||
|
|
||||||
|
var force = rt.Dot( vel ) / Time.Delta * mass * factor * rt;
|
||||||
|
Body.ApplyForce( -force );
|
||||||
}
|
}
|
||||||
|
|
||||||
Body.ApplyTorque( angForce );
|
Body.ApplyTorque( angForce );
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public abstract partial class VeloXBase : Component
|
|||||||
[Property, Group( "Components" )] public Rigidbody Body { get; protected set; }
|
[Property, Group( "Components" )] public Rigidbody Body { get; protected set; }
|
||||||
[Property, Group( "Components" )] public Collider Collider { get; protected set; }
|
[Property, Group( "Components" )] public Collider Collider { get; protected set; }
|
||||||
|
|
||||||
[Sync] public Angles SteerAngle { get; set; }
|
[Sync( SyncFlags.Interpolate )] public Angles SteerAngle { get; set; }
|
||||||
|
|
||||||
public Vector3 LocalVelocity;
|
public Vector3 LocalVelocity;
|
||||||
public float ForwardSpeed;
|
public float ForwardSpeed;
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace VeloX;
|
|
||||||
public class FrictionPreset
|
|
||||||
{
|
|
||||||
public float B { get; set; } = 10.86f;
|
|
||||||
public float C { get; set; } = 2.15f;
|
|
||||||
public float D { get; set; } = 0.933f;
|
|
||||||
public float E { get; set; } = 0.992f;
|
|
||||||
public float Evaluate( float slip )
|
|
||||||
{
|
|
||||||
var t = Math.Abs( slip );
|
|
||||||
|
|
||||||
return D * MathF.Sin( C * MathF.Atan( B * t - E * (B * t - MathF.Atan( B * t )) ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
Code/Base/Wheel/Pacejka.cs
Normal file
43
Code/Base/Wheel/Pacejka.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Services;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace VeloX;
|
||||||
|
public class Pacejka
|
||||||
|
{
|
||||||
|
public class PacejkaPreset
|
||||||
|
{
|
||||||
|
[KeyProperty] public float B { get; set; } = 10.86f;
|
||||||
|
[KeyProperty] public float C { get; set; } = 2.15f;
|
||||||
|
[KeyProperty] public float D { get; set; } = 0.933f;
|
||||||
|
[KeyProperty] public float E { get; set; } = 0.992f;
|
||||||
|
|
||||||
|
public float Evaluate( float slip ) => D * MathF.Sin( C * MathF.Atan( B * slip - E * (B * slip - MathF.Atan( B * slip )) ) );
|
||||||
|
|
||||||
|
public float GetPeakSlip()
|
||||||
|
{
|
||||||
|
float peakSlip = -1;
|
||||||
|
float yMax = 0;
|
||||||
|
|
||||||
|
for ( float i = 0; i < 1f; i += 0.01f )
|
||||||
|
{
|
||||||
|
float y = Evaluate( i );
|
||||||
|
if ( y > yMax )
|
||||||
|
{
|
||||||
|
yMax = y;
|
||||||
|
peakSlip = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return peakSlip;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacejkaPreset Lateral { get; set; } = new();
|
||||||
|
public PacejkaPreset Longitudinal { get; set; } = new();
|
||||||
|
|
||||||
|
public float PacejkaFx( float slip ) => Longitudinal.Evaluate( slip );
|
||||||
|
public float PacejkaFy( float slip ) => Lateral.Evaluate( slip );
|
||||||
|
}
|
||||||
18
Code/Base/Wheel/Suspension/BasicSuspension.cs
Normal file
18
Code/Base/Wheel/Suspension/BasicSuspension.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace VeloX;
|
||||||
|
|
||||||
|
public class BasicSuspension
|
||||||
|
{
|
||||||
|
private readonly Hinge Hinge;
|
||||||
|
|
||||||
|
public BasicSuspension( Vector3 wheel, Vector3 hingeBody, Vector3 hingeWheel )
|
||||||
|
{
|
||||||
|
Vector3 hingePoint = wheel - (hingeWheel - hingeBody);
|
||||||
|
Hinge = new Hinge( hingePoint, hingeWheel - hingeBody );
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void GetWheelTransform( float travel, out Rotation rotation, out Vector3 position )
|
||||||
|
{
|
||||||
|
rotation = Rotation.Identity;
|
||||||
|
position = Hinge.Rotate( travel );
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Code/Base/Wheel/Suspension/Hinge.cs
Normal file
26
Code/Base/Wheel/Suspension/Hinge.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace VeloX;
|
||||||
|
|
||||||
|
internal readonly struct Hinge( Vector3 hinge_anchor, Vector3 hinge_arm )
|
||||||
|
{
|
||||||
|
[Description( "the point that the wheels are rotated around as the suspension compresses" )]
|
||||||
|
public readonly Vector3 Anchor = hinge_anchor;
|
||||||
|
|
||||||
|
[Description( "anchor to wheel vector" )]
|
||||||
|
public readonly Vector3 Arm = hinge_arm;
|
||||||
|
|
||||||
|
[Description( "arm length squared" )]
|
||||||
|
public readonly float LengthSquared = hinge_arm.Dot( hinge_arm );
|
||||||
|
|
||||||
|
[Description( "1 / arm length in hinge axis normal plane" )]
|
||||||
|
public readonly float NormXY = 1 / MathF.Sqrt( hinge_arm.x * hinge_arm.x + hinge_arm.y * hinge_arm.y );
|
||||||
|
|
||||||
|
public readonly Vector3 Rotate( float travel )
|
||||||
|
{
|
||||||
|
float z = Arm.z + travel;
|
||||||
|
float lengthSq = MathF.Max( LengthSquared - z * z, 0.0f );
|
||||||
|
float nxy = NormXY * MathF.Sqrt( lengthSq );
|
||||||
|
return Anchor + new Vector3( Arm.x * nxy, Arm.y * nxy, z );
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Code/Base/Wheel/Suspension/MacPhersonSuspension.cs
Normal file
32
Code/Base/Wheel/Suspension/MacPhersonSuspension.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace VeloX;
|
||||||
|
public class MacPhersonSuspension
|
||||||
|
{
|
||||||
|
public readonly Vector3 WheelOffset;
|
||||||
|
public readonly Vector3 UprightTop;
|
||||||
|
public readonly Vector3 UprightAxis;
|
||||||
|
private readonly Hinge Hinge;
|
||||||
|
|
||||||
|
public MacPhersonSuspension( Vector3 wheel, Vector3 strutBody, Vector3 strutWheel, Vector3 hingeBody )
|
||||||
|
{
|
||||||
|
WheelOffset = wheel - strutWheel;
|
||||||
|
UprightTop = strutBody;
|
||||||
|
UprightAxis = (strutBody - strutWheel).Normal;
|
||||||
|
Hinge = new( hingeBody, strutWheel - hingeBody );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GetWheelTransform( float travel, out Rotation rotation, out Vector3 position )
|
||||||
|
{
|
||||||
|
Vector3 hingeEnd = Hinge.Rotate( travel );
|
||||||
|
Vector3 uprightAxisNew = (UprightTop - hingeEnd).Normal;
|
||||||
|
|
||||||
|
rotation = Rotation.FromAxis(
|
||||||
|
Vector3.Cross( UprightAxis, uprightAxisNew ),
|
||||||
|
MathF.Acos( Vector3.Dot( UprightAxis, uprightAxisNew ) ).RadianToDegree()
|
||||||
|
);
|
||||||
|
|
||||||
|
position = hingeEnd + WheelOffset.Transform( rotation );
|
||||||
|
}
|
||||||
|
}
|
||||||
60
Code/Base/Wheel/TirePreset.cs
Normal file
60
Code/Base/Wheel/TirePreset.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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 static void ComputeSlip( float lon_velocity, float lat_velocity, float rot_velocity, float wheel_radius, out float slip_ratio, out float slip_angle )
|
||||||
|
{
|
||||||
|
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 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 static float ComputeCamberVelocity( float sa, float vx )
|
||||||
|
{
|
||||||
|
float tansa = (1 / 3.0f * (sa * sa) + 1) * sa;
|
||||||
|
return tansa * vx;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Code/Base/Wheel/VeloXWheel.Sound.cs
Normal file
28
Code/Base/Wheel/VeloXWheel.Sound.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace VeloX;
|
||||||
|
|
||||||
|
|
||||||
|
public partial class VeloXWheel : Component
|
||||||
|
{
|
||||||
|
private RealTimeUntil expandSoundCD;
|
||||||
|
private RealTimeUntil contractSoundCD;
|
||||||
|
private void DoSuspensionSounds( VeloXBase vehicle, float change )
|
||||||
|
{
|
||||||
|
if ( change > 0.1f && expandSoundCD )
|
||||||
|
{
|
||||||
|
expandSoundCD = 0.3f;
|
||||||
|
var sound = Sound.Play( vehicle.SuspensionUpSound, WorldPosition );
|
||||||
|
sound.Volume = Math.Clamp( Math.Abs( change ) * 5f, 0, 0.5f );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( change < -0.1f && contractSoundCD )
|
||||||
|
{
|
||||||
|
contractSoundCD = 0.3f;
|
||||||
|
change = MathF.Abs( change );
|
||||||
|
var sound = Sound.Play( change > 0.3f ? vehicle.SuspensionHeavySound : vehicle.SuspensionDownSound, WorldPosition );
|
||||||
|
sound.Volume = Math.Clamp( change * 5f, 0, 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
using Sandbox;
|
using Sandbox;
|
||||||
|
using Sandbox.Rendering;
|
||||||
|
using Sandbox.Services;
|
||||||
using Sandbox.UI;
|
using Sandbox.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Text;
|
using System.Collections.Specialized;
|
||||||
|
using System.Diagnostics.Metrics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.Intrinsics.Arm;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using static Sandbox.CameraComponent;
|
||||||
|
using static Sandbox.Package;
|
||||||
|
using static Sandbox.SkinnedModelRenderer;
|
||||||
|
|
||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
|
|
||||||
@@ -18,18 +23,10 @@ public partial class VeloXWheel : Component
|
|||||||
[Property] public float Mass { get; set; } = 20;
|
[Property] public float Mass { get; set; } = 20;
|
||||||
[Property] public float RollingResistance { get; set; } = 20;
|
[Property] public float RollingResistance { get; set; } = 20;
|
||||||
[Property] public float SlipCircleShape { get; set; } = 1.05f;
|
[Property] public float SlipCircleShape { get; set; } = 1.05f;
|
||||||
|
[Property] public TirePreset TirePreset { get; set; }
|
||||||
public FrictionPreset LongitudinalFrictionPreset => WheelFriction.Longitudinal;
|
|
||||||
public FrictionPreset LateralFrictionPreset => WheelFriction.Lateral;
|
|
||||||
public FrictionPreset AligningFrictionPreset => WheelFriction.Aligning;
|
|
||||||
|
|
||||||
[Property] public WheelFriction WheelFriction { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
[Property] public float Width { get; set; } = 6;
|
[Property] public float Width { get; set; } = 6;
|
||||||
[Sync] public float SideSlip { get; private set; }
|
public float SideSlip { get; private set; }
|
||||||
[Sync] public float ForwardSlip { get; private set; }
|
public float ForwardSlip { get; private set; }
|
||||||
[Sync, Range( 0, 1 )] public float BrakePower { get; set; }
|
|
||||||
[Sync] public float Torque { get; set; }
|
[Sync] public float Torque { get; set; }
|
||||||
[Sync, Range( 0, 1 )] public float Brake { get; set; }
|
[Sync, Range( 0, 1 )] public float Brake { get; set; }
|
||||||
[Property] float BrakePowerMax { get; set; } = 3000;
|
[Property] float BrakePowerMax { get; set; } = 3000;
|
||||||
@@ -38,14 +35,19 @@ public partial class VeloXWheel : Component
|
|||||||
[Property] public float CasterAngle { get; set; } = 7; // todo
|
[Property] public float CasterAngle { get; set; } = 7; // todo
|
||||||
[Property] public float CamberAngle { get; set; } = -3;
|
[Property] public float CamberAngle { get; set; } = -3;
|
||||||
[Property] public float ToeAngle { get; set; } = 0.5f;
|
[Property] public float ToeAngle { get; set; } = 0.5f;
|
||||||
|
[Property] public float Ackermann { get; set; } = 0;
|
||||||
|
|
||||||
[Property, Group( "Suspension" )] float SuspensionLength { get; set; } = 10;
|
[Property, Group( "Suspension" )] float SuspensionLength { get; set; } = 10;
|
||||||
[Property, Group( "Suspension" )] float SpringStrength { get; set; } = 800;
|
[Property, Group( "Suspension" )] float SpringStrength { get; set; } = 800;
|
||||||
[Property, Group( "Suspension" )] float SpringDamper { get; set; } = 3000;
|
[Property, Group( "Suspension" )] float SpringDamper { get; set; } = 3000;
|
||||||
|
|
||||||
|
[Property] public bool AutoPhysics { get; set; } = true;
|
||||||
|
|
||||||
public float Spin { get; private set; }
|
public float Spin { get; private set; }
|
||||||
|
|
||||||
public float RPM { get => angularVelocity * 60f / MathF.Tau; set => angularVelocity = value / (60 / MathF.Tau); }
|
public float RPM { get => angularVelocity * 30f / MathF.PI; set => angularVelocity = value / (30f / MathF.PI); }
|
||||||
|
public float AngularVelocity { get => angularVelocity; set => angularVelocity = value; }
|
||||||
|
|
||||||
internal float DistributionFactor { get; set; }
|
internal float DistributionFactor { get; set; }
|
||||||
|
|
||||||
private Vector3 StartPos { get; set; }
|
private Vector3 StartPos { get; set; }
|
||||||
@@ -58,20 +60,24 @@ public partial class VeloXWheel : Component
|
|||||||
private float angularVelocity;
|
private float angularVelocity;
|
||||||
private float load;
|
private float load;
|
||||||
private float lastFraction;
|
private float lastFraction;
|
||||||
private RealTimeUntil expandSoundCD;
|
|
||||||
private RealTimeUntil contractSoundCD;
|
|
||||||
|
|
||||||
private Vector3 contactPos;
|
private Vector3 contactPos;
|
||||||
private Vector3 forward;
|
private Vector3 forward;
|
||||||
private Vector3 right;
|
private Vector3 right;
|
||||||
private Vector3 up;
|
private Vector3 up;
|
||||||
|
|
||||||
private Friction forwardFriction;
|
private float forwardFriction;
|
||||||
private Friction sideFriction;
|
private float sideFriction;
|
||||||
private Vector3 force;
|
private Vector3 force;
|
||||||
|
public float CounterTorque { get; private set; }
|
||||||
|
|
||||||
|
internal float BaseInertia => 0.5f * Mass * MathF.Pow( Radius.InchToMeter(), 2 );
|
||||||
|
public float Inertia
|
||||||
|
{
|
||||||
|
get => BaseInertia + inertia;
|
||||||
|
set => inertia = value;
|
||||||
|
}
|
||||||
|
|
||||||
private float BaseInertia => 0.5f * Mass * MathF.Pow( Radius.InchToMeter(), 2 );
|
|
||||||
private float Inertia => BaseInertia;
|
|
||||||
|
|
||||||
protected override void OnAwake()
|
protected override void OnAwake()
|
||||||
{
|
{
|
||||||
@@ -80,24 +86,6 @@ public partial class VeloXWheel : Component
|
|||||||
StartPos = LocalPosition;
|
StartPos = LocalPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DoSuspensionSounds( VeloXBase vehicle, float change )
|
|
||||||
{
|
|
||||||
if ( change > 0.1f && expandSoundCD )
|
|
||||||
{
|
|
||||||
expandSoundCD = 0.3f;
|
|
||||||
var sound = Sound.Play( vehicle.SuspensionUpSound, WorldPosition );
|
|
||||||
sound.Volume = Math.Clamp( Math.Abs( change ) * 5f, 0, 0.5f );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( change < -0.1f && contractSoundCD )
|
|
||||||
{
|
|
||||||
contractSoundCD = 0.3f;
|
|
||||||
change = MathF.Abs( change );
|
|
||||||
var sound = Sound.Play( change > 0.3f ? vehicle.SuspensionHeavySound : vehicle.SuspensionDownSound, WorldPosition );
|
|
||||||
sound.Volume = Math.Clamp( change * 5f, 0, 1 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Update( VeloXBase vehicle, in float dt )
|
internal void Update( VeloXBase vehicle, in float dt )
|
||||||
{
|
{
|
||||||
UpdateVisuals( vehicle, dt );
|
UpdateVisuals( vehicle, dt );
|
||||||
@@ -105,94 +93,102 @@ public partial class VeloXWheel : Component
|
|||||||
|
|
||||||
private void UpdateVisuals( VeloXBase vehicle, in float dt )
|
private void UpdateVisuals( VeloXBase vehicle, in float dt )
|
||||||
{
|
{
|
||||||
var entityAngles = vehicle.WorldRotation;
|
Spin -= angularVelocity.RadianToDegree() * dt;
|
||||||
|
WorldRotation = vehicle.WorldTransform.RotationToWorld( GetSteer( vehicle.SteerAngle.yaw ) ) * Rotation.FromAxis( Vector3.Right, Spin );
|
||||||
Spin -= angularVelocity.MeterToInch() * dt;
|
|
||||||
|
|
||||||
|
|
||||||
var steerRotated = entityAngles.RotateAroundAxis( Vector3.Up, vehicle.SteerAngle.yaw * SteerMultiplier + ToeAngle );
|
|
||||||
var camberRotated = steerRotated.RotateAroundAxis( Vector3.Forward, -CamberAngle );
|
|
||||||
var angularVelocityRotated = camberRotated.RotateAroundAxis( Vector3.Right, Spin );
|
|
||||||
|
|
||||||
WorldRotation = angularVelocityRotated;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private (float, float, float, float) StepLongitudinal( float Vx, float Lc, float kFx, float kSx, float dt )
|
private Rotation GetSteer( float steer )
|
||||||
{
|
{
|
||||||
float Tm = Torque;
|
|
||||||
float Tb = Brake * BrakePowerMax + RollingResistance;
|
|
||||||
float R = Radius.InchToMeter();
|
|
||||||
float I = Inertia;
|
|
||||||
|
|
||||||
float Winit = angularVelocity;
|
float angle = (-steer * SteerMultiplier).DegreeToRadian();
|
||||||
float W = angularVelocity;
|
|
||||||
|
|
||||||
float VxAbs = MathF.Abs( Vx );
|
float t = MathF.Tan( (MathF.PI / 2) - angle ) - Ackermann;
|
||||||
|
float steering_angle = MathF.CopySign( float.Pi / 2, t ) - MathF.Atan( t );
|
||||||
|
var steering_axis = Vector3.Up * MathF.Cos( -CasterAngle.DegreeToRadian() ) +
|
||||||
|
Vector3.Right * MathF.Sin( -CasterAngle.DegreeToRadian() );
|
||||||
|
|
||||||
|
return Rotation.FromAxis( Vector3.Forward, -CamberAngle ) * Rotation.FromAxis( steering_axis, steering_angle.RadianToDegree() );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 ));
|
||||||
|
private float inertia;
|
||||||
|
|
||||||
|
|
||||||
|
private (float, float, float, float) StepLongitudinal( float Tm, float Tb, float Vx, float W, float Lc, float R, float I )
|
||||||
|
{
|
||||||
|
float wInit = W;
|
||||||
|
float vxAbs = Math.Abs( Vx );
|
||||||
float Sx;
|
float Sx;
|
||||||
if ( VxAbs >= 0.1f )
|
if ( Lc < 0.01f )
|
||||||
Sx = (Vx - W * R) / VxAbs;
|
{
|
||||||
|
Sx = 0;
|
||||||
|
}
|
||||||
|
else if ( vxAbs >= 0.01f )
|
||||||
|
{
|
||||||
|
Sx = (W * R - Vx) / vxAbs;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Sx = (Vx - W * R) * 0.6f;
|
{
|
||||||
|
Sx = (W * R - Vx) * 0.6f;
|
||||||
|
}
|
||||||
|
|
||||||
Sx = Math.Clamp( Sx * kSx, -1, 1 );
|
Sx = Math.Clamp( Sx, -1, 1 );
|
||||||
|
|
||||||
W += Tm / I * dt;
|
W += Tm / I * Time.Delta;
|
||||||
|
|
||||||
Tb *= W > 0 ? -1 : 1;
|
Tb *= W > 0 ? -1 : 1;
|
||||||
|
|
||||||
float TbCap = MathF.Abs( W ) * I / dt;
|
float tbCap = Math.Abs( W ) * I / Time.Delta;
|
||||||
float Tbr = MathF.Abs( Tb ) - MathF.Abs( TbCap );
|
float tbr = Math.Abs( Tb ) - Math.Abs( tbCap );
|
||||||
Tbr = MathF.Max( Tbr, 0 );
|
tbr = Math.Max( tbr, 0 );
|
||||||
Tb = Math.Clamp( Tb, -TbCap, TbCap );
|
|
||||||
W += Tb / I * dt;
|
|
||||||
|
|
||||||
float maxTorque = LongitudinalFrictionPreset.Evaluate( Sx ) * Lc * kFx;
|
Tb = Math.Clamp( Tb, -tbCap, tbCap );
|
||||||
|
|
||||||
float errorTorque = (W - Vx / R) * I / dt;
|
W += Tb / I * Time.Delta;
|
||||||
|
|
||||||
float surfaceTorque = MathX.Clamp( errorTorque, -maxTorque, maxTorque );
|
float maxTorque = TirePreset.Pacejka.PacejkaFx( Math.Abs( Sx ) ) * Lc * R;
|
||||||
|
|
||||||
W -= surfaceTorque / I * dt;
|
float errorTorque = (W - Vx / R) * I / Time.Delta;
|
||||||
|
float surfaceTorque = Math.Clamp( errorTorque, -maxTorque, maxTorque );
|
||||||
|
|
||||||
|
W -= surfaceTorque / I * Time.Delta;
|
||||||
float Fx = surfaceTorque / R;
|
float Fx = surfaceTorque / R;
|
||||||
|
|
||||||
|
tbr *= (W > 0 ? -1 : 1);
|
||||||
|
float TbCap2 = Math.Abs( W ) * I / Time.Delta;
|
||||||
|
float Tb2 = Math.Clamp( tbr, -TbCap2, TbCap2 );
|
||||||
|
W += Tb2 / I * Time.Delta;
|
||||||
|
|
||||||
Tbr *= W > 0 ? -1 : 1;
|
float deltaOmegaTorque = (W - wInit) * I / Time.Delta;
|
||||||
float TbCap2 = MathF.Abs( W ) * I / dt;
|
|
||||||
|
|
||||||
float Tb2 = Math.Clamp( Tbr, -TbCap2, TbCap2 );
|
|
||||||
|
|
||||||
W += Tb2 / I * dt;
|
|
||||||
|
|
||||||
float deltaOmegaTorque = (W - Winit) * I / dt;
|
|
||||||
|
|
||||||
float Tcnt = -surfaceTorque + Tb + Tb2 - deltaOmegaTorque;
|
float Tcnt = -surfaceTorque + Tb + Tb2 - deltaOmegaTorque;
|
||||||
if ( Lc < 0.001f )
|
|
||||||
Sx = 0;
|
|
||||||
|
|
||||||
return (W, Sx, Fx, Tcnt);
|
return (W, Sx, Fx, Tcnt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (float, float) StepLateral( float Vx, float Vy, float Lc, float kFy, float kSy, float dt )
|
private void StepLateral( float Vx, float Vy, float Lc, out float Sy, out float Fy )
|
||||||
{
|
{
|
||||||
float VxAbs = MathF.Abs( Vx );
|
float VxAbs = Math.Abs( Vx );
|
||||||
float Sy;
|
|
||||||
|
|
||||||
if ( VxAbs > 0.1f )
|
if ( Lc < 0.01f )
|
||||||
Sy = MathF.Atan( Vy / VxAbs ).RadianToDegree() * 0.01111f;
|
{
|
||||||
else
|
|
||||||
Sy = Vy * (0.003f / dt);
|
|
||||||
|
|
||||||
|
|
||||||
Sy *= kSy * 0.95f;
|
|
||||||
Sy = Math.Clamp( Sy * kSy, -1, 1 );
|
|
||||||
float Fy = -MathF.Sign( Sy ) * LateralFrictionPreset.Evaluate( Sy ) * Lc * kFy;
|
|
||||||
if ( Lc < 0.0001f )
|
|
||||||
Sy = 0;
|
Sy = 0;
|
||||||
|
}
|
||||||
|
else if ( VxAbs > 0.1f )
|
||||||
|
{
|
||||||
|
|
||||||
return (Sy, Fy);
|
Sy = MathX.RadianToDegree( MathF.Atan( Vy / VxAbs ) ) / 50;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
Sy = Vy * (0.003f / Time.Delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sy = Math.Clamp( Sy, -1, 1 );
|
||||||
|
float slipSign = Sy < 0 ? -1 : 1;
|
||||||
|
Fy = -slipSign * TirePreset.Pacejka.PacejkaFy( Math.Abs( Sy ) ) * Lc;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SlipCircle( float Sx, float Sy, float Fx, ref float Fy )
|
private void SlipCircle( float Sx, float Sy, float Fx, ref float Fy )
|
||||||
@@ -200,39 +196,23 @@ public partial class VeloXWheel : Component
|
|||||||
float SxAbs = Math.Abs( Sx );
|
float SxAbs = Math.Abs( Sx );
|
||||||
if ( SxAbs > 0.01f )
|
if ( SxAbs > 0.01f )
|
||||||
{
|
{
|
||||||
|
|
||||||
float SxClamped = Math.Clamp( Sx, -1, 1 );
|
float SxClamped = Math.Clamp( Sx, -1, 1 );
|
||||||
|
|
||||||
float SyClamped = Math.Clamp( Sy, -1, 1 );
|
float SyClamped = Math.Clamp( Sy, -1, 1 );
|
||||||
|
Vector2 combinedSlip = new( SxClamped * 1.05f, SyClamped );
|
||||||
Vector2 combinedSlip = new(
|
|
||||||
SxClamped * SlipCircleShape,
|
|
||||||
SyClamped
|
|
||||||
);
|
|
||||||
|
|
||||||
Vector2 slipDir = combinedSlip.Normal;
|
Vector2 slipDir = combinedSlip.Normal;
|
||||||
|
|
||||||
float F = MathF.Sqrt( Fx * Fx + Fy * Fy );
|
float F = MathF.Sqrt( Fx * Fx + Fy * Fy );
|
||||||
|
float absSlipDirY = Math.Abs( slipDir.y );
|
||||||
|
|
||||||
float absSlipDirY = MathF.Abs( slipDir.y );
|
Fy = F * absSlipDirY * (Fy < 0 ? -1 : 1);
|
||||||
|
|
||||||
Fy = F * absSlipDirY * MathF.Sign( Fy );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DoPhysics( VeloXBase vehicle )
|
||||||
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 )
|
|
||||||
{
|
{
|
||||||
var pos = vehicle.WorldTransform.PointToWorld( StartPos );
|
var pos = vehicle.WorldTransform.PointToWorld( StartPos );
|
||||||
|
|
||||||
var ang = vehicle.WorldTransform.RotationToWorld( vehicle.SteerAngle * SteerMultiplier );
|
var ang = vehicle.WorldTransform.RotationToWorld( GetSteer( vehicle.SteerAngle.yaw ) );
|
||||||
|
|
||||||
forward = ang.Forward;
|
|
||||||
right = ang.Right;
|
|
||||||
up = ang.Up;
|
|
||||||
|
|
||||||
var maxLen = SuspensionLength;
|
var maxLen = SuspensionLength;
|
||||||
|
|
||||||
@@ -242,113 +222,95 @@ public partial class VeloXWheel : Component
|
|||||||
.Cylinder( Width, Radius, pos, endPos )
|
.Cylinder( Width, Radius, pos, endPos )
|
||||||
.Rotated( vehicle.WorldTransform.Rotation * CylinderOffset )
|
.Rotated( vehicle.WorldTransform.Rotation * CylinderOffset )
|
||||||
.UseRenderMeshes( false )
|
.UseRenderMeshes( false )
|
||||||
.UseHitPosition( false )
|
.UseHitPosition( true )
|
||||||
.WithoutTags( vehicle.WheelIgnoredTags )
|
.WithoutTags( vehicle.WheelIgnoredTags )
|
||||||
.Run();
|
.Run();
|
||||||
|
|
||||||
|
forward = Vector3.VectorPlaneProject( ang.Forward, Trace.Normal );
|
||||||
|
right = Vector3.VectorPlaneProject( ang.Right, Trace.Normal );
|
||||||
|
|
||||||
var fraction = Trace.Fraction;
|
var fraction = Trace.Fraction;
|
||||||
|
|
||||||
contactPos = pos - maxLen * fraction * up;
|
contactPos = pos - maxLen * fraction * ang.Up;
|
||||||
|
|
||||||
LocalPosition = vehicle.WorldTransform.PointToLocal( contactPos );
|
LocalPosition = vehicle.WorldTransform.PointToLocal( contactPos );
|
||||||
|
|
||||||
DoSuspensionSounds( vehicle, fraction - lastFraction );
|
DoSuspensionSounds( vehicle, fraction - lastFraction );
|
||||||
lastFraction = fraction;
|
lastFraction = fraction;
|
||||||
|
|
||||||
var normal = Trace.Normal;
|
|
||||||
|
|
||||||
var vel = vehicle.Body.GetVelocityAtPoint( pos );
|
|
||||||
|
|
||||||
vel.x = vel.x.InchToMeter();
|
|
||||||
vel.y = vel.y.InchToMeter();
|
|
||||||
vel.z = vel.z.InchToMeter();
|
|
||||||
|
|
||||||
if ( !IsOnGround )
|
if ( !IsOnGround )
|
||||||
{
|
|
||||||
SideSlip = 0;
|
|
||||||
ForwardSlip = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
var vel = vehicle.Body.GetVelocityAtPoint( contactPos );
|
||||||
|
|
||||||
var offset = maxLen - (fraction * maxLen);
|
var offset = maxLen - (fraction * maxLen);
|
||||||
var springForce = (offset * SpringStrength);
|
var springForce = offset * SpringStrength;
|
||||||
var damperForce = (lastSpringOffset - offset) * SpringDamper;
|
var damperForce = (lastSpringOffset - offset) * SpringDamper;
|
||||||
|
|
||||||
lastSpringOffset = offset;
|
lastSpringOffset = offset;
|
||||||
force = (springForce - damperForce) * MathF.Max( 0, up.Dot( normal ) ) * normal / dt;
|
|
||||||
|
|
||||||
// Vector3.CalculateVelocityOffset is broken (need fix)
|
|
||||||
//var velU = normal.Dot( vel ).MeterToInch();
|
|
||||||
//if ( velU < 0 && offset + Math.Abs( velU * dt ) > SuspensionLength )
|
|
||||||
//{
|
|
||||||
// var (linearVel, angularVel) = vehicle.Body.PhysicsBody.CalculateVelocityOffset( (-velU.InchToMeter() / dt) * normal, pos );
|
|
||||||
// vehicle.Body.Velocity += linearVel;
|
|
||||||
// vehicle.Body.AngularVelocity += angularVel;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
load = springForce - damperForce;
|
var velU = Trace.Normal.Dot( vel );
|
||||||
load = Math.Max( load, 0 );
|
|
||||||
|
|
||||||
var longitudinalLoadCoefficient = GetLongitudinalLoadCoefficient( load );
|
if ( velU < 0 && offset + Math.Abs( velU * Time.Delta ) > SuspensionLength )
|
||||||
var lateralLoadCoefficient = GetLateralLoadCoefficient( load );
|
|
||||||
|
|
||||||
float forwardSpeed = 0;
|
|
||||||
float sideSpeed = 0;
|
|
||||||
|
|
||||||
|
|
||||||
if ( IsOnGround )
|
|
||||||
{
|
{
|
||||||
forwardSpeed = vel.Dot( forward );
|
vehicle.Body.CalculateVelocityOffset( -velU / Time.Delta * Trace.Normal, pos, out var linearImp, out var angularImp );
|
||||||
sideSpeed = vel.Dot( right );
|
|
||||||
|
vehicle.Body.Velocity += linearImp;
|
||||||
|
vehicle.Body.AngularVelocity += angularImp;
|
||||||
|
vehicle.Body.CalculateVelocityOffset( Trace.HitPosition - (contactPos + Trace.Normal * velU * Time.Delta), pos, out var lin, out _ );
|
||||||
|
|
||||||
|
vehicle.WorldPosition += lin / Time.Delta;
|
||||||
|
damperForce = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(float W, float Sx, float Fx, float _) = StepLongitudinal(
|
force = (springForce - damperForce) * Trace.Normal;
|
||||||
forwardSpeed,
|
|
||||||
longitudinalLoadCoefficient,
|
load = Math.Max( springForce - damperForce, 0 );
|
||||||
0.95f,
|
float R = Radius.InchToMeter();
|
||||||
0.9f,
|
|
||||||
dt
|
float forwardSpeed = vel.Dot( forward ).InchToMeter();
|
||||||
|
float sideSpeed = vel.Dot( right ).InchToMeter();
|
||||||
|
|
||||||
|
float longitudinalLoadCoefficient = GetLongitudinalLoadCoefficient( load );
|
||||||
|
float lateralLoadCoefficient = GetLateralLoadCoefficient( load );
|
||||||
|
|
||||||
|
float F_roll = TirePreset.GetRollingResistance( angularVelocity * R, 1.0f ) * 10000;
|
||||||
|
|
||||||
|
( float W, float Sx, float Fx, float counterTq) = StepLongitudinal(
|
||||||
|
Torque,
|
||||||
|
Brake * BrakePowerMax + F_roll,
|
||||||
|
forwardSpeed,
|
||||||
|
angularVelocity,
|
||||||
|
longitudinalLoadCoefficient,
|
||||||
|
R,
|
||||||
|
Inertia
|
||||||
);
|
);
|
||||||
|
|
||||||
(float Sy, float Fy) = StepLateral(
|
StepLateral( forwardSpeed, sideSpeed, lateralLoadCoefficient, out float Sy, out float Fy );
|
||||||
forwardSpeed,
|
|
||||||
sideSpeed,
|
|
||||||
lateralLoadCoefficient,
|
|
||||||
0.95f,
|
|
||||||
0.9f,
|
|
||||||
dt
|
|
||||||
);
|
|
||||||
|
|
||||||
SlipCircle( Sx, Sy, Fx, ref Fy );
|
SlipCircle( Sx, Sy, Fx, ref Fy );
|
||||||
|
|
||||||
|
CounterTorque = counterTq;
|
||||||
angularVelocity = W;
|
angularVelocity = W;
|
||||||
|
|
||||||
forwardFriction = new Friction()
|
force += forward * Fx;
|
||||||
{
|
force += right * Fy * Math.Clamp( vehicle.TotalSpeed * 0.005f, 0, 1 );
|
||||||
Slip = Sx,
|
ForwardSlip = Sx;
|
||||||
Force = Fx.MeterToInch(),
|
SideSlip = Sy * Math.Clamp( vehicle.TotalSpeed * 0.005f, 0, 1 );
|
||||||
Speed = forwardSpeed
|
|
||||||
};
|
|
||||||
|
|
||||||
sideFriction = new Friction()
|
vehicle.Body.ApplyForceAt( pos, force / Time.Delta );
|
||||||
{
|
|
||||||
Slip = Sy,
|
|
||||||
Force = Fy.MeterToInch(),
|
|
||||||
Speed = sideSpeed
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
var frictionforce = right * sideFriction.Force + forward * forwardFriction.Force;
|
|
||||||
|
|
||||||
vehicle.Body.ApplyForceAt( contactPos, force + frictionforce );
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
// debug
|
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
DebugOverlay.Normal( contactPos, forward * forwardFriction.Force / 10000f, Color.Red, overlay: true );
|
DebugOverlay.Normal( contactPos, forward * forwardFriction, Color.Red, overlay: true );
|
||||||
DebugOverlay.Normal( contactPos, right * sideFriction.Force / 10000f, Color.Green, overlay: true );
|
DebugOverlay.Normal( contactPos, right * sideFriction, Color.Green, overlay: true );
|
||||||
DebugOverlay.Normal( contactPos, up * force / 50000f, Color.Blue, overlay: true );
|
DebugOverlay.Normal( contactPos, up * force / 1000f, Color.Blue, overlay: true );
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
using Sandbox;
|
|
||||||
|
|
||||||
namespace VeloX;
|
|
||||||
|
|
||||||
|
|
||||||
[GameResource( "Wheel Friction", "whfric", "Wheel Friction", Category = "VeloX", Icon = "radio_button_checked" )]
|
|
||||||
public class WheelFriction : GameResource
|
|
||||||
{
|
|
||||||
public FrictionPreset Longitudinal { get; set; }
|
|
||||||
public FrictionPreset Lateral { get; set; }
|
|
||||||
public FrictionPreset Aligning { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,354 +1,365 @@
|
|||||||
using Sandbox;
|
using Sandbox;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using VeloX.Powertrain;
|
||||||
|
|
||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
|
|
||||||
public partial class VeloXCar
|
public partial class VeloXCar
|
||||||
{
|
{
|
||||||
[Property, Feature( "Engine" ), Sync] public EngineState EngineState { get; set; }
|
|
||||||
[Property, Feature( "Engine" )] public EngineStream Stream { get; set; }
|
|
||||||
public EngineStreamPlayer StreamPlayer { get; set; }
|
|
||||||
|
|
||||||
[Property, Feature( "Engine" )] public float MinRPM { get; set; } = 800;
|
[Property, Feature( "Engine" )] Engine Engine { get; set; }
|
||||||
[Property, Feature( "Engine" )] public float MaxRPM { get; set; } = 7000;
|
|
||||||
[Property, Feature( "Engine" ), Range( -1, 1 )]
|
|
||||||
public float PowerDistribution
|
|
||||||
{
|
|
||||||
get => powerDistribution; set
|
|
||||||
{
|
|
||||||
powerDistribution = value;
|
|
||||||
UpdatePowerDistribution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Property, Feature( "Engine" )] public float FlyWheelMass { get; set; } = 80f;
|
|
||||||
[Property, Feature( "Engine" )] public float FlyWheelRadius { get; set; } = 0.5f;
|
|
||||||
[Property, Feature( "Engine" )] public float FlywheelFriction { get; set; } = -6000;
|
|
||||||
[Property, Feature( "Engine" )] public float FlywheelTorque { get; set; } = 20000;
|
|
||||||
[Property, Feature( "Engine" )] public float EngineBrakeTorque { get; set; } = 2000;
|
|
||||||
[Property, Feature( "Engine" )]
|
|
||||||
public Dictionary<int, float> Gears { get; set; } = new()
|
|
||||||
{
|
|
||||||
[-1] = 2.5f,
|
|
||||||
[0] = 0f,
|
|
||||||
[1] = 2.8f,
|
|
||||||
[2] = 1.7f,
|
|
||||||
[3] = 1.2f,
|
|
||||||
[4] = 0.9f,
|
|
||||||
[5] = 0.75f,
|
|
||||||
[6] = 0.7f
|
|
||||||
};
|
|
||||||
[Property, Feature( "Engine" )] public float DifferentialRatio { get; set; } = 1f;
|
|
||||||
[Property, Feature( "Engine" ), Range( 0, 1 )] public float TransmissionEfficiency { get; set; } = 0.8f;
|
|
||||||
[Property, Feature( "Engine" )] private float MinRPMTorque { get; set; } = 5000f;
|
|
||||||
[Property, Feature( "Engine" )] private float MaxRPMTorque { get; set; } = 8000f;
|
|
||||||
[Sync] public int Gear { get; set; } = 0;
|
|
||||||
[Sync] public float Clutch { get; set; } = 1;
|
|
||||||
[Sync( SyncFlags.Interpolate )] public float EngineRPM { get; set; }
|
|
||||||
public float RPMPercent => (EngineRPM - MinRPM) / MaxRPM;
|
|
||||||
|
|
||||||
private const float TAU = MathF.Tau;
|
|
||||||
|
|
||||||
private int MinGear { get; set; }
|
|
||||||
private int MaxGear { get; set; }
|
|
||||||
|
|
||||||
[Sync] public bool IsRedlining { get; private set; }
|
|
||||||
|
|
||||||
private float flywheelVelocity;
|
|
||||||
private TimeUntil switchCD = 0;
|
|
||||||
private float groundedCount;
|
|
||||||
private float burnout;
|
|
||||||
|
|
||||||
private float frontBrake;
|
|
||||||
private float rearBrake;
|
|
||||||
|
|
||||||
private float availableFrontTorque;
|
|
||||||
private float availableRearTorque;
|
|
||||||
|
|
||||||
private float avgSideSlip;
|
|
||||||
private float avgPoweredRPM;
|
|
||||||
private float avgForwardSlip;
|
|
||||||
|
|
||||||
private float inputThrottle, inputBrake;
|
|
||||||
private bool inputHandbrake;
|
|
||||||
private float transmissionRPM;
|
|
||||||
private float powerDistribution;
|
|
||||||
|
|
||||||
public float FlywheelRPM
|
|
||||||
{
|
|
||||||
get => flywheelVelocity * 60 / TAU;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
flywheelVelocity = value * TAU / 60; EngineRPM = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void UpdateGearList()
|
|
||||||
{
|
|
||||||
int minGear = 0;
|
|
||||||
int maxGear = 0;
|
|
||||||
|
|
||||||
foreach ( var (gear, ratio) in Gears )
|
|
||||||
{
|
|
||||||
|
|
||||||
if ( gear < minGear )
|
|
||||||
minGear = gear;
|
|
||||||
|
|
||||||
if ( gear > maxGear )
|
|
||||||
maxGear = gear;
|
|
||||||
|
|
||||||
if ( minGear != 0 || maxGear != 0 )
|
|
||||||
{
|
|
||||||
SwitchGear( 0, false );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MinGear = minGear;
|
|
||||||
MaxGear = maxGear;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void SwitchGear( int index, bool cooldown = true )
|
|
||||||
{
|
|
||||||
if ( Gear == index ) return;
|
|
||||||
index = Math.Clamp( index, MinGear, MaxGear );
|
|
||||||
|
|
||||||
if ( index == 0 || !cooldown )
|
|
||||||
switchCD = 0;
|
|
||||||
else
|
|
||||||
switchCD = 0.3f;
|
|
||||||
Clutch = 1;
|
|
||||||
Gear = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float TransmissionToEngineRPM( int gear ) => avgPoweredRPM * Gears[gear] * DifferentialRatio * 60 / TAU;
|
|
||||||
public float GetTransmissionMaxRPM( int gear ) => FlywheelRPM / Gears[gear] / DifferentialRatio;
|
|
||||||
|
|
||||||
private void UpdatePowerDistribution()
|
|
||||||
{
|
|
||||||
if ( Wheels is null ) return;
|
|
||||||
int frontCount = 0, rearCount = 0;
|
|
||||||
|
|
||||||
foreach ( var wheel in Wheels )
|
|
||||||
{
|
|
||||||
if ( wheel.IsFront )
|
|
||||||
frontCount++;
|
|
||||||
else
|
|
||||||
rearCount++;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
float frontDistribution = 0.5f + PowerDistribution * 0.5f;
|
|
||||||
|
|
||||||
float rearDistribution = 1 - frontDistribution;
|
|
||||||
|
|
||||||
frontDistribution /= frontCount;
|
|
||||||
rearDistribution /= rearCount;
|
|
||||||
|
|
||||||
foreach ( var wheel in Wheels )
|
|
||||||
if ( wheel.IsFront )
|
|
||||||
wheel.DistributionFactor = frontDistribution;
|
|
||||||
else
|
|
||||||
wheel.DistributionFactor = rearDistribution;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EngineAccelerate( float torque, float dt )
|
|
||||||
{
|
|
||||||
var inertia = 0.5f * FlyWheelMass * FlyWheelRadius * FlyWheelRadius;
|
|
||||||
var angularAcceleration = torque / inertia;
|
|
||||||
|
|
||||||
flywheelVelocity += angularAcceleration * dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float GetTransmissionTorque( int gear, float minTorque, float maxTorque )
|
|
||||||
{
|
|
||||||
var torque = FlywheelRPM.Remap( MinRPM, MaxRPM, minTorque, maxTorque, true );
|
|
||||||
torque *= (1 - Clutch);
|
|
||||||
torque = torque * Gears[gear] * DifferentialRatio * TransmissionEfficiency;
|
|
||||||
|
|
||||||
return gear == -1 ? -torque : torque;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AutoGearSwitch()
|
|
||||||
{
|
|
||||||
if ( ForwardSpeed < 100 && Input.Down( "Backward" ) )
|
|
||||||
{
|
|
||||||
SwitchGear( -1, false );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentGear = Gear;
|
|
||||||
|
|
||||||
if ( currentGear < 0 && ForwardSpeed < -100 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ( Math.Abs( avgForwardSlip ) > 10 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
var gear = Math.Clamp( currentGear, 1, MaxGear );
|
|
||||||
|
|
||||||
float minRPM = MinRPM, maxRPM = MaxRPM;
|
|
||||||
|
|
||||||
|
|
||||||
maxRPM *= 0.98f;
|
|
||||||
|
|
||||||
float gearRPM;
|
|
||||||
|
|
||||||
|
|
||||||
for ( int i = 1; i <= MaxGear; i++ )
|
|
||||||
{
|
|
||||||
gearRPM = TransmissionToEngineRPM( i );
|
|
||||||
|
|
||||||
if ( (i == 1 && gearRPM < minRPM) || (gearRPM > minRPM && gearRPM < maxRPM) )
|
|
||||||
{
|
|
||||||
gear = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var threshold = minRPM + (maxRPM - minRPM) * (0.5 - Throttle * 0.3);
|
|
||||||
if ( gear < currentGear && gear > currentGear - 2 && EngineRPM > threshold )
|
|
||||||
return;
|
|
||||||
|
|
||||||
SwitchGear( gear );
|
|
||||||
}
|
|
||||||
|
|
||||||
private float EngineClutch( float dt )
|
|
||||||
{
|
|
||||||
if ( !switchCD )
|
|
||||||
{
|
|
||||||
inputThrottle = 0;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( inputHandbrake )
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
var absForwardSpeed = Math.Abs( ForwardSpeed );
|
|
||||||
|
|
||||||
if ( groundedCount < 1 && absForwardSpeed > 30 )
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if ( ForwardSpeed < -50 && inputBrake > 0 && Gear < 0 )
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if ( absForwardSpeed > 200 )
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return inputThrottle > 0.1f ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EngineThink( float dt )
|
private void EngineThink( float dt )
|
||||||
{
|
{
|
||||||
inputThrottle = Input.Down( "Forward" ) ? 1 : 0;
|
Engine.Throttle = Input.Down( "Forward" ) ? 1 : 0;
|
||||||
inputBrake = Input.Down( "Backward" ) ? 1 : 0;
|
Engine.ForwardStep( 0, 0 );
|
||||||
inputHandbrake = Input.Down( "Jump" );
|
|
||||||
if ( burnout > 0 )
|
|
||||||
{
|
|
||||||
SwitchGear( 1, false );
|
|
||||||
if ( inputThrottle < 0.1f || inputBrake < 0.1f )
|
|
||||||
burnout = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
AutoGearSwitch();
|
|
||||||
|
|
||||||
if ( Gear < 0 )
|
|
||||||
(inputBrake, inputThrottle) = (inputThrottle, inputBrake);
|
|
||||||
|
|
||||||
var rpm = FlywheelRPM;
|
|
||||||
|
|
||||||
var clutch = EngineClutch( dt );
|
|
||||||
|
|
||||||
if ( inputThrottle > 0.1 && inputBrake > 0.1 && Math.Abs( ForwardSpeed ) < 50 )
|
|
||||||
{
|
|
||||||
burnout = MathX.Approach( burnout, 1, dt * 2 );
|
|
||||||
Clutch = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
else if ( inputHandbrake )
|
|
||||||
{
|
|
||||||
frontBrake = 0f;
|
|
||||||
rearBrake = 0.5f;
|
|
||||||
Clutch = 1;
|
|
||||||
clutch = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if ( (Gear == -1 || Gear == 1) && inputThrottle < 0.05f && inputBrake < 0.1f && groundedCount > 1 && rpm < MinRPM * 1.2f )
|
|
||||||
inputBrake = 0.2f;
|
|
||||||
|
|
||||||
frontBrake = inputBrake * 0.5f;
|
|
||||||
rearBrake = inputBrake * 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
clutch = MathX.Approach( Clutch, clutch, dt * ((Gear < 2 && inputThrottle > 0.1f) ? 6 : 2) );
|
|
||||||
|
|
||||||
Clutch = clutch;
|
|
||||||
|
|
||||||
var isRedlining = false;
|
|
||||||
transmissionRPM = 0;
|
|
||||||
|
|
||||||
if ( Gear != 0 )
|
|
||||||
{
|
|
||||||
transmissionRPM = TransmissionToEngineRPM( Gear );
|
|
||||||
transmissionRPM = Gear < 0 ? -transmissionRPM : transmissionRPM;
|
|
||||||
rpm = (rpm * clutch) + (MathF.Max( 0, transmissionRPM ) * (1 - clutch));
|
|
||||||
}
|
|
||||||
var throttle = Throttle;
|
|
||||||
|
|
||||||
var gearTorque = GetTransmissionTorque( Gear, MinRPMTorque, MaxRPMTorque );
|
|
||||||
|
|
||||||
var availableTorque = gearTorque * throttle;
|
|
||||||
|
|
||||||
if ( transmissionRPM < 0 )
|
|
||||||
{
|
|
||||||
availableTorque += gearTorque * 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var engineBrakeTorque = GetTransmissionTorque( Gear, EngineBrakeTorque, EngineBrakeTorque );
|
|
||||||
|
|
||||||
availableTorque -= engineBrakeTorque * (1 - throttle) * 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxRPM = MaxRPM;
|
|
||||||
|
|
||||||
if ( rpm < MinRPM )
|
|
||||||
{
|
|
||||||
rpm = MinRPM;
|
|
||||||
}
|
|
||||||
else if ( rpm > maxRPM )
|
|
||||||
{
|
|
||||||
|
|
||||||
if ( rpm > maxRPM * 1.2f )
|
|
||||||
availableTorque = 0;
|
|
||||||
|
|
||||||
rpm = maxRPM;
|
|
||||||
|
|
||||||
if ( Gear != MaxGear || groundedCount < Wheels.Count )
|
|
||||||
isRedlining = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlywheelRPM = Math.Clamp( rpm, 0, maxRPM );
|
|
||||||
|
|
||||||
if ( burnout > 0 )
|
|
||||||
availableTorque += availableTorque * burnout * 0.1f;
|
|
||||||
|
|
||||||
var front = 0.5f + PowerDistribution * 0.5f;
|
|
||||||
var rear = 1 - front;
|
|
||||||
|
|
||||||
availableFrontTorque = availableTorque * front;
|
|
||||||
availableRearTorque = availableTorque * rear;
|
|
||||||
|
|
||||||
throttle = MathX.Approach( throttle, inputThrottle, dt * 4 );
|
|
||||||
|
|
||||||
|
|
||||||
EngineAccelerate( FlywheelFriction + FlywheelTorque * throttle, dt );
|
|
||||||
|
|
||||||
Throttle = throttle;
|
|
||||||
|
|
||||||
IsRedlining = (isRedlining && inputThrottle > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//[Property, Feature( "Engine" ), Sync] public EngineState EngineState { get; set; }
|
||||||
|
//[Property, Feature( "Engine" )] public EngineStream Stream { get; set; }
|
||||||
|
//public EngineStreamPlayer StreamPlayer { get; set; }
|
||||||
|
|
||||||
|
//[Property, Feature( "Engine" )] public float MinRPM { get; set; } = 800;
|
||||||
|
//[Property, Feature( "Engine" )] public float MaxRPM { get; set; } = 7000;
|
||||||
|
//[Property, Feature( "Engine" ), Range( -1, 1 )]
|
||||||
|
//public float PowerDistribution
|
||||||
|
//{
|
||||||
|
// get => powerDistribution; set
|
||||||
|
// {
|
||||||
|
// powerDistribution = value;
|
||||||
|
// UpdatePowerDistribution();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//[Property, Feature( "Engine" )] public float FlyWheelMass { get; set; } = 80f;
|
||||||
|
//[Property, Feature( "Engine" )] public float FlyWheelRadius { get; set; } = 0.5f;
|
||||||
|
//[Property, Feature( "Engine" )] public float FlywheelFriction { get; set; } = -6000;
|
||||||
|
//[Property, Feature( "Engine" )] public float FlywheelTorque { get; set; } = 20000;
|
||||||
|
//[Property, Feature( "Engine" )] public float EngineBrakeTorque { get; set; } = 2000;
|
||||||
|
//[Property, Feature( "Engine" )]
|
||||||
|
//public Dictionary<int, float> Gears { get; set; } = new()
|
||||||
|
//{
|
||||||
|
// [-1] = 2.5f,
|
||||||
|
// [0] = 0f,
|
||||||
|
// [1] = 2.8f,
|
||||||
|
// [2] = 1.7f,
|
||||||
|
// [3] = 1.2f,
|
||||||
|
// [4] = 0.9f,
|
||||||
|
// [5] = 0.75f,
|
||||||
|
// [6] = 0.7f
|
||||||
|
//};
|
||||||
|
//[Property, Feature( "Engine" )] public float DifferentialRatio { get; set; } = 1f;
|
||||||
|
//[Property, Feature( "Engine" ), Range( 0, 1 )] public float TransmissionEfficiency { get; set; } = 0.8f;
|
||||||
|
//[Property, Feature( "Engine" )] private float MinRPMTorque { get; set; } = 5000f;
|
||||||
|
//[Property, Feature( "Engine" )] private float MaxRPMTorque { get; set; } = 8000f;
|
||||||
|
//[Sync] public int Gear { get; set; } = 0;
|
||||||
|
//[Sync] public float Clutch { get; set; } = 1;
|
||||||
|
//[Sync( SyncFlags.Interpolate )] public float EngineRPM { get; set; }
|
||||||
|
//public float RPMPercent => (EngineRPM - MinRPM) / MaxRPM;
|
||||||
|
|
||||||
|
//private const float TAU = MathF.Tau;
|
||||||
|
|
||||||
|
//private int MinGear { get; set; }
|
||||||
|
//private int MaxGear { get; set; }
|
||||||
|
|
||||||
|
//[Sync] public bool IsRedlining { get; private set; }
|
||||||
|
|
||||||
|
//private float flywheelVelocity;
|
||||||
|
//private TimeUntil switchCD = 0;
|
||||||
|
//private float groundedCount;
|
||||||
|
//private float burnout;
|
||||||
|
|
||||||
|
//private float frontBrake;
|
||||||
|
//private float rearBrake;
|
||||||
|
|
||||||
|
//private float availableFrontTorque;
|
||||||
|
//private float availableRearTorque;
|
||||||
|
|
||||||
|
//private float avgSideSlip;
|
||||||
|
//private float avgPoweredRPM;
|
||||||
|
//private float avgForwardSlip;
|
||||||
|
|
||||||
|
//private float inputThrottle, inputBrake;
|
||||||
|
//private bool inputHandbrake;
|
||||||
|
//private float transmissionRPM;
|
||||||
|
//private float powerDistribution;
|
||||||
|
|
||||||
|
//public float FlywheelRPM
|
||||||
|
//{
|
||||||
|
// get => flywheelVelocity * 60 / TAU;
|
||||||
|
// set
|
||||||
|
// {
|
||||||
|
// flywheelVelocity = value * TAU / 60; EngineRPM = value;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//private void UpdateGearList()
|
||||||
|
//{
|
||||||
|
// int minGear = 0;
|
||||||
|
// int maxGear = 0;
|
||||||
|
|
||||||
|
// foreach ( var (gear, ratio) in Gears )
|
||||||
|
// {
|
||||||
|
|
||||||
|
// if ( gear < minGear )
|
||||||
|
// minGear = gear;
|
||||||
|
|
||||||
|
// if ( gear > maxGear )
|
||||||
|
// maxGear = gear;
|
||||||
|
|
||||||
|
// if ( minGear != 0 || maxGear != 0 )
|
||||||
|
// {
|
||||||
|
// SwitchGear( 0, false );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// MinGear = minGear;
|
||||||
|
// MaxGear = maxGear;
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
//public void SwitchGear( int index, bool cooldown = true )
|
||||||
|
//{
|
||||||
|
// if ( Gear == index ) return;
|
||||||
|
// index = Math.Clamp( index, MinGear, MaxGear );
|
||||||
|
|
||||||
|
// if ( index == 0 || !cooldown )
|
||||||
|
// switchCD = 0;
|
||||||
|
// else
|
||||||
|
// switchCD = 0.3f;
|
||||||
|
// Clutch = 1;
|
||||||
|
// Gear = index;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public float TransmissionToEngineRPM( int gear ) => avgPoweredRPM * Gears[gear] * DifferentialRatio * 60 / TAU;
|
||||||
|
//public float GetTransmissionMaxRPM( int gear ) => FlywheelRPM / Gears[gear] / DifferentialRatio;
|
||||||
|
|
||||||
|
//private void UpdatePowerDistribution()
|
||||||
|
//{
|
||||||
|
// if ( Wheels is null ) return;
|
||||||
|
// int frontCount = 0, rearCount = 0;
|
||||||
|
|
||||||
|
// foreach ( var wheel in Wheels )
|
||||||
|
// {
|
||||||
|
// if ( wheel.IsFront )
|
||||||
|
// frontCount++;
|
||||||
|
// else
|
||||||
|
// rearCount++;
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// float frontDistribution = 0.5f + PowerDistribution * 0.5f;
|
||||||
|
|
||||||
|
// float rearDistribution = 1 - frontDistribution;
|
||||||
|
|
||||||
|
// frontDistribution /= frontCount;
|
||||||
|
// rearDistribution /= rearCount;
|
||||||
|
|
||||||
|
// foreach ( var wheel in Wheels )
|
||||||
|
// if ( wheel.IsFront )
|
||||||
|
// wheel.DistributionFactor = frontDistribution;
|
||||||
|
// else
|
||||||
|
// wheel.DistributionFactor = rearDistribution;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void EngineAccelerate( float torque, float dt )
|
||||||
|
//{
|
||||||
|
// var inertia = 0.5f * FlyWheelMass * FlyWheelRadius * FlyWheelRadius;
|
||||||
|
// var angularAcceleration = torque / inertia;
|
||||||
|
|
||||||
|
// flywheelVelocity += angularAcceleration * dt;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private float GetTransmissionTorque( int gear, float minTorque, float maxTorque )
|
||||||
|
//{
|
||||||
|
// var torque = FlywheelRPM.Remap( MinRPM, MaxRPM, minTorque, maxTorque, true );
|
||||||
|
// torque *= (1 - Clutch);
|
||||||
|
// torque = torque * Gears[gear] * DifferentialRatio * TransmissionEfficiency;
|
||||||
|
|
||||||
|
// return gear == -1 ? -torque : torque;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void AutoGearSwitch()
|
||||||
|
//{
|
||||||
|
// if ( ForwardSpeed < 100 && Input.Down( "Backward" ) )
|
||||||
|
// {
|
||||||
|
// SwitchGear( -1, false );
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var currentGear = Gear;
|
||||||
|
|
||||||
|
// if ( currentGear < 0 && ForwardSpeed < -100 )
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// if ( Math.Abs( avgForwardSlip ) > 10 )
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// var gear = Math.Clamp( currentGear, 1, MaxGear );
|
||||||
|
|
||||||
|
// float minRPM = MinRPM, maxRPM = MaxRPM;
|
||||||
|
|
||||||
|
|
||||||
|
// maxRPM *= 0.98f;
|
||||||
|
|
||||||
|
// float gearRPM;
|
||||||
|
|
||||||
|
|
||||||
|
// for ( int i = 1; i <= MaxGear; i++ )
|
||||||
|
// {
|
||||||
|
// gearRPM = TransmissionToEngineRPM( i );
|
||||||
|
|
||||||
|
// if ( (i == 1 && gearRPM < minRPM) || (gearRPM > minRPM && gearRPM < maxRPM) )
|
||||||
|
// {
|
||||||
|
// gear = i;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var threshold = minRPM + (maxRPM - minRPM) * (0.5 - Throttle * 0.3);
|
||||||
|
// if ( gear < currentGear && gear > currentGear - 2 && EngineRPM > threshold )
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// SwitchGear( gear );
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private float EngineClutch( float dt )
|
||||||
|
//{
|
||||||
|
// if ( !switchCD )
|
||||||
|
// {
|
||||||
|
// inputThrottle = 0;
|
||||||
|
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if ( inputHandbrake )
|
||||||
|
// return 1;
|
||||||
|
|
||||||
|
// var absForwardSpeed = Math.Abs( ForwardSpeed );
|
||||||
|
|
||||||
|
// if ( groundedCount < 1 && absForwardSpeed > 30 )
|
||||||
|
// return 1;
|
||||||
|
|
||||||
|
// if ( ForwardSpeed < -50 && inputBrake > 0 && Gear < 0 )
|
||||||
|
// return 1;
|
||||||
|
|
||||||
|
// if ( absForwardSpeed > 200 )
|
||||||
|
// return 0;
|
||||||
|
|
||||||
|
// return inputThrottle > 0.1f ? 0 : 1;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//private void EngineThink( float dt )
|
||||||
|
//{
|
||||||
|
// inputThrottle = Input.Down( "Forward" ) ? 1 : 0;
|
||||||
|
// inputBrake = Input.Down( "Backward" ) ? 1 : 0;
|
||||||
|
// inputHandbrake = Input.Down( "Jump" );
|
||||||
|
// if ( burnout > 0 )
|
||||||
|
// {
|
||||||
|
// SwitchGear( 1, false );
|
||||||
|
// if ( inputThrottle < 0.1f || inputBrake < 0.1f )
|
||||||
|
// burnout = 0;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// AutoGearSwitch();
|
||||||
|
|
||||||
|
// if ( Gear < 0 )
|
||||||
|
// (inputBrake, inputThrottle) = (inputThrottle, inputBrake);
|
||||||
|
|
||||||
|
// var rpm = FlywheelRPM;
|
||||||
|
|
||||||
|
// var clutch = EngineClutch( dt );
|
||||||
|
|
||||||
|
// if ( inputThrottle > 0.1 && inputBrake > 0.1 && Math.Abs( ForwardSpeed ) < 50 )
|
||||||
|
// {
|
||||||
|
// burnout = MathX.Approach( burnout, 1, dt * 2 );
|
||||||
|
// Clutch = 0;
|
||||||
|
|
||||||
|
// }
|
||||||
|
// else if ( inputHandbrake )
|
||||||
|
// {
|
||||||
|
// frontBrake = 0f;
|
||||||
|
// rearBrake = 0.5f;
|
||||||
|
// Clutch = 1;
|
||||||
|
// clutch = 1;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// if ( (Gear == -1 || Gear == 1) && inputThrottle < 0.05f && inputBrake < 0.1f && groundedCount > 1 && rpm < MinRPM * 1.2f )
|
||||||
|
// inputBrake = 0.2f;
|
||||||
|
|
||||||
|
// frontBrake = inputBrake * 0.5f;
|
||||||
|
// rearBrake = inputBrake * 0.5f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// clutch = MathX.Approach( Clutch, clutch, dt * ((Gear < 2 && inputThrottle > 0.1f) ? 6 : 2) );
|
||||||
|
|
||||||
|
// Clutch = clutch;
|
||||||
|
|
||||||
|
// var isRedlining = false;
|
||||||
|
// transmissionRPM = 0;
|
||||||
|
|
||||||
|
// if ( Gear != 0 )
|
||||||
|
// {
|
||||||
|
// transmissionRPM = TransmissionToEngineRPM( Gear );
|
||||||
|
// transmissionRPM = Gear < 0 ? -transmissionRPM : transmissionRPM;
|
||||||
|
// rpm = (rpm * clutch) + (MathF.Max( 0, transmissionRPM ) * (1 - clutch));
|
||||||
|
// }
|
||||||
|
// var throttle = Throttle;
|
||||||
|
|
||||||
|
// var gearTorque = GetTransmissionTorque( Gear, MinRPMTorque, MaxRPMTorque );
|
||||||
|
|
||||||
|
// var availableTorque = gearTorque * throttle;
|
||||||
|
|
||||||
|
// if ( transmissionRPM < 0 )
|
||||||
|
// {
|
||||||
|
// availableTorque += gearTorque * 2;
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// var engineBrakeTorque = GetTransmissionTorque( Gear, EngineBrakeTorque, EngineBrakeTorque );
|
||||||
|
|
||||||
|
// availableTorque -= engineBrakeTorque * (1 - throttle) * 0.5f;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var maxRPM = MaxRPM;
|
||||||
|
|
||||||
|
// if ( rpm < MinRPM )
|
||||||
|
// {
|
||||||
|
// rpm = MinRPM;
|
||||||
|
// }
|
||||||
|
// else if ( rpm > maxRPM )
|
||||||
|
// {
|
||||||
|
|
||||||
|
// if ( rpm > maxRPM * 1.2f )
|
||||||
|
// availableTorque = 0;
|
||||||
|
|
||||||
|
// rpm = maxRPM;
|
||||||
|
|
||||||
|
// if ( Gear != MaxGear || groundedCount < Wheels.Count )
|
||||||
|
// isRedlining = true;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// FlywheelRPM = Math.Clamp( rpm, 0, maxRPM );
|
||||||
|
|
||||||
|
// if ( burnout > 0 )
|
||||||
|
// availableTorque += availableTorque * burnout * 0.1f;
|
||||||
|
|
||||||
|
// var front = 0.5f + PowerDistribution * 0.5f;
|
||||||
|
// var rear = 1 - front;
|
||||||
|
|
||||||
|
// availableFrontTorque = availableTorque * front;
|
||||||
|
// availableRearTorque = availableTorque * rear;
|
||||||
|
|
||||||
|
// throttle = MathX.Approach( throttle, inputThrottle, dt * 4 );
|
||||||
|
|
||||||
|
|
||||||
|
// EngineAccelerate( FlywheelFriction + FlywheelTorque * throttle, dt );
|
||||||
|
|
||||||
|
// Throttle = throttle;
|
||||||
|
|
||||||
|
// IsRedlining = (isRedlining && inputThrottle > 0);
|
||||||
|
//}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,33 +14,40 @@ public partial class VeloXCar
|
|||||||
[Property, Feature( "Steer" )] public float MaxSteerAngle { get; set; } = 35f;
|
[Property, Feature( "Steer" )] public float MaxSteerAngle { get; set; } = 35f;
|
||||||
|
|
||||||
[Sync] public float Steering { get; private set; }
|
[Sync] public float Steering { get; private set; }
|
||||||
private float jTurnMultiplier;
|
|
||||||
|
|
||||||
private float inputSteer;
|
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 )
|
private void UpdateSteering( float dt )
|
||||||
{
|
{
|
||||||
var inputSteer = Input.AnalogMove.y;
|
var inputSteer = Input.AnalogMove.y;
|
||||||
var absInputSteer = Math.Abs( inputSteer );
|
|
||||||
|
|
||||||
var sideSlip = Math.Clamp( avgSideSlip, -1, 1 );
|
|
||||||
|
|
||||||
var steerConeFactor = Math.Clamp( TotalSpeed / SteerConeMaxSpeed, 0, 1 );
|
VelocityAngle = 0;// -SignedAngle( Body.Velocity, WorldRotation.Forward, WorldRotation.Up );
|
||||||
var steerCone = 1 - steerConeFactor * (1 - SteerConeMaxAngle);
|
|
||||||
|
|
||||||
steerCone = Math.Clamp( steerCone, Math.Abs( sideSlip ), 1 );
|
//var steerConeFactor = Math.Clamp( TotalSpeed / SteerConeMaxSpeed, 0, 1 );
|
||||||
|
//var steerCone = 1 - steerConeFactor * (1 - SteerConeMaxAngle);
|
||||||
|
|
||||||
inputSteer = ExpDecay( this.inputSteer, inputSteer * steerCone, SteerConeChangeRate, dt );
|
inputSteer = ExpDecay( this.inputSteer, inputSteer, SteerConeChangeRate, dt );
|
||||||
this.inputSteer = inputSteer;
|
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;
|
Steering = inputSteer;
|
||||||
SteerAngle = new( 0, inputSteer * MaxSteerAngle, 0 );
|
SteerAngle = new( 0, target, 0 );
|
||||||
|
|
||||||
if ( ForwardSpeed < -100 )
|
|
||||||
jTurnMultiplier = 0.5f;
|
|
||||||
else
|
|
||||||
jTurnMultiplier = ExpDecay( jTurnMultiplier, 1, 2, dt );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,45 +2,9 @@
|
|||||||
|
|
||||||
public partial class VeloXCar
|
public partial class VeloXCar
|
||||||
{
|
{
|
||||||
|
|
||||||
private void WheelThink( in float dt )
|
private void WheelThink( in float dt )
|
||||||
{
|
{
|
||||||
var maxRPM = GetTransmissionMaxRPM( Gear );
|
|
||||||
|
|
||||||
|
|
||||||
var frontTorque = availableFrontTorque;
|
|
||||||
var rearTorque = availableRearTorque;
|
|
||||||
|
|
||||||
|
|
||||||
groundedCount = 0;
|
|
||||||
float avgRPM = 0, totalSideSlip = 0, totalForwardSlip = 0;
|
|
||||||
|
|
||||||
foreach ( var w in Wheels )
|
foreach ( var w in Wheels )
|
||||||
{
|
|
||||||
w.Update( this, dt );
|
w.Update( this, dt );
|
||||||
|
|
||||||
totalSideSlip += w.SideSlip;
|
|
||||||
totalForwardSlip += w.ForwardSlip;
|
|
||||||
var rpm = w.RPM;
|
|
||||||
|
|
||||||
avgRPM += rpm * w.DistributionFactor;
|
|
||||||
|
|
||||||
w.Torque = w.DistributionFactor * (w.IsFront ? frontTorque : rearTorque);
|
|
||||||
w.Brake = w.IsFront ? frontBrake : rearBrake;
|
|
||||||
|
|
||||||
if ( inputHandbrake && !w.IsFront )
|
|
||||||
w.RPM = 0;
|
|
||||||
|
|
||||||
if ( rpm > maxRPM )
|
|
||||||
w.RPM = maxRPM;
|
|
||||||
|
|
||||||
if ( w.IsOnGround )
|
|
||||||
groundedCount++;
|
|
||||||
|
|
||||||
}
|
|
||||||
avgPoweredRPM = avgRPM;
|
|
||||||
avgSideSlip = totalSideSlip / Wheels.Count;
|
|
||||||
avgForwardSlip = totalForwardSlip / Wheels.Count;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,6 @@ namespace VeloX;
|
|||||||
[Title( "VeloX - Car" )]
|
[Title( "VeloX - Car" )]
|
||||||
public partial class VeloXCar : VeloXBase
|
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()
|
protected override void OnFixedUpdate()
|
||||||
{
|
{
|
||||||
if ( !IsDriver )
|
if ( !IsDriver )
|
||||||
@@ -40,9 +14,10 @@ public partial class VeloXCar : VeloXBase
|
|||||||
|
|
||||||
base.OnFixedUpdate();
|
base.OnFixedUpdate();
|
||||||
|
|
||||||
Brake = Math.Clamp( frontBrake + rearBrake + (Input.Down( "Jump" ) ? 1 : 0), 0, 1 );
|
Brake = Math.Clamp( (Input.Down( "Jump" ) ? 1 : 0), 0, 1 );
|
||||||
|
|
||||||
var dt = Time.Delta;
|
var dt = Time.Delta;
|
||||||
|
|
||||||
EngineThink( dt );
|
EngineThink( dt );
|
||||||
WheelThink( dt );
|
WheelThink( dt );
|
||||||
UpdateSteering( dt );
|
UpdateSteering( dt );
|
||||||
|
|||||||
@@ -28,44 +28,38 @@ public static class PhysicsExtensions
|
|||||||
value.x * (xz2 - wy2) + value.y * (yz2 + wx2) + value.z * (1.0f - xx2 - yy2)
|
value.x * (xz2 - wy2) + value.y * (yz2 + wx2) + value.z * (1.0f - xx2 - yy2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the linear and angular velocities on the center of mass for an offset impulse.
|
/// Calculates the linear and angular velocities on the object's center of mass for an offset impulse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="physObj">The physics object</param>
|
/// <param name="physObj">The physics object</param>
|
||||||
/// <param name="impulse">The impulse acting on the object in kg*units/s (World frame)</param>
|
/// <param name="impulse">The impulse acting on the object in kg*units/s (World frame)</param>
|
||||||
/// <param name="position">The location of the impulse in world coordinates</param>
|
/// <param name="position">The location of the impulse in world coordinates</param>
|
||||||
/// <returns>
|
/// <param name="LinearVelocity">Linear velocity on center of mass (World frame)</param>
|
||||||
/// Vector1: Linear velocity from the impulse (World frame)
|
/// <param name="AngularVelocity">Angular velocity on center of mass (World frame)</param>
|
||||||
/// Vector2: Angular velocity from the impulse (Local frame)
|
public static void CalculateVelocityOffset(
|
||||||
/// </returns>
|
this Rigidbody physObj,
|
||||||
public static (Vector3 LinearVelocity, Vector3 AngularVelocity) CalculateVelocityOffset( this PhysicsBody physObj, Vector3 impulse, Vector3 position )
|
Vector3 impulse,
|
||||||
|
Vector3 position,
|
||||||
|
out Vector3 LinearVelocity,
|
||||||
|
out Vector3 AngularVelocity )
|
||||||
{
|
{
|
||||||
if ( !physObj.IsValid() || !physObj.MotionEnabled )
|
if ( !physObj.IsValid() || !physObj.MotionEnabled )
|
||||||
return (Vector3.Zero, Vector3.Zero);
|
{
|
||||||
|
LinearVelocity = 0;
|
||||||
|
AngularVelocity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 com = physObj.WorldTransform.PointToWorld( physObj.MassCenter );
|
||||||
|
Rotation bodyRot = physObj.PhysicsBody.Rotation;
|
||||||
|
|
||||||
Vector3 linearVelocity = impulse / physObj.Mass;
|
Vector3 r = position - com;
|
||||||
|
Vector3 torque = Vector3.Cross( r, impulse );
|
||||||
Vector3 r = position - physObj.MassCenter;
|
Vector3 torqueLocal = bodyRot.Inverse * torque;
|
||||||
|
Vector3 angularVelocityLocal = torqueLocal * physObj.PhysicsBody.Inertia.Inverse;
|
||||||
|
AngularVelocity = bodyRot * angularVelocityLocal;
|
||||||
// Calculate torque impulse in world frame: τ = r × impulse
|
LinearVelocity = impulse * (1 / physObj.Mass);
|
||||||
Vector3 torqueImpulseWorld = r.Cross( impulse );
|
|
||||||
Rotation worldToLocal = physObj.Rotation.Inverse;
|
|
||||||
Vector3 torqueImpulseLocal = torqueImpulseWorld.Transform( worldToLocal );
|
|
||||||
|
|
||||||
var InverseInertiaDiagLocal = physObj.Inertia.Inverse;
|
|
||||||
|
|
||||||
// Compute angular velocity change in rad/s (local frame)
|
|
||||||
Vector3 angularVelocityRadLocal = new(
|
|
||||||
InverseInertiaDiagLocal.x * torqueImpulseLocal.x,
|
|
||||||
InverseInertiaDiagLocal.y * torqueImpulseLocal.y,
|
|
||||||
InverseInertiaDiagLocal.z * torqueImpulseLocal.z
|
|
||||||
);
|
|
||||||
const float radToDeg = 180f / MathF.PI;
|
|
||||||
Vector3 angularVelocityDegLocal = angularVelocityRadLocal * radToDeg;
|
|
||||||
|
|
||||||
return (linearVelocity, angularVelocityDegLocal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,34 +68,31 @@ public static class PhysicsExtensions
|
|||||||
/// <param name="physObj">The physics object</param>
|
/// <param name="physObj">The physics object</param>
|
||||||
/// <param name="impulse">The impulse acting on the object in kg*units/s (World frame)</param>
|
/// <param name="impulse">The impulse acting on the object in kg*units/s (World frame)</param>
|
||||||
/// <param name="position">The location of the impulse in world coordinates</param>
|
/// <param name="position">The location of the impulse in world coordinates</param>
|
||||||
/// <returns>
|
/// <param name="LinearImpulse">Linear impulse on center of mass (World frame)</param>
|
||||||
/// Vector1: Linear impulse on center of mass (World frame)
|
/// <param name="AngularImpulse">Angular impulse on center of mass (World frame)</param>
|
||||||
/// Vector2: Angular impulse on center of mass (Local frame)
|
public static void CalculateForceOffset(
|
||||||
/// </returns>
|
this Rigidbody physObj,
|
||||||
public static (Vector3 LinearImpulse, Vector3 AngularImpulse) CalculateForceOffset(
|
|
||||||
this PhysicsBody physObj,
|
|
||||||
Vector3 impulse,
|
Vector3 impulse,
|
||||||
Vector3 position )
|
Vector3 position,
|
||||||
|
out Vector3 LinearImpulse,
|
||||||
|
out Vector3 AngularImpulse )
|
||||||
{
|
{
|
||||||
if ( !physObj.IsValid() || !physObj.MotionEnabled )
|
if ( !physObj.IsValid() || !physObj.MotionEnabled )
|
||||||
{
|
{
|
||||||
return (Vector3.Zero, Vector3.Zero);
|
LinearImpulse = 0;
|
||||||
|
AngularImpulse = 0;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Linear impulse is the same as the input impulse (conservation of momentum)
|
Vector3 com = physObj.WorldTransform.PointToWorld( physObj.MassCenter );
|
||||||
Vector3 linearImpulse = impulse;
|
Rotation bodyRot = physObj.PhysicsBody.Rotation;
|
||||||
|
|
||||||
// 2. Calculate angular impulse (torque) from the offset force
|
Vector3 r = position - com;
|
||||||
// τ = r * F (cross product of position relative to COM and force)
|
Vector3 torque = Vector3.Cross( r, impulse );
|
||||||
Vector3 centerOfMass = physObj.MassCenter;
|
Vector3 torqueLocal = bodyRot.Inverse * torque;
|
||||||
Vector3 relativePosition = position - centerOfMass;
|
Vector3 angularImpulseLocal = torqueLocal * physObj.PhysicsBody.Inertia.Inverse;
|
||||||
Vector3 worldAngularImpulse = relativePosition.Cross( impulse );
|
AngularImpulse = bodyRot * angularImpulseLocal;
|
||||||
|
LinearImpulse = impulse;
|
||||||
// Convert angular impulse to local space (since we'll use it with LocalInertia)
|
|
||||||
Rotation bodyRotation = physObj.Transform.Rotation;
|
|
||||||
Vector3 localAngularImpulse = bodyRotation.Inverse * worldAngularImpulse;
|
|
||||||
|
|
||||||
return (linearImpulse, localAngularImpulse);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
//using Editor;
|
|
||||||
//using Editor.Assets;
|
|
||||||
//using Sandbox;
|
|
||||||
//using VeloX;
|
|
||||||
//using static Editor.Inspectors.AssetInspector;
|
|
||||||
|
|
||||||
//[CanEdit( "asset:engstr" )]
|
|
||||||
//public class EngineStreamInspector : Widget, IAssetInspector
|
|
||||||
//{
|
|
||||||
// EngineStream EngineStream;
|
|
||||||
// ControlSheet MainSheet;
|
|
||||||
|
|
||||||
// public EngineStreamInspector( Widget parent ) : base( parent )
|
|
||||||
// {
|
|
||||||
// Layout = Layout.Column();
|
|
||||||
// Layout.Margin = 12;
|
|
||||||
// Layout.Spacing = 12;
|
|
||||||
|
|
||||||
// MainSheet = new ControlSheet();
|
|
||||||
|
|
||||||
// Layout.Add( MainSheet, 1 );
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// [EditorEvent.Hotload]
|
|
||||||
// void RebuildSheet()
|
|
||||||
// {
|
|
||||||
// if ( EngineStream is null || MainSheet is null )
|
|
||||||
// return;
|
|
||||||
|
|
||||||
// Layout.Clear( true );
|
|
||||||
// var text = Layout.Add( new Editor.TextEdit() );
|
|
||||||
// var but = Layout.Add( new Editor.Button( "Load JSON" ) );
|
|
||||||
// but.Clicked += () =>
|
|
||||||
// {
|
|
||||||
// EngineStream.LoadFromJson( text.PlainText );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// var so = EngineStream.GetSerialized();
|
|
||||||
|
|
||||||
|
|
||||||
// so.OnPropertyChanged += _ =>
|
|
||||||
// {
|
|
||||||
// EngineStream.StateHasChanged();
|
|
||||||
// };
|
|
||||||
// Layout.Add( ControlWidget.Create( so.GetProperty( nameof( EngineStream.Layers ) ) ) );
|
|
||||||
// Layout.Add( ControlWidget.Create( so.GetProperty( nameof( EngineStream.Parameters ) ) ) );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void SetAsset( Asset asset )
|
|
||||||
// {
|
|
||||||
// EngineStream = asset.LoadResource<EngineStream>();
|
|
||||||
// RebuildSheet();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
48
Editor/Wheel/PacejkaWidget.cs
Normal file
48
Editor/Wheel/PacejkaWidget.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Editor;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace VeloX;
|
||||||
|
|
||||||
|
[CustomEditor( typeof( Pacejka ) )]
|
||||||
|
public class PacejkaWidget : ControlObjectWidget
|
||||||
|
{
|
||||||
|
public override bool SupportsMultiEdit => false;
|
||||||
|
public override bool IncludeLabel => false;
|
||||||
|
|
||||||
|
[CustomEditor( typeof( Pacejka.PacejkaPreset ) )]
|
||||||
|
private class LateralForceWidget : ControlObjectWidget
|
||||||
|
{
|
||||||
|
public LateralForceWidget( SerializedProperty property ) : base( property, true )
|
||||||
|
{
|
||||||
|
Layout = Layout.Column();
|
||||||
|
Layout.Margin = 8f;
|
||||||
|
Layout.Spacing = 8;
|
||||||
|
foreach ( var item in TypeLibrary.GetType<Pacejka.PacejkaPreset>().Properties )
|
||||||
|
{
|
||||||
|
var row = Layout.AddRow();
|
||||||
|
row.Spacing = 8;
|
||||||
|
var propetry = SerializedObject.GetProperty( item.Name );
|
||||||
|
row.Add( new Label( propetry.Name ) );
|
||||||
|
row.Add( Create( propetry ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private Pacejka Pacejka;
|
||||||
|
public PacejkaWidget( SerializedProperty property ) : base( property, true )
|
||||||
|
{
|
||||||
|
|
||||||
|
var obj = SerializedObject;
|
||||||
|
Pacejka = obj.ParentProperty.GetValue<Pacejka>();
|
||||||
|
|
||||||
|
Layout = Layout.Column();
|
||||||
|
Layout.Margin = 8f;
|
||||||
|
Layout.Add( new Label.Body( $" {ToolTip}" ) { Color = Color.White } );
|
||||||
|
var tabs = Layout.Add( new TabWidget( null ) );
|
||||||
|
tabs.AddPage( nameof( Pacejka.Lateral ), null,
|
||||||
|
Layout.Add( Create( obj.GetProperty( nameof( Pacejka.Lateral ) ) ) )
|
||||||
|
);
|
||||||
|
tabs.AddPage( nameof( Pacejka.Longitudinal ), null,
|
||||||
|
Layout.Add( Create( obj.GetProperty( nameof( Pacejka.Longitudinal ) ) ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
149
Editor/Wheel/TirePresetPreview.cs
Normal file
149
Editor/Wheel/TirePresetPreview.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
using Editor;
|
||||||
|
using Editor.Assets;
|
||||||
|
using Editor.Inspectors;
|
||||||
|
using Sandbox;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace VeloX;
|
||||||
|
|
||||||
|
[AssetPreview( "tire" )]
|
||||||
|
class TirePresetPreview : AssetPreview
|
||||||
|
{
|
||||||
|
private Texture texture;
|
||||||
|
public override bool IsAnimatedPreview => false;
|
||||||
|
[Range( 0.01f, 1 )] private float Zoom { get; set; } = 1;
|
||||||
|
private TirePreset Tire;
|
||||||
|
public AssetPreviewWidget Widget { get; private set; }
|
||||||
|
public override Widget CreateWidget( Widget parent )
|
||||||
|
{
|
||||||
|
Widget = parent as AssetPreviewWidget;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public override Widget CreateToolbar()
|
||||||
|
{
|
||||||
|
var info = new IconButton( "settings" );
|
||||||
|
info.Layout = Layout.Row();
|
||||||
|
info.MinimumSize = 16;
|
||||||
|
info.MouseLeftPress = () => OpenSettings( info );
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
public void OpenSettings( Widget parent )
|
||||||
|
{
|
||||||
|
var popup = new PopupWidget( parent )
|
||||||
|
{
|
||||||
|
IsPopup = true,
|
||||||
|
Layout = Layout.Column()
|
||||||
|
};
|
||||||
|
|
||||||
|
popup.Layout.Margin = 16;
|
||||||
|
|
||||||
|
var ps = new ControlSheet();
|
||||||
|
|
||||||
|
ps.AddProperty( this, x => x.Zoom );
|
||||||
|
|
||||||
|
popup.Layout.Add( ps );
|
||||||
|
popup.MaximumWidth = 300;
|
||||||
|
popup.Show();
|
||||||
|
popup.Position = parent.ScreenRect.TopRight - popup.Size;
|
||||||
|
popup.ConstrainToScreen();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task InitializeAsset()
|
||||||
|
{
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
using ( Scene.Push() )
|
||||||
|
{
|
||||||
|
PrimaryObject = new()
|
||||||
|
{
|
||||||
|
WorldTransform = Transform.Zero
|
||||||
|
};
|
||||||
|
|
||||||
|
var plane = PrimaryObject.AddComponent<ModelRenderer>();
|
||||||
|
plane.Model = Model.Plane;
|
||||||
|
plane.LocalScale = new Vector3( 1, 1, 1 );
|
||||||
|
plane.MaterialOverride = Material.Load( "materials/dev/reflectivity_30.vmat" );
|
||||||
|
plane.Tint = new Color( 0.02f, 0.04f, 0.03f );
|
||||||
|
|
||||||
|
var bounds = PrimaryObject.GetBounds();
|
||||||
|
SceneCenter = bounds.Center;
|
||||||
|
SceneSize = bounds.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
public override void UpdateScene( float cycle, float timeStep )
|
||||||
|
{
|
||||||
|
if ( !Widget.IsValid() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
Camera.WorldPosition = Vector3.Up * 300;
|
||||||
|
Camera.Orthographic = true;
|
||||||
|
Camera.WorldRotation = new Angles( 90, 0, 0 );
|
||||||
|
|
||||||
|
var bitmap = new Bitmap( 512, 512 );
|
||||||
|
|
||||||
|
Draw( bitmap );
|
||||||
|
|
||||||
|
texture.Clear( Color.Black );
|
||||||
|
//texture.Update( bitmap );
|
||||||
|
DebugOverlaySystem.Current.Texture( texture, new Rect( 0, Widget.Size ) );
|
||||||
|
|
||||||
|
FrameScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Vector2> pointCache = [];
|
||||||
|
|
||||||
|
public TirePresetPreview( Asset asset ) : base( asset )
|
||||||
|
{
|
||||||
|
texture = Texture.CreateRenderTarget().WithDynamicUsage().WithScreenFormat().WithSize( 512, 512 ).Create();
|
||||||
|
Tire = Asset.LoadResource<TirePreset>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPacejka( Bitmap bitmap )
|
||||||
|
{
|
||||||
|
var tire = Tire.Pacejka;
|
||||||
|
var width = bitmap.Width;
|
||||||
|
var height = bitmap.Height;
|
||||||
|
|
||||||
|
|
||||||
|
{ // draw lateral line
|
||||||
|
pointCache.Clear();
|
||||||
|
|
||||||
|
bitmap.SetPen( Color.Red, 1 );
|
||||||
|
|
||||||
|
for ( float x = 0; x <= 1; x += 0.01f )
|
||||||
|
{
|
||||||
|
float val = tire.PacejkaFy( x ) * Zoom;
|
||||||
|
pointCache.Add( new( width * x, height - height * val ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmap.DrawLines( pointCache.ToArray() );
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // draw longitudinal line
|
||||||
|
pointCache.Clear();
|
||||||
|
|
||||||
|
bitmap.SetPen( Color.Green, 1 );
|
||||||
|
|
||||||
|
for ( float x = 0; x <= 1; x += 0.01f )
|
||||||
|
{
|
||||||
|
float val = tire.PacejkaFx( x ) * Zoom;
|
||||||
|
pointCache.Add( new( width * x, height - height * val ) );
|
||||||
|
}
|
||||||
|
bitmap.DrawLines( pointCache.ToArray() );
|
||||||
|
}
|
||||||
|
|
||||||
|
pointCache.Clear();
|
||||||
|
}
|
||||||
|
private void Draw( Bitmap bitmap )
|
||||||
|
{
|
||||||
|
bitmap.Clear( Color.Black );
|
||||||
|
bitmap.SetAntialias( true );
|
||||||
|
DrawPacejka( bitmap );
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user