Compare commits
No commits in common. "ba9afba4d13b7b408813d48d3c59d2493e9d20a1" and "c7c17c17781d4431715289e2669f6e5b986b644c" have entirely different histories.
ba9afba4d1
...
c7c17c1778
@ -1,42 +0,0 @@
|
|||||||
// THIS FILE IS AUTO-GENERATED
|
|
||||||
|
|
||||||
Layer0
|
|
||||||
{
|
|
||||||
shader "shaders/complex.shader"
|
|
||||||
|
|
||||||
//---- Rendering ----
|
|
||||||
F_DO_NOT_CAST_SHADOWS 1
|
|
||||||
|
|
||||||
//---- Ambient Occlusion ----
|
|
||||||
g_flAmbientOcclusionDirectDiffuse "0.000"
|
|
||||||
g_flAmbientOcclusionDirectSpecular "0.000"
|
|
||||||
TextureAmbientOcclusion "materials/default/default_ao.tga"
|
|
||||||
|
|
||||||
//---- Color ----
|
|
||||||
g_flModelTintAmount "1.000"
|
|
||||||
g_vColorTint "[1.000000 1.000000 1.000000 0.000000]"
|
|
||||||
TextureColor "textures/pd7aum1vufg.jpg"
|
|
||||||
|
|
||||||
//---- Fade ----
|
|
||||||
g_flFadeExponent "1.000"
|
|
||||||
|
|
||||||
//---- Fog ----
|
|
||||||
g_bFogEnabled "1"
|
|
||||||
|
|
||||||
//---- Metalness ----
|
|
||||||
g_flMetalness "0.000"
|
|
||||||
|
|
||||||
//---- Normal ----
|
|
||||||
TextureNormal "materials/default/default_normal.tga"
|
|
||||||
|
|
||||||
//---- Roughness ----
|
|
||||||
g_flRoughnessScaleFactor "1.000"
|
|
||||||
TextureRoughness "materials/default/default_rough.tga"
|
|
||||||
|
|
||||||
//---- Texture Coordinates ----
|
|
||||||
g_nScaleTexCoordUByModelScaleAxis "0"
|
|
||||||
g_nScaleTexCoordVByModelScaleAxis "0"
|
|
||||||
g_vTexCoordOffset "[0.000 0.000]"
|
|
||||||
g_vTexCoordScale "[100.000 100.000]"
|
|
||||||
g_vTexCoordScrollSpeed "[0.000 0.000]"
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB |
@ -1,23 +1,11 @@
|
|||||||
using Sandbox;
|
using Sandbox;
|
||||||
using System;
|
|
||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
|
|
||||||
public abstract partial class VeloXBase
|
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" )] internal InputResolver Input { get; set; } = new();
|
||||||
|
[Property, Feature( "Input" )] public GameObject Driver { get => Input.Driver; set => Input.Driver = value; }
|
||||||
private Guid _guid;
|
|
||||||
|
|
||||||
[Sync( SyncFlags.FromHost )]
|
|
||||||
public Guid ConnectionID
|
|
||||||
{
|
|
||||||
get => _guid;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_guid = value;
|
|
||||||
Input.Driver = Connection.Find( _guid );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Property, Feature( "Input" )] public bool IsDriver => ConnectionID == Connection.Local.Id;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace VeloX;
|
|||||||
public abstract partial class VeloXBase
|
public abstract partial class VeloXBase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private Vector3 linForce;
|
||||||
private Vector3 angForce;
|
private Vector3 angForce;
|
||||||
|
|
||||||
private void PhysicsSimulate()
|
private void PhysicsSimulate()
|
||||||
@ -13,6 +14,10 @@ public abstract partial class VeloXBase
|
|||||||
var mass = Body.Mass;
|
var mass = Body.Mass;
|
||||||
var angVel = Body.AngularVelocity;
|
var angVel = Body.AngularVelocity;
|
||||||
|
|
||||||
|
linForce.x = 0;
|
||||||
|
linForce.y = 0;
|
||||||
|
linForce.z = 0;
|
||||||
|
|
||||||
angForce.x = angVel.x * drag.x * mass;
|
angForce.x = angVel.x * drag.x * mass;
|
||||||
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;
|
||||||
@ -20,12 +25,19 @@ public abstract partial class VeloXBase
|
|||||||
|
|
||||||
if ( Wheels.Count > 0 )
|
if ( Wheels.Count > 0 )
|
||||||
{
|
{
|
||||||
|
Vector3 vehVel = Body.Velocity;
|
||||||
|
Vector3 vehAngVel = Body.AngularVelocity;
|
||||||
|
|
||||||
var dt = Time.Delta;
|
var dt = Time.Delta;
|
||||||
foreach ( var v in Wheels )
|
foreach ( var v in Wheels )
|
||||||
v.DoPhysics( this, in dt );
|
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 );
|
Body.ApplyTorque( angForce );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ namespace VeloX;
|
|||||||
|
|
||||||
public abstract partial class VeloXBase : Component
|
public abstract partial class VeloXBase : Component
|
||||||
{
|
{
|
||||||
|
[Sync] public EngineState EngineState { get; set; }
|
||||||
[Sync] public WaterState WaterState { get; set; }
|
[Sync] public WaterState WaterState { get; set; }
|
||||||
[Sync] public bool IsEngineOnFire { get; set; }
|
[Sync] public bool IsEngineOnFire { get; set; }
|
||||||
[Sync, Range( 0, 1 ), Property] public float Brake { get; set; }
|
[Sync, Range( 0, 1 ), Property] public float Brake { get; set; }
|
||||||
@ -23,7 +24,7 @@ public abstract partial class VeloXBase : Component
|
|||||||
|
|
||||||
protected override void OnFixedUpdate()
|
protected override void OnFixedUpdate()
|
||||||
{
|
{
|
||||||
if ( !IsDriver )
|
if ( IsProxy )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
LocalVelocity = WorldTransform.PointToLocal( WorldPosition + Body.Velocity );
|
LocalVelocity = WorldTransform.PointToLocal( WorldPosition + Body.Velocity );
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
namespace VeloX;
|
|
||||||
|
|
||||||
public struct Friction
|
|
||||||
{
|
|
||||||
public float SlipCoef { get; set; }
|
|
||||||
public float ForceCoef { get; set; }
|
|
||||||
|
|
||||||
public float Force { get; set; }
|
|
||||||
public float Slip { get; set; }
|
|
||||||
public float Speed { get; set; }
|
|
||||||
}
|
|
||||||
@ -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 )) ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,11 +1,7 @@
|
|||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Sandbox.UI;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Text;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.Intrinsics.Arm;
|
using System.Runtime.Intrinsics.Arm;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
|
|
||||||
@ -15,34 +11,14 @@ public partial class VeloXWheel : Component
|
|||||||
{
|
{
|
||||||
|
|
||||||
[Property] public float Radius { get; set; } = 15;
|
[Property] public float Radius { get; set; } = 15;
|
||||||
[Property] public float Mass { get; set; } = 20;
|
|
||||||
[Property] public float RollingResistance { get; set; } = 20;
|
|
||||||
[Property] public float SlipCircleShape { get; set; } = 1.05f;
|
|
||||||
|
|
||||||
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; }
|
[Sync] public float SideSlip { get; private set; }
|
||||||
[Sync] public float ForwardSlip { get; private set; }
|
[Sync] public float ForwardSlip { get; private set; }
|
||||||
[Sync, Range( 0, 1 )] public float BrakePower { get; 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] public bool IsFront { get; protected set; }
|
[Property] public bool IsFront { get; protected set; }
|
||||||
[Property] public float SteerMultiplier { get; set; }
|
[Property] public float SteerMultiplier { get; set; }
|
||||||
[Property] public float CasterAngle { get; set; } = 7; // todo
|
|
||||||
[Property] public float CamberAngle { get; set; } = -3;
|
|
||||||
[Property] public float ToeAngle { get; set; } = 0.5f;
|
|
||||||
|
|
||||||
[Property, Group( "Suspension" )] float SuspensionLength { get; set; } = 10;
|
|
||||||
[Property, Group( "Suspension" )] float SpringStrength { get; set; } = 800;
|
|
||||||
[Property, Group( "Suspension" )] float SpringDamper { get; set; } = 3000;
|
|
||||||
|
|
||||||
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 * 60f / MathF.Tau; set => angularVelocity = value / (60 / MathF.Tau); }
|
||||||
@ -55,23 +31,12 @@ public partial class VeloXWheel : Component
|
|||||||
public bool IsOnGround => Trace.Hit;
|
public bool IsOnGround => Trace.Hit;
|
||||||
|
|
||||||
private float lastSpringOffset;
|
private float lastSpringOffset;
|
||||||
|
private Vector2 tractionCycle;
|
||||||
private float angularVelocity;
|
private float angularVelocity;
|
||||||
private float load;
|
|
||||||
private float lastFraction;
|
private float lastFraction;
|
||||||
private RealTimeUntil expandSoundCD;
|
private RealTimeUntil expandSoundCD;
|
||||||
private RealTimeUntil contractSoundCD;
|
private RealTimeUntil contractSoundCD;
|
||||||
|
|
||||||
private Vector3 contactPos;
|
|
||||||
private Vector3 forward;
|
|
||||||
private Vector3 right;
|
|
||||||
private Vector3 up;
|
|
||||||
|
|
||||||
private Friction forwardFriction;
|
|
||||||
private Friction sideFriction;
|
|
||||||
private Vector3 force;
|
|
||||||
|
|
||||||
private float BaseInertia => 0.5f * Mass * MathF.Pow( Radius.InchToMeter(), 2 );
|
|
||||||
private float Inertia => BaseInertia;
|
|
||||||
|
|
||||||
protected override void OnAwake()
|
protected override void OnAwake()
|
||||||
{
|
{
|
||||||
@ -80,6 +45,15 @@ public partial class VeloXWheel : Component
|
|||||||
StartPos = LocalPosition;
|
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 )
|
private void DoSuspensionSounds( VeloXBase vehicle, float change )
|
||||||
{
|
{
|
||||||
if ( change > 0.1f && expandSoundCD )
|
if ( change > 0.1f && expandSoundCD )
|
||||||
@ -101,145 +75,41 @@ public partial class VeloXWheel : Component
|
|||||||
internal void Update( VeloXBase vehicle, in float dt )
|
internal void Update( VeloXBase vehicle, in float dt )
|
||||||
{
|
{
|
||||||
UpdateVisuals( vehicle, 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 )
|
private void UpdateVisuals( VeloXBase vehicle, in float dt )
|
||||||
{
|
{
|
||||||
var entityAngles = vehicle.WorldRotation;
|
//Rotate the wheel around the axle axis
|
||||||
|
Spin = (Spin - MathX.RadianToDegree( angularVelocity ) * dt) % 360;
|
||||||
|
|
||||||
Spin -= angularVelocity.MeterToInch() * dt;
|
WorldRotation = vehicle.WorldTransform.RotationToWorld( vehicle.SteerAngle * SteerMultiplier ).RotateAroundAxis( Vector3.Right, Spin );
|
||||||
|
|
||||||
|
|
||||||
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 )
|
public void DoPhysics( VeloXBase vehicle, ref Vector3 vehVel, ref Vector3 vehAngVel, ref Vector3 outLin, ref Vector3 outAng, in float dt )
|
||||||
{
|
|
||||||
float Tm = Torque;
|
|
||||||
float Tb = Brake * BrakePowerMax + RollingResistance;
|
|
||||||
float R = Radius.InchToMeter();
|
|
||||||
float I = Inertia;
|
|
||||||
|
|
||||||
float Winit = angularVelocity;
|
|
||||||
float W = angularVelocity;
|
|
||||||
|
|
||||||
float VxAbs = MathF.Abs( Vx );
|
|
||||||
float Sx;
|
|
||||||
if ( VxAbs >= 0.1f )
|
|
||||||
Sx = (Vx - W * R) / VxAbs;
|
|
||||||
|
|
||||||
else
|
|
||||||
Sx = (Vx - W * R) * 0.6f;
|
|
||||||
|
|
||||||
Sx = Math.Clamp( Sx * kSx, -1, 1 );
|
|
||||||
|
|
||||||
W += Tm / I * dt;
|
|
||||||
|
|
||||||
Tb *= W > 0 ? -1 : 1;
|
|
||||||
|
|
||||||
float TbCap = MathF.Abs( W ) * I / dt;
|
|
||||||
float Tbr = MathF.Abs( Tb ) - MathF.Abs( TbCap );
|
|
||||||
Tbr = MathF.Max( Tbr, 0 );
|
|
||||||
Tb = Math.Clamp( Tb, -TbCap, TbCap );
|
|
||||||
W += Tb / I * dt;
|
|
||||||
|
|
||||||
float maxTorque = LongitudinalFrictionPreset.Evaluate( Sx ) * Lc * kFx;
|
|
||||||
|
|
||||||
float errorTorque = (W - Vx / R) * I / dt;
|
|
||||||
|
|
||||||
float surfaceTorque = MathX.Clamp( errorTorque, -maxTorque, maxTorque );
|
|
||||||
|
|
||||||
W -= surfaceTorque / I * dt;
|
|
||||||
|
|
||||||
float Fx = surfaceTorque / R;
|
|
||||||
|
|
||||||
|
|
||||||
Tbr *= W > 0 ? -1 : 1;
|
|
||||||
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;
|
|
||||||
if ( Lc < 0.001f )
|
|
||||||
Sx = 0;
|
|
||||||
|
|
||||||
return (W, Sx, Fx, Tcnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private (float, float) StepLateral( float Vx, float Vy, float Lc, float kFy, float kSy, float dt )
|
|
||||||
{
|
|
||||||
float VxAbs = MathF.Abs( Vx );
|
|
||||||
float Sy;
|
|
||||||
|
|
||||||
if ( VxAbs > 0.1f )
|
|
||||||
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;
|
|
||||||
|
|
||||||
return (Sy, Fy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SlipCircle( float Sx, float Sy, float Fx, ref float Fy )
|
|
||||||
{
|
|
||||||
float SxAbs = Math.Abs( Sx );
|
|
||||||
if ( SxAbs > 0.01f )
|
|
||||||
{
|
{
|
||||||
|
|
||||||
float SxClamped = Math.Clamp( Sx, -1, 1 );
|
|
||||||
|
|
||||||
float SyClamped = Math.Clamp( Sy, -1, 1 );
|
|
||||||
|
|
||||||
Vector2 combinedSlip = new(
|
|
||||||
SxClamped * SlipCircleShape,
|
|
||||||
SyClamped
|
|
||||||
);
|
|
||||||
|
|
||||||
Vector2 slipDir = combinedSlip.Normal;
|
|
||||||
|
|
||||||
float F = MathF.Sqrt( Fx * Fx + Fy * Fy );
|
|
||||||
|
|
||||||
float absSlipDirY = MathF.Abs( slipDir.y );
|
|
||||||
|
|
||||||
Fy = F * absSlipDirY * MathF.Sign( Fy );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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( vehicle.SteerAngle * SteerMultiplier );
|
||||||
|
|
||||||
forward = ang.Forward;
|
var fw = ang.Forward;
|
||||||
right = ang.Right;
|
var rt = ang.Right;
|
||||||
up = ang.Up;
|
var up = ang.Up;
|
||||||
|
|
||||||
var maxLen = SuspensionLength;
|
var maxLen = SuspensionLength;
|
||||||
|
|
||||||
var endPos = pos + ang.Down * maxLen;
|
var endPos = pos + ang.Down * maxLen;
|
||||||
Trace = Scene.Trace
|
Trace = Scene.Trace.IgnoreGameObjectHierarchy( vehicle.GameObject )
|
||||||
.IgnoreGameObjectHierarchy( vehicle.GameObject )
|
.FromTo( pos, endPos )
|
||||||
.Cylinder( Width, Radius, pos, endPos )
|
.Cylinder( Width, Radius )
|
||||||
.Rotated( vehicle.WorldTransform.Rotation * CylinderOffset )
|
.Rotated( vehicle.WorldTransform.Rotation * CylinderOffset )
|
||||||
.UseRenderMeshes( false )
|
.UseRenderMeshes( false )
|
||||||
.UseHitPosition( false )
|
.UseHitPosition( false )
|
||||||
@ -248,107 +118,123 @@ public partial class VeloXWheel : Component
|
|||||||
|
|
||||||
var fraction = Trace.Fraction;
|
var fraction = Trace.Fraction;
|
||||||
|
|
||||||
contactPos = pos - maxLen * fraction * up;
|
var contactPos = pos - maxLen * fraction * 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;
|
SideSlip = 0;
|
||||||
ForwardSlip = 0;
|
ForwardSlip = 0;
|
||||||
return;
|
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 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;
|
var force = (springForce - damperForce) * up.Dot( normal ) * normal;
|
||||||
|
|
||||||
// Vector3.CalculateVelocityOffset is broken (need fix)
|
//If the suspension spring is going to be fully compressed on the next frame...
|
||||||
//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;
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
if ( velU < 0 && offset + Math.Abs( velU * dt ) > SuspensionLength )
|
||||||
load = springForce - damperForce;
|
|
||||||
load = Math.Max( load, 0 );
|
|
||||||
|
|
||||||
var longitudinalLoadCoefficient = GetLongitudinalLoadCoefficient( load );
|
|
||||||
var lateralLoadCoefficient = GetLateralLoadCoefficient( load );
|
|
||||||
|
|
||||||
float forwardSpeed = 0;
|
|
||||||
float sideSpeed = 0;
|
|
||||||
|
|
||||||
|
|
||||||
if ( IsOnGround )
|
|
||||||
{
|
{
|
||||||
forwardSpeed = vel.Dot( forward );
|
var (linearVel, angularVel) = vehicle.Body.PhysicsBody.CalculateVelocityOffset( (-velU / dt) * normal, pos );
|
||||||
sideSpeed = vel.Dot( right );
|
vehVel += linearVel;
|
||||||
|
vehAngVel += angularVel;
|
||||||
}
|
}
|
||||||
|
|
||||||
(float W, float Sx, float Fx, float _) = StepLongitudinal(
|
//Rolling resistance
|
||||||
forwardSpeed,
|
force += 0.05f * -velF * fw;
|
||||||
longitudinalLoadCoefficient,
|
|
||||||
0.95f,
|
|
||||||
0.9f,
|
|
||||||
dt
|
|
||||||
);
|
|
||||||
|
|
||||||
(float Sy, float Fy) = StepLateral(
|
//Brake and torque forces
|
||||||
forwardSpeed,
|
var surfaceGrip = 1;
|
||||||
sideSpeed,
|
|
||||||
lateralLoadCoefficient,
|
|
||||||
0.95f,
|
|
||||||
0.9f,
|
|
||||||
dt
|
|
||||||
);
|
|
||||||
|
|
||||||
SlipCircle( Sx, Sy, Fx, ref Fy );
|
var maxTraction = ForwardTractionMax * surfaceGrip * 1;
|
||||||
angularVelocity = W;
|
//Grip loss logic
|
||||||
|
|
||||||
forwardFriction = new Friction()
|
var brakeForce = MathX.Clamp( -velF, -Brake, Brake ) * BrakePowerMax * surfaceGrip;
|
||||||
{
|
|
||||||
Slip = Sx,
|
|
||||||
Force = Fx.MeterToInch(),
|
|
||||||
Speed = forwardSpeed
|
|
||||||
};
|
|
||||||
|
|
||||||
sideFriction = new Friction()
|
var forwardForce = Torque + brakeForce;
|
||||||
{
|
|
||||||
Slip = Sy,
|
var signForwardForce = forwardForce > 0 ? 1 : (forwardForce < 0 ? -1 : 0);
|
||||||
Force = Fy.MeterToInch(),
|
|
||||||
Speed = sideSpeed
|
// 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 );
|
||||||
|
|
||||||
|
|
||||||
var frictionforce = right * sideFriction.Force + forward * forwardFriction.Force;
|
// Reduce the forward force by the amount of grip we lost,
|
||||||
|
|
||||||
vehicle.Body.ApplyForceAt( contactPos, force + frictionforce );
|
// 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 );
|
||||||
|
|
||||||
|
|
||||||
// debug
|
|
||||||
protected override void OnUpdate()
|
|
||||||
{
|
|
||||||
DebugOverlay.Normal( contactPos, forward * forwardFriction.Force / 10000f, Color.Red, overlay: true );
|
|
||||||
DebugOverlay.Normal( contactPos, right * sideFriction.Force / 10000f, Color.Green, overlay: true );
|
|
||||||
DebugOverlay.Normal( contactPos, up * force / 50000f, Color.Blue, overlay: true );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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; }
|
|
||||||
}
|
|
||||||
@ -6,7 +6,6 @@ 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; }
|
[Property, Feature( "Engine" )] public EngineStream Stream { get; set; }
|
||||||
public EngineStreamPlayer StreamPlayer { get; set; }
|
public EngineStreamPlayer StreamPlayer { get; set; }
|
||||||
|
|
||||||
@ -182,6 +181,7 @@ public partial class VeloXCar
|
|||||||
if ( currentGear < 0 && ForwardSpeed < -100 )
|
if ( currentGear < 0 && ForwardSpeed < -100 )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
if ( Math.Abs( avgForwardSlip ) > 10 )
|
if ( Math.Abs( avgForwardSlip ) > 10 )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -351,4 +351,8 @@ public partial class VeloXCar
|
|||||||
IsRedlining = (isRedlining && inputThrottle > 0);
|
IsRedlining = (isRedlining && inputThrottle > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StreamEngineUpdate()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,17 @@ namespace VeloX;
|
|||||||
[Title( "VeloX - Car" )]
|
[Title( "VeloX - Car" )]
|
||||||
public partial class VeloXCar : VeloXBase
|
public partial class VeloXCar : VeloXBase
|
||||||
{
|
{
|
||||||
|
protected override void OnAwake()
|
||||||
|
{
|
||||||
|
base.OnAwake();
|
||||||
|
|
||||||
|
StreamPlayer = new( Stream );
|
||||||
|
}
|
||||||
protected override void OnStart()
|
protected override void OnStart()
|
||||||
{
|
{
|
||||||
base.OnStart();
|
base.OnStart();
|
||||||
StreamPlayer = new( Stream );
|
|
||||||
if ( IsDriver )
|
if ( !IsProxy )
|
||||||
{
|
{
|
||||||
UpdateGearList();
|
UpdateGearList();
|
||||||
UpdatePowerDistribution();
|
UpdatePowerDistribution();
|
||||||
@ -29,24 +35,26 @@ public partial class VeloXCar : VeloXBase
|
|||||||
StreamPlayer.EngineState = EngineState;
|
StreamPlayer.EngineState = EngineState;
|
||||||
StreamPlayer.IsRedlining = IsRedlining;
|
StreamPlayer.IsRedlining = IsRedlining;
|
||||||
|
|
||||||
|
if ( Driver.IsValid() && Driver.IsProxy )
|
||||||
|
StreamPlayer.Update( Time.Delta, Scene.Camera.WorldPosition );
|
||||||
|
else
|
||||||
StreamPlayer.Update( Time.Delta, WorldPosition );
|
StreamPlayer.Update( Time.Delta, WorldPosition );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
protected override void OnFixedUpdate()
|
protected override void OnFixedUpdate()
|
||||||
{
|
{
|
||||||
if ( !IsDriver )
|
if ( IsProxy )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
base.OnFixedUpdate();
|
base.OnFixedUpdate();
|
||||||
|
|
||||||
Brake = Math.Clamp( frontBrake + rearBrake + (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 );
|
||||||
|
|
||||||
|
Brake = Math.Clamp( frontBrake + rearBrake + (Input.Down( "Jump" ) ? 1 : 0), 0, 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
public class InputResolver
|
public class InputResolver
|
||||||
{
|
{
|
||||||
public Connection Driver { get; internal set; }
|
public GameObject Driver { get; internal set; }
|
||||||
|
|
||||||
|
|
||||||
|
private bool IsDriverActive => Driver.IsValid();
|
||||||
|
|
||||||
private bool IsDriverActive => Driver is not null;
|
|
||||||
|
|
||||||
public Vector2 MouseDelta => IsDriverActive ? Input.MouseDelta : default;
|
public Vector2 MouseDelta => IsDriverActive ? Input.MouseDelta : default;
|
||||||
public Vector2 MouseWheel => IsDriverActive ? Input.MouseWheel : default;
|
public Vector2 MouseWheel => IsDriverActive ? Input.MouseWheel : default;
|
||||||
@ -38,6 +40,8 @@ public class InputResolver
|
|||||||
public void StopAllHaptics()
|
public void StopAllHaptics()
|
||||||
{
|
{
|
||||||
if ( IsDriverActive )
|
if ( IsDriverActive )
|
||||||
|
{
|
||||||
Input.StopAllHaptics();
|
Input.StopAllHaptics();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using Sandbox.Audio;
|
using Sandbox.Audio;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using VeloX.Audio;
|
using VeloX.Audio;
|
||||||
using static VeloX.EngineStream;
|
using static VeloX.EngineStream;
|
||||||
|
|
||||||
|
|||||||
@ -1,33 +1,9 @@
|
|||||||
|
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using System;
|
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace VeloX;
|
namespace VeloX;
|
||||||
public static class PhysicsExtensions
|
public static class PhysicsExtensions
|
||||||
{
|
{
|
||||||
public static Vector3 Transform( this Vector3 value, Quaternion rotation )
|
|
||||||
{
|
|
||||||
float x2 = rotation.X + rotation.X;
|
|
||||||
float y2 = rotation.Y + rotation.Y;
|
|
||||||
float z2 = rotation.Z + rotation.Z;
|
|
||||||
|
|
||||||
float wx2 = rotation.W * x2;
|
|
||||||
float wy2 = rotation.W * y2;
|
|
||||||
float wz2 = rotation.W * z2;
|
|
||||||
float xx2 = rotation.X * x2;
|
|
||||||
float xy2 = rotation.X * y2;
|
|
||||||
float xz2 = rotation.X * z2;
|
|
||||||
float yy2 = rotation.Y * y2;
|
|
||||||
float yz2 = rotation.Y * z2;
|
|
||||||
float zz2 = rotation.Z * z2;
|
|
||||||
|
|
||||||
return new Vector3(
|
|
||||||
value.x * (1.0f - yy2 - zz2) + value.y * (xy2 - wz2) + value.z * (xz2 + wy2),
|
|
||||||
value.x * (xy2 + wz2) + value.y * (1.0f - xx2 - zz2) + value.z * (yz2 - wx2),
|
|
||||||
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 center of mass for an offset impulse.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -46,26 +22,21 @@ public static class PhysicsExtensions
|
|||||||
|
|
||||||
Vector3 linearVelocity = impulse / physObj.Mass;
|
Vector3 linearVelocity = impulse / physObj.Mass;
|
||||||
|
|
||||||
Vector3 r = position - physObj.MassCenter;
|
Vector3 centerOfMass = physObj.MassCenter;
|
||||||
|
Vector3 relativePosition = position - centerOfMass;
|
||||||
|
Vector3 torque = relativePosition.Cross( impulse );
|
||||||
|
|
||||||
|
Rotation bodyRotation = physObj.Transform.Rotation;
|
||||||
|
Vector3 localTorque = bodyRotation.Inverse * torque;
|
||||||
|
|
||||||
// Calculate torque impulse in world frame: τ = r × impulse
|
Vector3 localInverseInertia = physObj.Inertia.Inverse;
|
||||||
Vector3 torqueImpulseWorld = r.Cross( impulse );
|
|
||||||
Rotation worldToLocal = physObj.Rotation.Inverse;
|
|
||||||
Vector3 torqueImpulseLocal = torqueImpulseWorld.Transform( worldToLocal );
|
|
||||||
|
|
||||||
var InverseInertiaDiagLocal = physObj.Inertia.Inverse;
|
Vector3 localAngularVelocity = new(
|
||||||
|
localTorque.x * localInverseInertia.x,
|
||||||
|
localTorque.y * localInverseInertia.y,
|
||||||
|
localTorque.z * localInverseInertia.z );
|
||||||
|
|
||||||
// Compute angular velocity change in rad/s (local frame)
|
return (linearVelocity, localAngularVelocity);
|
||||||
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>
|
||||||
@ -92,7 +63,7 @@ public static class PhysicsExtensions
|
|||||||
Vector3 linearImpulse = impulse;
|
Vector3 linearImpulse = impulse;
|
||||||
|
|
||||||
// 2. Calculate angular impulse (torque) from the offset force
|
// 2. Calculate angular impulse (torque) from the offset force
|
||||||
// τ = r * F (cross product of position relative to COM and force)
|
// τ = r × F (cross product of position relative to COM and force)
|
||||||
Vector3 centerOfMass = physObj.MassCenter;
|
Vector3 centerOfMass = physObj.MassCenter;
|
||||||
Vector3 relativePosition = position - centerOfMass;
|
Vector3 relativePosition = position - centerOfMass;
|
||||||
Vector3 worldAngularImpulse = relativePosition.Cross( impulse );
|
Vector3 worldAngularImpulse = relativePosition.Cross( impulse );
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user