first commit
This commit is contained in:
11
Code/Base/VeloXBase.Input.cs
Normal file
11
Code/Base/VeloXBase.Input.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
public abstract partial class VeloXBase
|
||||
{
|
||||
//[Property, Feature( "Input" ), InputAction] string ThrottleInput { get; set; } = "Forward";
|
||||
[Property, Feature( "Input" )] internal InputResolver Input { get; set; } = new();
|
||||
[Property, Feature( "Input" )] public GameObject Driver { get => Input.Driver; set => Input.Driver = value; }
|
||||
|
||||
}
|
||||
43
Code/Base/VeloXBase.Phys.cs
Normal file
43
Code/Base/VeloXBase.Phys.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
public abstract partial class VeloXBase
|
||||
{
|
||||
|
||||
private Vector3 linForce;
|
||||
private Vector3 angForce;
|
||||
|
||||
private void PhysicsSimulate()
|
||||
{
|
||||
var drag = AngularDrag;
|
||||
var mass = Body.Mass;
|
||||
var angVel = Body.AngularVelocity;
|
||||
|
||||
linForce.x = 0;
|
||||
linForce.y = 0;
|
||||
linForce.z = 0;
|
||||
|
||||
angForce.x = angVel.x * drag.x * mass;
|
||||
angForce.y = angVel.y * drag.y * mass;
|
||||
angForce.z = angVel.z * drag.z * mass;
|
||||
|
||||
|
||||
if ( Wheels.Count > 0 )
|
||||
{
|
||||
Vector3 vehVel = Body.Velocity;
|
||||
Vector3 vehAngVel = Body.AngularVelocity;
|
||||
|
||||
var dt = Time.Delta;
|
||||
foreach ( var v in Wheels )
|
||||
v.DoPhysics( this, ref vehVel, ref vehAngVel, ref linForce, ref angForce, in dt );
|
||||
|
||||
Body.Velocity = vehVel;
|
||||
Body.AngularVelocity = vehAngVel;
|
||||
}
|
||||
|
||||
|
||||
Body.ApplyForce( linForce );
|
||||
Body.ApplyTorque( angForce );
|
||||
}
|
||||
}
|
||||
62
Code/Base/VeloXBase.Sound.cs
Normal file
62
Code/Base/VeloXBase.Sound.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using Sandbox;
|
||||
using Sandbox.Audio;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using VeloX.Audio;
|
||||
using static Sandbox.Utility.Noise;
|
||||
using static VeloX.EngineStream;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
|
||||
public abstract partial class VeloXBase : Component, Component.ICollisionListener
|
||||
{
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Sound" )]
|
||||
public SoundEvent SuspensionHeavySound { get; protected set; } = ResourceLibrary.Get<SoundEvent>( "sounds/suspension/compress_heavy.sound" );
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Sound" )]
|
||||
public SoundEvent SuspensionDownSound { get; protected set; } = ResourceLibrary.Get<SoundEvent>( "sounds/suspension/pneumatic_down.sound" );
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Sound" )]
|
||||
public SoundEvent SuspensionUpSound { get; protected set; } = ResourceLibrary.Get<SoundEvent>( "sounds/suspension/pneumatic_up.sound" );
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Sound" )]
|
||||
public SoundEvent SoftCollisionSound { get; protected set; } = ResourceLibrary.Get<SoundEvent>( "sounds/collisions/car_light.sound" );
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Sound" )]
|
||||
public SoundEvent HardCollisionSound { get; protected set; } = ResourceLibrary.Get<SoundEvent>( "sounds/collisions/car_heavy.sound" );
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Sound" )]
|
||||
public SoundEvent VehicleScrapeSound { get; protected set; } = ResourceLibrary.Get<SoundEvent>( "sounds/collisions/metal_scrape.sound" );
|
||||
|
||||
[Property, Feature( "Effects" ), Group( "Particle" )]
|
||||
public GameObject MetalImpactEffect { get; protected set; } = GameObject.GetPrefab( "effects/metal_impact.prefab" );
|
||||
void ICollisionListener.OnCollisionStart( Collision collision )
|
||||
{
|
||||
|
||||
var speed = MathF.Abs( collision.Contact.NormalSpeed );
|
||||
var surfaceNormal = collision.Contact.Normal;
|
||||
if ( speed < 100 )
|
||||
return;
|
||||
|
||||
var isHardHit = speed > 300;
|
||||
var volume = Math.Clamp( speed / 400f, 0, 1 );
|
||||
var sound = Sound.Play( SoftCollisionSound, WorldPosition );
|
||||
sound.Volume = volume;
|
||||
|
||||
var a = MetalImpactEffect.Clone( new CloneConfig() { StartEnabled = true, Transform = new( collision.Contact.Point, surfaceNormal.Cross( surfaceNormal ).EulerAngles ) } );
|
||||
a.Components.Get<ParticleConeEmitter>().Burst = speed / 2;
|
||||
|
||||
if ( isHardHit )
|
||||
{
|
||||
var hardSound = Sound.Play( HardCollisionSound, WorldPosition );
|
||||
hardSound.Volume = volume;
|
||||
}
|
||||
else if ( surfaceNormal.Dot( -collision.Contact.Speed.Normal ) < 0.5f )
|
||||
{
|
||||
var scrapSound = Sound.Play( VehicleScrapeSound, WorldPosition );
|
||||
scrapSound.Volume = 0.4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Code/Base/VeloXBase.Wheel.cs
Normal file
20
Code/Base/VeloXBase.Wheel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Sandbox;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
public abstract partial class VeloXBase
|
||||
{
|
||||
|
||||
[Property] public List<VeloXWheel> Wheels { get; set; }
|
||||
[Property] public TagSet WheelIgnoredTags { get; set; }
|
||||
|
||||
public List<VeloXWheel> FindWheels() => [.. Components.GetAll<VeloXWheel>()];
|
||||
|
||||
[Button]
|
||||
public void SetupWheels()
|
||||
{
|
||||
Wheels = FindWheels();
|
||||
}
|
||||
|
||||
}
|
||||
38
Code/Base/VeloXBase.cs
Normal file
38
Code/Base/VeloXBase.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
|
||||
public abstract partial class VeloXBase : Component
|
||||
{
|
||||
[Sync] public EngineState EngineState { get; set; }
|
||||
[Sync] public WaterState WaterState { get; set; }
|
||||
[Sync] public bool IsEngineOnFire { get; set; }
|
||||
[Sync, Range( 0, 1 ), Property] public float Brake { get; set; }
|
||||
[Sync( SyncFlags.Interpolate ), Range( 0, 1 ), Property] public float Throttle { get; set; }
|
||||
|
||||
[Property] public Vector3 AngularDrag { get; set; } = new( -0.1f, -0.1f, -3 );
|
||||
[Property] public float Mass { get; set; } = 900;
|
||||
[Property, Group( "Components" )] public Rigidbody Body { get; protected set; }
|
||||
[Property, Group( "Components" )] public Collider Collider { get; protected set; }
|
||||
|
||||
[Sync] public Angles SteerAngle { get; set; }
|
||||
|
||||
public Vector3 LocalVelocity;
|
||||
public float ForwardSpeed;
|
||||
public float TotalSpeed;
|
||||
|
||||
protected override void OnFixedUpdate()
|
||||
{
|
||||
if ( IsProxy )
|
||||
return;
|
||||
|
||||
LocalVelocity = WorldTransform.PointToLocal( WorldPosition + Body.Velocity );
|
||||
ForwardSpeed = LocalVelocity.x;
|
||||
TotalSpeed = LocalVelocity.Length;
|
||||
Body.PhysicsBody.Mass = Mass;
|
||||
|
||||
PhysicsSimulate();
|
||||
}
|
||||
|
||||
}
|
||||
74
Code/Base/Wheel/VeloXWheel.Gizmo.cs
Normal file
74
Code/Base/Wheel/VeloXWheel.Gizmo.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
|
||||
public partial class VeloXWheel : Component
|
||||
{
|
||||
protected override void DrawGizmos()
|
||||
{
|
||||
|
||||
if ( !Gizmo.IsSelected )
|
||||
return;
|
||||
|
||||
Gizmo.Draw.IgnoreDepth = true;
|
||||
|
||||
//
|
||||
// Suspension length
|
||||
//
|
||||
{
|
||||
var suspensionStart = Vector3.Zero;
|
||||
var suspensionEnd = Vector3.Zero + Vector3.Down * SuspensionLength;
|
||||
|
||||
Gizmo.Draw.Color = Color.Cyan;
|
||||
Gizmo.Draw.LineThickness = 0.25f;
|
||||
|
||||
Gizmo.Draw.Line( suspensionStart, suspensionEnd );
|
||||
|
||||
Gizmo.Draw.Line( suspensionStart + Vector3.Forward, suspensionStart + Vector3.Backward );
|
||||
Gizmo.Draw.Line( suspensionEnd + Vector3.Forward, suspensionEnd + Vector3.Backward );
|
||||
}
|
||||
var widthOffset = Vector3.Right * Width * 0.5f;
|
||||
//
|
||||
// Wheel radius
|
||||
//
|
||||
{
|
||||
Gizmo.Draw.LineThickness = 0.5f;
|
||||
Gizmo.Draw.Color = Color.White;
|
||||
|
||||
Gizmo.Draw.LineCylinder( widthOffset, -widthOffset, Radius, Radius, 16 );
|
||||
}
|
||||
|
||||
//
|
||||
// Wheel width
|
||||
//
|
||||
{
|
||||
var circlePosition = Vector3.Zero;
|
||||
|
||||
Gizmo.Draw.LineThickness = 0.25f;
|
||||
Gizmo.Draw.Color = Color.White;
|
||||
|
||||
for ( float i = 0; i < 16; i++ )
|
||||
{
|
||||
|
||||
var pos = circlePosition + Vector3.Up.RotateAround( Vector3.Zero, new Angles( i / 16 * 360, 0, 0 ) ) * Radius;
|
||||
|
||||
Gizmo.Draw.Line( new Line( pos - widthOffset, pos + widthOffset ) );
|
||||
|
||||
var pos2 = circlePosition + Vector3.Up.RotateAround( Vector3.Zero, new Angles( (i + 1) / 16 * 360, 0, 0 ) ) * Radius;
|
||||
Gizmo.Draw.Line( pos - widthOffset, pos2 + widthOffset );
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
//// Forward direction
|
||||
////
|
||||
//{
|
||||
// var arrowStart = Vector3.Forward * Radius;
|
||||
// var arrowEnd = arrowStart + Vector3.Forward * 8f;
|
||||
|
||||
// Gizmo.Draw.Color = Color.Red;
|
||||
// Gizmo.Draw.Arrow( arrowStart, arrowEnd, 4, 1 );
|
||||
//}
|
||||
}
|
||||
}
|
||||
18
Code/Base/Wheel/VeloXWheel.Suspension.cs
Normal file
18
Code/Base/Wheel/VeloXWheel.Suspension.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Sandbox;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
public partial class VeloXWheel
|
||||
{
|
||||
[Property] float BrakePowerMax { get; set; } = 3000;
|
||||
[Property, Group( "Suspension" )] float SuspensionLength { get; set; } = 10;
|
||||
[Property, Group( "Suspension" )] float SpringStrength { get; set; } = 800;
|
||||
[Property, Group( "Suspension" )] float SpringDamper { get; set; } = 3000;
|
||||
|
||||
[Property, Group( "Traction" )] float ForwardTractionMax { get; set; } = 2600;
|
||||
[Property, Group( "Traction" )] float SideTractionMultiplier { get; set; } = 20;
|
||||
[Property, Group( "Traction" )] float SideTractionMaxAng { get; set; } = 25;
|
||||
[Property, Group( "Traction" )] float SideTractionMax { get; set; } = 2400;
|
||||
[Property, Group( "Traction" )] float SideTractionMin { get; set; } = 800;
|
||||
|
||||
}
|
||||
240
Code/Base/Wheel/VeloXWheel.cs
Normal file
240
Code/Base/Wheel/VeloXWheel.cs
Normal file
@@ -0,0 +1,240 @@
|
||||
using Sandbox;
|
||||
using System;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
[Group( "VeloX" )]
|
||||
[Title( "VeloX - Wheel" )]
|
||||
public partial class VeloXWheel : Component
|
||||
{
|
||||
|
||||
[Property] public float Radius { get; set; } = 15;
|
||||
[Property] public float Width { get; set; } = 6;
|
||||
[Sync] public float SideSlip { get; private set; }
|
||||
[Sync] public float ForwardSlip { get; private set; }
|
||||
[Sync, Range( 0, 1 )] public float BrakePower { get; set; }
|
||||
[Sync] public float Torque { get; set; }
|
||||
[Sync, Range( 0, 1 )] public float Brake { get; set; }
|
||||
[Property] public bool IsFront { get; protected set; }
|
||||
[Property] public float SteerMultiplier { get; set; }
|
||||
public float Spin { get; private set; }
|
||||
|
||||
public float RPM { get => angularVelocity * 60f / MathF.Tau; set => angularVelocity = value / (60 / MathF.Tau); }
|
||||
internal float DistributionFactor { get; set; }
|
||||
|
||||
private Vector3 StartPos { get; set; }
|
||||
private static Rotation CylinderOffset => Rotation.FromRoll( 90 );
|
||||
|
||||
public SceneTraceResult Trace { get; private set; }
|
||||
public bool IsOnGround => Trace.Hit;
|
||||
|
||||
private float lastSpringOffset;
|
||||
private Vector2 tractionCycle;
|
||||
private float angularVelocity;
|
||||
private float lastFraction;
|
||||
private RealTimeUntil expandSoundCD;
|
||||
private RealTimeUntil contractSoundCD;
|
||||
|
||||
|
||||
protected override void OnAwake()
|
||||
{
|
||||
base.OnAwake();
|
||||
if ( StartPos.IsNearZeroLength )
|
||||
StartPos = LocalPosition;
|
||||
}
|
||||
|
||||
private static float TractionRamp( float slipAngle, float sideTractionMaxAng, float sideTractionMax, float sideTractionMin )
|
||||
{
|
||||
sideTractionMaxAng /= 90; // Convert max slip angle to the 0 - 1 range
|
||||
var x = (slipAngle - sideTractionMaxAng) / (1 - sideTractionMaxAng);
|
||||
|
||||
|
||||
return slipAngle < sideTractionMaxAng ? sideTractionMax : (sideTractionMax * (1 - x)) + (sideTractionMin * x);
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
UpdateVisuals( vehicle, dt );
|
||||
|
||||
if ( !IsOnGround )
|
||||
{
|
||||
angularVelocity += (Torque / 20) * dt;
|
||||
angularVelocity = MathX.Approach( angularVelocity, 0, dt * 4 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateVisuals( VeloXBase vehicle, in float dt )
|
||||
{
|
||||
//Rotate the wheel around the axle axis
|
||||
Spin = (Spin - MathX.RadianToDegree( angularVelocity ) * dt) % 360;
|
||||
|
||||
WorldRotation = vehicle.WorldTransform.RotationToWorld( vehicle.SteerAngle * SteerMultiplier ).RotateAroundAxis( Vector3.Right, Spin );
|
||||
|
||||
}
|
||||
|
||||
public void DoPhysics( VeloXBase vehicle, ref Vector3 vehVel, ref Vector3 vehAngVel, ref Vector3 outLin, ref Vector3 outAng, in float dt )
|
||||
{
|
||||
|
||||
var pos = vehicle.WorldTransform.PointToWorld( StartPos );
|
||||
|
||||
var ang = vehicle.WorldTransform.RotationToWorld( vehicle.SteerAngle * SteerMultiplier );
|
||||
|
||||
var fw = ang.Forward;
|
||||
var rt = ang.Right;
|
||||
var up = ang.Up;
|
||||
|
||||
var maxLen = SuspensionLength;
|
||||
|
||||
var endPos = pos + ang.Down * maxLen;
|
||||
Trace = Scene.Trace.IgnoreGameObjectHierarchy( vehicle.GameObject )
|
||||
.FromTo( pos, endPos )
|
||||
.Cylinder( Width, Radius )
|
||||
.Rotated( vehicle.WorldTransform.Rotation * CylinderOffset )
|
||||
.UseRenderMeshes( false )
|
||||
.UseHitPosition( false )
|
||||
.WithoutTags( vehicle.WheelIgnoredTags )
|
||||
.Run();
|
||||
|
||||
var fraction = Trace.Fraction;
|
||||
|
||||
var contactPos = pos - maxLen * fraction * up;
|
||||
|
||||
LocalPosition = vehicle.WorldTransform.PointToLocal( contactPos );
|
||||
|
||||
DoSuspensionSounds( vehicle, fraction - lastFraction );
|
||||
lastFraction = fraction;
|
||||
|
||||
if ( !IsOnGround )
|
||||
{
|
||||
SideSlip = 0;
|
||||
ForwardSlip = 0;
|
||||
return;
|
||||
}
|
||||
var normal = Trace.Normal;
|
||||
|
||||
var vel = vehicle.Body.GetVelocityAtPoint( pos );
|
||||
// Split that velocity among our local directions
|
||||
|
||||
var velF = fw.Dot( vel );
|
||||
|
||||
var velR = rt.Dot( vel );
|
||||
|
||||
var velU = normal.Dot( vel );
|
||||
|
||||
var absVelR = Math.Abs( velR );
|
||||
|
||||
|
||||
//Make forward forces be perpendicular to the surface normal
|
||||
|
||||
fw = normal.Cross( rt );
|
||||
|
||||
//Suspension spring force &damping
|
||||
|
||||
var offset = maxLen - (fraction * maxLen);
|
||||
|
||||
var springForce = (offset * SpringStrength);
|
||||
|
||||
var damperForce = (lastSpringOffset - offset) * SpringDamper;
|
||||
lastSpringOffset = offset;
|
||||
var force = (springForce - damperForce) * up.Dot( normal ) * normal;
|
||||
|
||||
//If the suspension spring is going to be fully compressed on the next frame...
|
||||
|
||||
if ( velU < 0 && offset + Math.Abs( velU * dt ) > SuspensionLength )
|
||||
{
|
||||
var (linearVel, angularVel) = vehicle.Body.PhysicsBody.CalculateVelocityOffset( (-velU / dt) * normal, pos );
|
||||
vehVel += linearVel;
|
||||
vehAngVel += angularVel;
|
||||
}
|
||||
|
||||
//Rolling resistance
|
||||
force += 0.05f * -velF * fw;
|
||||
|
||||
//Brake and torque forces
|
||||
var surfaceGrip = 1;
|
||||
|
||||
var maxTraction = ForwardTractionMax * surfaceGrip * 1;
|
||||
//Grip loss logic
|
||||
|
||||
var brakeForce = MathX.Clamp( -velF, -Brake, Brake ) * BrakePowerMax * surfaceGrip;
|
||||
|
||||
var forwardForce = Torque + brakeForce;
|
||||
|
||||
var signForwardForce = forwardForce > 0 ? 1 : (forwardForce < 0 ? -1 : 0);
|
||||
|
||||
// Given an amount of sideways slippage( up to the max.traction )
|
||||
// and the forward force, calculate how much grip we are losing.
|
||||
|
||||
tractionCycle.x = Math.Min( absVelR, maxTraction );
|
||||
tractionCycle.y = forwardForce;
|
||||
|
||||
var gripLoss = Math.Max( tractionCycle.Length - maxTraction, 0 );
|
||||
|
||||
|
||||
// Reduce the forward force by the amount of grip we lost,
|
||||
|
||||
// but still allow some amount of brake force to apply regardless.
|
||||
|
||||
forwardForce += -(gripLoss * signForwardForce) + MathX.Clamp( brakeForce * 0.5f, -maxTraction, maxTraction );
|
||||
|
||||
force += fw * forwardForce;
|
||||
|
||||
// Get how fast the wheel would be spinning if it had never lost grip
|
||||
|
||||
var groundAngularVelocity = MathF.Tau * (velF / (Radius * MathF.Tau));
|
||||
|
||||
// Add our grip loss to our spin velocity
|
||||
var _angvel = groundAngularVelocity + gripLoss * (Torque > 0 ? 1 : (Torque < 0 ? -1 : 0));
|
||||
|
||||
// Smoothly match our current angular velocity to the angular velocity affected by grip loss
|
||||
|
||||
angularVelocity = MathX.Approach( angularVelocity, _angvel, dt * 200 );
|
||||
ForwardSlip = groundAngularVelocity - angularVelocity;
|
||||
|
||||
// Calculate side slip angle
|
||||
var slipAngle = MathF.Atan2( velR, MathF.Abs( velF ) ) / MathF.PI * 2;
|
||||
SideSlip = slipAngle * MathX.Clamp( vehicle.TotalSpeed * 0.005f, 0, 1 ) * 2;
|
||||
|
||||
//Sideways traction ramp
|
||||
|
||||
slipAngle = MathF.Abs( slipAngle * slipAngle );
|
||||
|
||||
maxTraction = TractionRamp( slipAngle, SideTractionMaxAng, SideTractionMax, SideTractionMin );
|
||||
|
||||
var sideForce = -rt.Dot( vel * SideTractionMultiplier );
|
||||
|
||||
// Reduce sideways traction force as the wheel slips forward
|
||||
sideForce *= 1 - Math.Clamp( MathF.Abs( gripLoss ) * 0.1f, 0, 1 ) * 0.9f;
|
||||
|
||||
// Apply sideways traction force
|
||||
force += Math.Clamp( sideForce, -maxTraction, maxTraction ) * surfaceGrip * rt;
|
||||
|
||||
force += velR * SideTractionMultiplier * -0.1f * rt;
|
||||
//Apply the forces at the axle / ground contact position
|
||||
|
||||
vehicle.Body.ApplyForceAt( pos, force / dt );
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user